]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/show-sig'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 Jan 2012 20:44:07 +0000 (12:44 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 Jan 2012 20:44:07 +0000 (12:44 -0800)
* jc/show-sig:
  log --show-signature: reword the common two-head merge case
  log-tree: show mergetag in log --show-signature output
  log-tree.c: small refactor in show_signature()
  commit --amend -S: strip existing gpgsig headers
  verify_signed_buffer: fix stale comment
  gpg-interface: allow use of a custom GPG binary
  pretty: %G[?GS] placeholders
  test "commit -S" and "log --show-signature"
  log: --show-signature
  commit: teach --gpg-sign option

Conflicts:
builtin/commit-tree.c
builtin/commit.c
builtin/merge.c
notes-cache.c
pretty.c

12 files changed:
1  2 
Documentation/RelNotes/1.7.9.txt
Documentation/config.txt
Documentation/git-tag.txt
builtin/commit-tree.c
builtin/commit.c
builtin/merge.c
commit.c
commit.h
log-tree.c
notes-cache.c
notes-merge.c
pretty.c

index 9689efa986c27ffdc9684f62767bfc7cd93fb113,0000000000000000000000000000000000000000..1c548d94362ec5d9fe36873d377b838d649cde9c
mode 100644,000000..100644
--- /dev/null
@@@ -1,112 -1,0 +1,108 @@@
-  * gitweb did not correctly fall back to configured $fallback_encoding
-    that is not 'latin1'.
-    (merge b13e3ea jn/maint-gitweb-utf8-fix later to maint).
 +Git v1.7.9 Release Notes (draft)
 +========================
 +
 +Updates since v1.7.8
 +--------------------
 +
 + * gitk updates accumulated since early 2011.
 +
 + * git-gui updated to 0.16.0.
 +
 + * git-p4 (in contrib/) updates.
 +
 + * Git uses gettext to translate its most common interface messages
 +   into the user's language if translations are available and the
 +   locale is appropriately set. Distributors can drop in new PO files
 +   in po/ to add new translations.
 +
 + * The code to handle username/password for HTTP transaction used in
 +   "git push" & "git fetch" learned to talk "credential API" to
 +   external programs to cache or store them, to allow integration with
 +   platform native keychain mechanisms.
 +
 + * The prompted input in the terminal use our own getpass() replacement
 +   when possible. HTTP transactions used to ask username without echoing
 +   back what was typed, but with this change you will see it as you type.
 +
 + * The internal of "revert/cherry-pick" has been tweaked to prepare
 +   building more generic "sequencer" on top of the implementation that
 +   drives them.
 +
 + * "git add" learned to stream large files directly into a packfile
 +   instead of writing them into individual loose object files.
 +
 + * "git checkout -B <current branch> <elsewhere>" is a more intuitive
 +   way to spell "git reset --keep <elsewhere>".
 +
 + * "git checkout" and "git merge" learned "--no-overwrite-ignore" option
 +   to tell Git that untracked and ignored files are not expendable.
 +
 + * "git commit --amend" learned "--no-edit" option to say that the
 +   user is amending the tree being recorded, without updating the
 +   commit log message.
 +
 + * "git commit" and "git reset" re-learned the optimization to prime
 +   the cache-tree information in the index, which makes it faster to
 +   write a tree object out after the index entries are updated.
 +
 + * "git commit" detects and rejects an attempt to stuff NUL byte in
 +   the commit log message.
 +
 + * fsck and prune are relatively lengthy operations that still go
 +   silent while making the end-user wait. They learned to give progress
 +   output like other slow operations.
 +
 + * The set of built-in function-header patterns for various languages
 +   knows MATLAB.
 +
 + * "git log --format='<format>'" learned new %g[nNeE] specifiers to
 +   show information from the reflog entries when warlking the reflog
 +   (i.e. with "-g").
 +
 + * "git pull" can be used to fetch and merge an annotated/signed tag,
 +   instead of the tip of a topic branch. The GPG signature from the
 +   signed tag is recorded in the resulting merge commit for later
 +   auditing.
 +
 + * "git branch --edit-description" can be used to add descriptive text
 +   to explain what a topic branch is about.
 +
 + * "git fmt-merge-msg" learned to take the branch description into
 +   account when preparing a merge summary that "git merge" records
 +   when merging a local branch.
 +
 + * "git request-pull" has been updated to convey more information
 +   useful for integrators to decide if a topic is worth merging and
 +   what is pulled is indeed what the requestor asked to pull,
 +   including:
 +
 +   - the tip of the branch being requested to be merged;
 +   - the branch description describing what the topic is about;
 +   - the contents of the annotated tag, when requesting to pull a tag.
 +
 + * "git pull" learned to notice 'pull.rebase' configuration variable,
 +   which serves as a global fallback for setting 'branch.<name>.rebase'
 +   configuration variable per branch.
 +
 + * "git tag" learned "--cleanup" option to control how the whitespaces
 +   and empty lines in tag message are cleaned up.
 +
 + * "gitweb" learned to show side-by-side diff.
 +
 +Also contains minor documentation updates and code clean-ups.
 +
 +
 +Fixes since v1.7.8
 +------------------
 +
 +Unless otherwise noted, all the fixes since v1.7.8 in the maintenance
 +releases are contained in this release (see release notes to them for
 +details).
 +
 +--
 +exec >/var/tmp/1
 +O=v1.7.8.2-301-g48de656
 +echo O=$(git describe master)
 +git log --first-parent --oneline --reverse ^$O master
 +echo
 +git shortlog --no-merges ^$O ^maint master
diff --combined Documentation/config.txt
index 27c7689007556e288b55107cfcb0f04d94b6c16e,094c1c9de31f634a8acc37c2217e3e1202d73080..04f5e19dc343d07d48f17e266fa557896f68d43d
@@@ -45,10 -45,9 +45,10 @@@ lines.  Variables may belong directly t
  You can have `[section]` if you have `[section "subsection"]`, but you
  don't need to.
  
 -There is also a case insensitive alternative `[section.subsection]` syntax.
 -In this syntax, subsection names follow the same restrictions as for section
 -names.
 +There is also a deprecated `[section.subsection]` syntax. With this
 +syntax, the subsection name is converted to lower-case and is also
 +compared case sensitively. These subsection names follow the same
 +restrictions as section names.
  
  All the other lines (and the remainder of the line after the section
  header) are recognized as setting variables, in the form
@@@ -115,32 -114,35 +115,32 @@@ in the appropriate manual page. You wil
  porcelain configuration variables in the respective porcelain documentation.
  
  advice.*::
 -      When set to 'true', display the given optional help message.
 -      When set to 'false', do not display. The configuration variables
 -      are:
 +      These variables control various optional help messages designed to
 +      aid new users. All 'advice.*' variables default to 'true', and you
 +      can tell Git that you do not need help by setting these to 'false':
  +
  --
        pushNonFastForward::
                Advice shown when linkgit:git-push[1] refuses
 -              non-fast-forward refs. Default: true.
 +              non-fast-forward refs.
        statusHints::
                Directions on how to stage/unstage/add shown in the
                output of linkgit:git-status[1] and the template shown
 -              when writing commit messages. Default: true.
 +              when writing commit messages.
        commitBeforeMerge::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting local changes.
 -              Default: true.
        resolveConflict::
                Advices shown by various commands when conflicts
                prevent the operation from being performed.
 -              Default: true.
        implicitIdentity::
                Advice on how to set your identity configuration when
                your information is guessed from the system username and
 -              domain name. Default: true.
 -
 +              domain name.
        detachedHead::
 -              Advice shown when you used linkgit::git-checkout[1] to
 +              Advice shown when you used linkgit:git-checkout[1] to
                move to the detach HEAD state, to instruct how to create
 -              a local branch after the fact.  Default: true.
 +              a local branch after the fact.
  --
  
  core.fileMode::
@@@ -471,12 -473,6 +471,12 @@@ core.editor:
        variable when it is set, and the environment variable
        `GIT_EDITOR` is not set.  See linkgit:git-var[1].
  
 +sequence.editor::
 +      Text editor used by `git rebase -i` for editing the rebase insn file.
 +      The value is meant to be interpreted by the shell when it is used.
 +      It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
 +      When not configured the default commit message editor is used instead.
 +
  core.pager::
        The command that git will use to paginate output.  Can
        be overridden with the `GIT_PAGER` environment
@@@ -674,12 -670,10 +674,12 @@@ branch.<name>.mergeoptions:
  branch.<name>.rebase::
        When true, rebase the branch <name> on top of the fetched branch,
        instead of merging the default branch from the default remote when
 -      "git pull" is run.
 -      *NOTE*: this is a possibly dangerous operation; do *not* use
 -      it unless you understand the implications (see linkgit:git-rebase[1]
 -      for details).
 +      "git pull" is run. See "pull.rebase" for doing this in a non
 +      branch-specific manner.
 ++
 +*NOTE*: this is a possibly dangerous operation; do *not* use
 +it unless you understand the implications (see linkgit:git-rebase[1]
 +for details).
  
  browser.<tool>.cmd::
        Specify the command to invoke the specified browser. The
@@@ -831,29 -825,6 +831,29 @@@ commit.template:
        "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
        specified user's home directory.
  
 +credential.helper::
 +      Specify an external helper to be called when a username or
 +      password credential is needed; the helper may consult external
 +      storage to avoid prompting the user for the credentials. See
 +      linkgit:gitcredentials[7] for details.
 +
 +credential.useHttpPath::
 +      When acquiring credentials, consider the "path" component of an http
 +      or https URL to be important. Defaults to false. See
 +      linkgit:gitcredentials[7] for more information.
 +
 +credential.username::
 +      If no username is set for a network authentication, use this username
 +      by default. See credential.<context>.* below, and
 +      linkgit:gitcredentials[7].
 +
 +credential.<url>.*::
 +      Any of the credential.* options above can be applied selectively to
 +      some credentials. For example "credential.https://example.com.username"
 +      would set the default username only for https connections to
 +      example.com. See linkgit:gitcredentials[7] for details on how URLs are
 +      matched.
 +
  include::diff-config.txt[]
  
  difftool.<tool>.path::
@@@ -1123,6 -1094,17 +1123,17 @@@ grep.lineNumber:
  grep.extendedRegexp::
        If set to true, enable '--extended-regexp' option by default.
  
+ gpg.program::
+       Use this custom program instead of "gpg" found on $PATH when
+       making or verifying a PGP signature. The program must support the
+       same command line interface as GPG, namely, to verify a detached
+       signature, "gpg --verify $file - <$signature" is run, and the
+       program is expected to signal a good signature by exiting with
+       code 0, and to generate an ascii-armored detached signature, the
+       standard input of "gpg -bsau $key" is fed with the contents to be
+       signed, and the program is expected to send the result to its
+       standard output.
  gui.commitmsgwidth::
        Defines how wide the commit message window is in the
        linkgit:git-gui[1]. "75" is the default.
@@@ -1612,16 -1594,6 +1623,16 @@@ pretty.<name>:
        Note that an alias with the same name as a built-in format
        will be silently ignored.
  
 +pull.rebase::
 +      When true, rebase branches on top of the fetched branch, instead
 +      of merging the default branch from the default remote when "git
 +      pull" is run. See "branch.<name>.rebase" for setting this on a
 +      per-branch basis.
 ++
 +*NOTE*: this is a possibly dangerous operation; do *not* use
 +it unless you understand the implications (see linkgit:git-rebase[1]
 +for details).
 +
  pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@@ -1772,10 -1744,10 +1783,10 @@@ rerere.autoupdate:
  
  rerere.enabled::
        Activate recording of resolved conflicts, so that identical
 -      conflict hunks can be resolved automatically, should they
 -      be encountered again.  linkgit:git-rerere[1] command is by
 -      default enabled if you create `rr-cache` directory under
 -      `$GIT_DIR`, but can be disabled by setting this option to false.
 +      conflict hunks can be resolved automatically, should they be
 +      encountered again.  By default, linkgit:git-rerere[1] is
 +      enabled if there is an `rr-cache` directory under the
 +      `$GIT_DIR`.
  
  sendemail.identity::
        A configuration identity. When given, causes values in the
index 622a019eb034e61f73bf326ed1f000fd90fe9375,74fc7e006f8bb3511c2a333c07ee812f7c6a784b..53ff5f6cf7b9420933b022accace1355db6337c6
@@@ -38,7 -38,9 +38,9 @@@ created (i.e. a lightweight tag)
  A GnuPG signed tag object will be created when `-s` or `-u
  <key-id>` is used.  When `-u <key-id>` is not used, the
  committer identity for the current user is used to find the
- GnuPG key for signing.
+ GnuPG key for signing.        The configuration variable `gpg.program`
+ is used to specify custom GnuPG binary.
  
  OPTIONS
  -------
  
  -s::
  --sign::
-       Make a GPG-signed tag, using the default e-mail address's key
+       Make a GPG-signed tag, using the default e-mail address's key.
  
  -u <key-id>::
  --local-user=<key-id>::
-       Make a GPG-signed tag, using the given key
+       Make a GPG-signed tag, using the given key.
  
  -f::
  --force::
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
        is given.
  
 +--cleanup=<mode>::
 +      This option sets how the tag message is cleaned up.
 +      The  '<mode>' can be one of 'verbatim', 'whitespace' and 'strip'.  The
 +      'strip' mode is default. The 'verbatim' mode does not change message at
 +      all, 'whitespace' removes just leading/trailing whitespace lines and
 +      'strip' removes both whitespace and commentary.
 +
  <tagname>::
        The name of the tag to create, delete, or describe.
        The new tag name must pass all checks defined by
diff --combined builtin/commit-tree.c
index 05353c30f292684b44b42f7f744d8eb1b7062f69,d5e19af54702d5d2ab38a5e4ef2cab6c9b88eead..164b655df93fea1ec2f63f5238c1ad7a377c8385
@@@ -8,8 -8,9 +8,9 @@@
  #include "tree.h"
  #include "builtin.h"
  #include "utf8.h"
+ #include "gpg-interface.h"
  
- static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-m <message>] [-F <file>] <sha1> <changelog";
+ static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
  
  static void new_parent(struct commit *parent, struct commit_list **parents_p)
  {
        commit_list_insert(parent, parents_p);
  }
  
+ static int commit_tree_config(const char *var, const char *value, void *cb)
+ {
+       int status = git_gpg_config(var, value, NULL);
+       if (status)
+               return status;
+       return git_default_config(var, value, cb);
+ }
  int cmd_commit_tree(int argc, const char **argv, const char *prefix)
  {
        int i, got_tree = 0;
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
        struct strbuf buffer = STRBUF_INIT;
+       const char *sign_commit = NULL;
  
-       git_config(git_default_config, NULL);
+       git_config(commit_tree_config, NULL);
  
        if (argc < 2 || !strcmp(argv[1], "-h"))
                usage(commit_tree_usage);
  
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "-p")) {
                        continue;
                }
  
+               if (!memcmp(arg, "-S", 2)) {
+                       sign_commit = arg + 2;
+                       continue;
+               }
                if (!strcmp(arg, "-m")) {
                        if (argc <= ++i)
                                usage(commit_tree_usage);
                        die_errno("git commit-tree: failed to read");
        }
  
-       if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, NULL)) {
 -      if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,
++      if (commit_tree(&buffer, tree_sha1, parents, commit_sha1,
+                       NULL, sign_commit)) {
                strbuf_release(&buffer);
                return 1;
        }
diff --combined builtin/commit.c
index 5891e95758a0d2530b325bd5fd346e15702e3017,970a83662a67a8f1c6cb8a67fd2e6636a5260948..eba1377eb32c02e57c46364c381df940afa66048
@@@ -26,6 -26,7 +26,7 @@@
  #include "unpack-trees.h"
  #include "quote.h"
  #include "submodule.h"
+ #include "gpg-interface.h"
  
  static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@@ -81,11 -82,12 +82,13 @@@ static const char *template_file
  static const char *author_message, *author_message_buffer;
  static char *edit_message, *use_message;
  static char *fixup_message, *squash_message;
 -static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
 +static int all, also, interactive, patch_interactive, only, amend, signoff;
 +static int edit_flag = -1; /* unspecified */
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static int no_post_rewrite, allow_empty_message;
  static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+ static char *sign_commit;
  /*
   * The default commit message cleanup mode will remove the lines
   * beginning with # (shell comments) and leading and trailing
@@@ -104,7 -106,7 +107,7 @@@ static enum commit_whence whence
  static int use_editor = 1, include_status = 1;
  static int show_ignored_in_status;
  static const char *only_include_assumed;
 -static struct strbuf message;
 +static struct strbuf message = STRBUF_INIT;
  
  static int null_termination;
  static enum {
@@@ -139,12 -141,14 +142,14 @@@ static struct option builtin_commit_opt
        OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
        OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
        OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
 -      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
 +      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
 -      OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 +      OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
        OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+       { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+         "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
        /* end commit message options */
  
        OPT_GROUP("Commit contents options"),
@@@ -395,7 -399,6 +400,7 @@@ static char *prepare_index(int argc, co
                fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache_or_die(refresh_flags);
 +              update_main_cache_tree(1);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
 +                      update_main_cache_tree(1);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
@@@ -865,7 -867,10 +870,7 @@@ static int prepare_to_commit(const cha
         */
        discard_cache();
        read_cache_from(index_file);
 -      if (!active_cache_tree)
 -              active_cache_tree = cache_tree();
 -      if (cache_tree_update(active_cache_tree,
 -                            active_cache, active_nr, 0, 0) < 0) {
 +      if (update_main_cache_tree(0)) {
                error(_("Error building trees"));
                return 0;
        }
@@@ -1020,8 -1025,8 +1025,8 @@@ static int parse_and_validate_options(i
  
        if (logfile || message.len || use_message || fixup_message)
                use_editor = 0;
 -      if (edit_flag)
 -              use_editor = 1;
 +      if (0 <= edit_flag)
 +              use_editor = edit_flag;
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
  
@@@ -1259,7 -1264,7 +1264,7 @@@ static void print_summary(const char *p
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
        unsigned char junk_sha1[20];
 -      const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
 +      const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
 +      head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
        printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
  static int git_commit_config(const char *k, const char *v, void *cb)
  {
        struct wt_status *s = cb;
+       int status;
  
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
                return 0;
        }
  
+       status = git_gpg_config(k, v, NULL);
+       if (status)
+               return status;
        return git_status_config(k, v, s);
  }
  
@@@ -1486,14 -1494,15 +1495,15 @@@ int cmd_commit(int argc, const char **a
        }
  
        if (amend) {
-               extra = read_commit_extra_headers(current_head);
+               const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+               extra = read_commit_extra_headers(current_head, exclude_gpgsig);
        } else {
                struct commit_extra_header **tail = &extra;
                append_merge_tag_headers(parents, &tail);
        }
  
 -      if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1,
 +      if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
-                                author_ident.buf, extra)) {
+                                author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
        }
  
        unlink(git_path("CHERRY_PICK_HEAD"));
 +      unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
diff --combined builtin/merge.c
index 4b0ca6550c130dc8efbf248c56503018b8bcb604,e5afe64cefba88cd435a89c9d4d87f459488853d..3a451727d0e637ae197c9a1193435e57e84e6c58
@@@ -26,7 -26,7 +26,8 @@@
  #include "merge-recursive.h"
  #include "resolve-undo.h"
  #include "remote.h"
 +#include "fmt-merge-msg.h"
+ #include "gpg-interface.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -45,12 -45,11 +46,12 @@@ static const char * const builtin_merge
        NULL
  };
  
 -static int show_diffstat = 1, shortlog_len, squash;
 +static int show_diffstat = 1, shortlog_len = -1, squash;
  static int option_commit = 1, allow_fast_forward = 1;
  static int fast_forward_only, option_edit;
  static int allow_trivial = 1, have_message;
 -static struct strbuf merge_msg;
 +static int overwrite_ignore = 1;
 +static struct strbuf merge_msg = STRBUF_INIT;
  static struct commit_list *remoteheads;
  static struct strategy **use_strategies;
  static size_t use_strategies_nr, use_strategies_alloc;
@@@ -64,6 -63,7 +65,7 @@@ static int allow_rerere_auto
  static int abort_current_merge;
  static int show_progress = -1;
  static int default_to_upstream;
+ static const char *sign_commit;
  
  static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@@ -209,7 -209,8 +211,9 @@@ static struct option builtin_merge_opti
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
        OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+       { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+         "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 +      OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
        OPT_END()
  };
  
@@@ -319,15 -320,13 +323,15 @@@ static void squash_message(struct commi
        struct rev_info rev;
        struct strbuf out = STRBUF_INIT;
        struct commit_list *j;
 +      const char *filename;
        int fd;
        struct pretty_print_context ctx = {0};
  
        printf(_("Squash commit -- not updating HEAD\n"));
 -      fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
 +      filename = git_path("SQUASH_MSG");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
 -              die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
 +              die_errno(_("Could not write to '%s'"), filename);
  
        init_revisions(&rev, NULL);
        rev.ignore_merges = 1;
@@@ -415,7 -414,7 +419,7 @@@ static void finish(struct commit *head_
  static void merge_name(const char *remote, struct strbuf *msg)
  {
        struct commit *remote_head;
 -      unsigned char branch_head[20], buf_sha[20];
 +      unsigned char branch_head[20];
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
                strbuf_addstr(&truname, "refs/heads/");
                strbuf_addstr(&truname, remote);
                strbuf_setlen(&truname, truname.len - len);
 -              if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
 +              if (ref_exists(truname.buf)) {
                        strbuf_addf(msg,
                                    "%s\t\tbranch '%s'%s of .\n",
                                    sha1_to_hex(remote_head->object.sha1),
  
        if (!strcmp(remote, "FETCH_HEAD") &&
                        !access(git_path("FETCH_HEAD"), R_OK)) {
 +              const char *filename;
                FILE *fp;
                struct strbuf line = STRBUF_INIT;
                char *ptr;
  
 -              fp = fopen(git_path("FETCH_HEAD"), "r");
 +              filename = git_path("FETCH_HEAD");
 +              fp = fopen(filename, "r");
                if (!fp)
                        die_errno(_("could not open '%s' for reading"),
 -                                git_path("FETCH_HEAD"));
 +                                filename);
                strbuf_getline(&line, fp, '\n');
                fclose(fp);
                ptr = strstr(line.buf, "\tnot-for-merge\t");
@@@ -558,7 -555,15 +562,7 @@@ static int git_merge_config(const char 
                return git_config_string(&pull_octopus, k, v);
        else if (!strcmp(k, "merge.renormalize"))
                option_renormalize = git_config_bool(k, v);
 -      else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
 -              int is_bool;
 -              shortlog_len = git_config_bool_or_int(k, v, &is_bool);
 -              if (!is_bool && shortlog_len < 0)
 -                      return error(_("%s: negative length %s"), k, v);
 -              if (is_bool && shortlog_len)
 -                      shortlog_len = DEFAULT_MERGE_LOG_LEN;
 -              return 0;
 -      } else if (!strcmp(k, "merge.ff")) {
 +      else if (!strcmp(k, "merge.ff")) {
                int boolval = git_config_maybe_bool(k, v);
                if (0 <= boolval) {
                        allow_fast_forward = boolval;
                default_to_upstream = git_config_bool(k, v);
                return 0;
        }
 +      status = fmt_merge_msg_config(k, v, cb);
++      if (status)
++              return status;
+       status = git_gpg_config(k, v, NULL);
        if (status)
                return status;
        return git_diff_ui_config(k, v, cb);
@@@ -768,12 -774,10 +776,12 @@@ int checkout_fast_forward(const unsigne
        memset(&trees, 0, sizeof(trees));
        memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
 -      memset(&dir, 0, sizeof(dir));
 -      dir.flags |= DIR_SHOW_IGNORED;
 -      dir.exclude_per_dir = ".gitignore";
 -      opts.dir = &dir;
 +      if (overwrite_ignore) {
 +              memset(&dir, 0, sizeof(dir));
 +              dir.flags |= DIR_SHOW_IGNORED;
 +              setup_standard_excludes(&dir);
 +              opts.dir = &dir;
 +      }
  
        opts.head_idx = 1;
        opts.src_index = &the_index;
@@@ -848,22 -852,20 +856,22 @@@ static void add_strategies(const char *
  
  static void write_merge_msg(struct strbuf *msg)
  {
 -      int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 +      const char *filename = git_path("MERGE_MSG");
 +      int fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MSG"));
 +                        filename);
        if (write_in_full(fd, msg->buf, msg->len) != msg->len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
  static void read_merge_msg(struct strbuf *msg)
  {
 +      const char *filename = git_path("MERGE_MSG");
        strbuf_reset(msg);
 -      if (strbuf_read_file(msg, git_path("MERGE_MSG"), 0) < 0)
 -              die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
 +      if (strbuf_read_file(msg, filename, 0) < 0)
 +              die_errno(_("Could not read from '%s'"), filename);
  }
  
  static void write_merge_state(void);
@@@ -910,8 -912,8 +918,9 @@@ static int merge_trivial(struct commit 
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
        prepare_to_commit();
-       if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL))
 -      commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,
 -                  sign_commit);
++      if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
++                      sign_commit))
 +              die(_("failed to write commit object"));
        finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
@@@ -942,8 -944,8 +951,9 @@@ static int finish_automerge(struct comm
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit();
        free_commit_list(remoteheads);
-       if (commit_tree(&merge_msg, result_tree, parents, result_commit, NULL))
 -      commit_tree(merge_msg.buf, result_tree, parents, result_commit,
 -                  NULL, sign_commit);
++      if (commit_tree(&merge_msg, result_tree, parents, result_commit,
++                      NULL, sign_commit))
 +              die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
  
  static int suggest_conflicts(int renormalizing)
  {
 +      const char *filename;
        FILE *fp;
        int pos;
  
 -      fp = fopen(git_path("MERGE_MSG"), "a");
 +      filename = git_path("MERGE_MSG");
 +      fp = fopen(filename, "a");
        if (!fp)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MSG"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        fprintf(fp, "\nConflicts:\n");
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@@ -1052,7 -1053,6 +1062,7 @@@ static int setup_with_upstream(const ch
  
  static void write_merge_state(void)
  {
 +      const char *filename;
        int fd;
        struct commit_list *j;
        struct strbuf buf = STRBUF_INIT;
                }
                strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
        }
 -      fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
 +      filename = git_path("MERGE_HEAD");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_HEAD"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
        strbuf_addch(&merge_msg, '\n');
        write_merge_msg(&merge_msg);
 -      fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
 +
 +      filename = git_path("MERGE_MODE");
 +      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd < 0)
 -              die_errno(_("Could not open '%s' for writing"),
 -                        git_path("MERGE_MODE"));
 +              die_errno(_("Could not open '%s' for writing"), filename);
        strbuf_reset(&buf);
        if (!allow_fast_forward)
                strbuf_addf(&buf, "no-ff");
        if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
@@@ -1097,12 -1096,11 +1107,12 @@@ int cmd_merge(int argc, const char **ar
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
 -      int flag, i;
 +      int flag, i, ret = 0;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list **remotes = &remoteheads;
 +      void *branch_to_free;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_merge_usage, builtin_merge_options);
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
 -      branch = resolve_ref("HEAD", head_sha1, 0, &flag);
 +      branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
 +      if (shortlog_len < 0)
 +              shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
  
        if (verbosity < 0 && show_progress == -1)
                show_progress = 0;
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
  
                /* Invoke 'git reset --merge' */
 -              return cmd_reset(nargc, nargv, prefix);
 +              ret = cmd_reset(nargc, nargv, prefix);
 +              goto done;
        }
  
        if (read_cache_unmerged())
                die(_("You cannot combine --no-ff with --ff-only."));
  
        if (!abort_current_merge) {
 -              if (!argc && default_to_upstream)
 -                      argc = setup_with_upstream(&argv);
 -              else if (argc == 1 && !strcmp(argv[0], "-"))
 +              if (!argc) {
 +                      if (default_to_upstream)
 +                              argc = setup_with_upstream(&argv);
 +                      else
 +                              die(_("No commit specified and merge.defaultToUpstream not set."));
 +              } else if (argc == 1 && !strcmp(argv[0], "-"))
                        argv[0] = "@{-1}";
        }
        if (!argc)
                read_empty(remote_head->object.sha1, 0);
                update_ref("initial pull", "HEAD", remote_head->object.sha1,
                           NULL, 0, DIE_ON_ERR);
 -              return 0;
 +              goto done;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
  
                 * but first the most common case of merging one remote.
                 */
                finish_up_to_date("Already up-to-date.");
 -              return 0;
 +              goto done;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
                        !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
                commit = remoteheads->item;
 -              if (!commit)
 -                      return 1;
 +              if (!commit) {
 +                      ret = 1;
 +                      goto done;
 +              }
  
                if (checkout_fast_forward(head_commit->object.sha1,
 -                                        commit->object.sha1))
 -                      return 1;
 +                                        commit->object.sha1)) {
 +                      ret = 1;
 +                      goto done;
 +              }
  
                finish(head_commit, commit->object.sha1, msg.buf);
                drop_save();
 -              return 0;
 +              goto done;
        } else if (!remoteheads->next && common->next)
                ;
                /*
                        git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        printf(_("Trying really trivial in-index merge...\n"));
                        if (!read_tree_trivial(common->item->object.sha1,
 -                                      head_commit->object.sha1, remoteheads->item->object.sha1))
 -                              return merge_trivial(head_commit);
 +                                             head_commit->object.sha1,
 +                                             remoteheads->item->object.sha1)) {
 +                              ret = merge_trivial(head_commit);
 +                              goto done;
 +                      }
                        printf(_("Nope.\n"));
                }
        } else {
                }
                if (up_to_date) {
                        finish_up_to_date("Already up-to-date. Yeeah!");
 -                      return 0;
 +                      goto done;
                }
        }
  
         * If we have a resulting tree, that means the strategy module
         * auto resolved the merge cleanly.
         */
 -      if (automerge_was_ok)
 -              return finish_automerge(head_commit, common, result_tree,
 -                                      wt_strategy);
 +      if (automerge_was_ok) {
 +              ret = finish_automerge(head_commit, common, result_tree,
 +                                     wt_strategy);
 +              goto done;
 +      }
  
        /*
         * Pick the result from the best strategy and have the user fix
                else
                        fprintf(stderr, _("Merge with strategy %s failed.\n"),
                                use_strategies[0]->name);
 -              return 2;
 +              ret = 2;
 +              goto done;
        } else if (best_strategy == wt_strategy)
                ; /* We already have its result in the working tree. */
        else {
        else
                write_merge_state();
  
 -      if (merge_was_ok) {
 +      if (merge_was_ok)
                fprintf(stderr, _("Automatic merge went well; "
                        "stopped before committing as requested\n"));
 -              return 0;
 -      } else
 -              return suggest_conflicts(option_renormalize);
 +      else
 +              ret = suggest_conflicts(option_renormalize);
 +
 +done:
 +      free(branch_to_free);
 +      return ret;
  }
diff --combined commit.c
index 44bc96d44d391209d3a60e9e8c1b98ed209e6b4a,2162a7c5724c1dcdd42b249c11d1995e66abcd6f..35af4988f0ff83c6a3379ea9f6de4e4e1568c39f
+++ b/commit.c
@@@ -6,6 -6,7 +6,7 @@@
  #include "diff.h"
  #include "revision.h"
  #include "notes.h"
+ #include "gpg-interface.h"
  
  int save_commit_buffer = 1;
  
@@@ -840,6 -841,86 +841,86 @@@ struct commit_list *reduce_heads(struc
        return result;
  }
  
+ static const char gpg_sig_header[] = "gpgsig";
+ static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+ static int do_sign_commit(struct strbuf *buf, const char *keyid)
+ {
+       struct strbuf sig = STRBUF_INIT;
+       int inspos, copypos;
+       /* find the end of the header */
+       inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+       if (!keyid || !*keyid)
+               keyid = get_signing_key();
+       if (sign_buffer(buf, &sig, keyid)) {
+               strbuf_release(&sig);
+               return -1;
+       }
+       for (copypos = 0; sig.buf[copypos]; ) {
+               const char *bol = sig.buf + copypos;
+               const char *eol = strchrnul(bol, '\n');
+               int len = (eol - bol) + !!*eol;
+               if (!copypos) {
+                       strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+                       inspos += gpg_sig_header_len;
+               }
+               strbuf_insert(buf, inspos++, " ", 1);
+               strbuf_insert(buf, inspos, bol, len);
+               inspos += len;
+               copypos += len;
+       }
+       strbuf_release(&sig);
+       return 0;
+ }
+ int parse_signed_commit(const unsigned char *sha1,
+                       struct strbuf *payload, struct strbuf *signature)
+ {
+       unsigned long size;
+       enum object_type type;
+       char *buffer = read_sha1_file(sha1, &type, &size);
+       int in_signature, saw_signature = -1;
+       char *line, *tail;
+       if (!buffer || type != OBJ_COMMIT)
+               goto cleanup;
+       line = buffer;
+       tail = buffer + size;
+       in_signature = 0;
+       saw_signature = 0;
+       while (line < tail) {
+               const char *sig = NULL;
+               char *next = memchr(line, '\n', tail - line);
+               next = next ? next + 1 : tail;
+               if (in_signature && line[0] == ' ')
+                       sig = line + 1;
+               else if (!prefixcmp(line, gpg_sig_header) &&
+                        line[gpg_sig_header_len] == ' ')
+                       sig = line + gpg_sig_header_len + 1;
+               if (sig) {
+                       strbuf_add(signature, sig, next - sig);
+                       saw_signature = 1;
+                       in_signature = 1;
+               } else {
+                       if (*line == '\n')
+                               /* dump the whole remainder of the buffer */
+                               next = tail;
+                       strbuf_add(payload, line, next - line);
+                       in_signature = 0;
+               }
+               line = next;
+       }
+  cleanup:
+       free(buffer);
+       return saw_signature;
+ }
  static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
  {
        struct merge_remote_desc *desc;
@@@ -900,14 -981,15 +981,15 @@@ static void add_extra_header(struct str
                strbuf_addch(buffer, '\n');
  }
  
- struct commit_extra_header *read_commit_extra_headers(struct commit *commit)
+ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
+                                                     const char **exclude)
  {
        struct commit_extra_header *extra = NULL;
        unsigned long size;
        enum object_type type;
        char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
        if (buffer && type == OBJ_COMMIT)
-               extra = read_commit_extra_header_lines(buffer, size);
+               extra = read_commit_extra_header_lines(buffer, size, exclude);
        free(buffer);
        return extra;
  }
@@@ -921,7 -1003,23 +1003,23 @@@ static inline int standard_header_field
                (len == 8 && !memcmp(field, "encoding ", 9)));
  }
  
- struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size)
+ static int excluded_header_field(const char *field, size_t len, const char **exclude)
+ {
+       if (!exclude)
+               return 0;
+       while (*exclude) {
+               size_t xlen = strlen(*exclude);
+               if (len == xlen &&
+                   !memcmp(field, *exclude, xlen) && field[xlen] == ' ')
+                       return 1;
+               exclude++;
+       }
+       return 0;
+ }
+ struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size,
+                                                          const char **exclude)
  {
        struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
        const char *line, *next, *eof, *eob;
                if (next <= eof)
                        eof = next;
  
-               if (standard_header_field(line, eof - line))
+               if (standard_header_field(line, eof - line) ||
+                   excluded_header_field(line, eof - line, exclude))
                        continue;
  
                it = xcalloc(1, sizeof(*it));
@@@ -973,15 -1072,16 +1072,16 @@@ void free_commit_extra_headers(struct c
        }
  }
  
 -int commit_tree(const char *msg, unsigned char *tree,
 +int commit_tree(const struct strbuf *msg, unsigned char *tree,
                struct commit_list *parents, unsigned char *ret,
-               const char *author)
+               const char *author, const char *sign_commit)
  {
        struct commit_extra_header *extra = NULL, **tail = &extra;
        int result;
  
        append_merge_tag_headers(parents, &tail);
-       result = commit_tree_extended(msg, tree, parents, ret, author, extra);
+       result = commit_tree_extended(msg, tree, parents, ret,
+                                     author, sign_commit, extra);
        free_commit_extra_headers(extra);
        return result;
  }
@@@ -991,9 -1091,10 +1091,10 @@@ static const char commit_utf8_warn[] 
  "You may want to amend it after fixing the message, or set the config\n"
  "variable i18n.commitencoding to the encoding your project uses.\n";
  
 -int commit_tree_extended(const char *msg, unsigned char *tree,
 +int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
                         struct commit_list *parents, unsigned char *ret,
-                        const char *author, struct commit_extra_header *extra)
+                        const char *author, const char *sign_commit,
+                        struct commit_extra_header *extra)
  {
        int result;
        int encoding_is_utf8;
  
        assert_sha1_type(tree, OBJ_TREE);
  
 +      if (memchr(msg->buf, '\0', msg->len))
 +              return error("a NUL byte in commit log message not allowed.");
 +
        /* Not having i18n.commitencoding is the same as having utf-8 */
        encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
  
        strbuf_addch(&buffer, '\n');
  
        /* And add the comment */
 -      strbuf_addstr(&buffer, msg);
 +      strbuf_addbuf(&buffer, msg);
  
        /* And check the encoding */
        if (encoding_is_utf8 && !is_utf8(buffer.buf))
                fprintf(stderr, commit_utf8_warn);
  
+       if (sign_commit && do_sign_commit(&buffer, sign_commit))
+               return -1;
        result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
        strbuf_release(&buffer);
        return result;
diff --combined commit.h
index 4df397865abe7161a9f16e3177e3b486bf8434ca,123aea3a7ccf95821d4b4eb5eff9cf7f384d881d..154c0e34ff7d2dbaddcfb66b74d26697ffba6381
+++ b/commit.h
@@@ -191,17 -191,17 +191,17 @@@ struct commit_extra_header 
  extern void append_merge_tag_headers(struct commit_list *parents,
                                     struct commit_extra_header ***tail);
  
 -extern int commit_tree(const char *msg, unsigned char *tree,
 +extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
                       struct commit_list *parents, unsigned char *ret,
-                      const char *author);
+                      const char *author, const char *sign_commit);
  
 -extern int commit_tree_extended(const char *msg, unsigned char *tree,
 +extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
                                struct commit_list *parents, unsigned char *ret,
-                               const char *author,
+                               const char *author, const char *sign_commit,
                                struct commit_extra_header *);
  
- extern struct commit_extra_header *read_commit_extra_headers(struct commit *);
- extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len);
+ extern struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
+ extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
  
  extern void free_commit_extra_headers(struct commit_extra_header *extra);
  
@@@ -218,4 -218,6 +218,6 @@@ struct merge_remote_desc 
   */
  struct commit *get_merge_parent(const char *name);
  
+ extern int parse_signed_commit(const unsigned char *sha1,
+                              struct strbuf *message, struct strbuf *signature);
  #endif /* COMMIT_H */
diff --combined log-tree.c
index 319bd31e2bd67f5ee38d6b887da2ca1b8e9ac742,3cf569ce7eccc07cd69cf659df0814724647c5a2..c719a6e38597130597083666e5328517b075228e
@@@ -8,6 -8,7 +8,7 @@@
  #include "refs.h"
  #include "string-list.h"
  #include "color.h"
+ #include "gpg-interface.h"
  
  struct decoration name_decoration = { "object names" };
  
@@@ -403,6 -404,129 +404,129 @@@ void log_write_email_headers(struct rev
        *extra_headers_p = extra_headers;
  }
  
+ static void show_sig_lines(struct rev_info *opt, int status, const char *bol)
+ {
+       const char *color, *reset, *eol;
+       color = diff_get_color_opt(&opt->diffopt,
+                                  status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
+       reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
+       while (*bol) {
+               eol = strchrnul(bol, '\n');
+               printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
+                      *eol ? "\n" : "");
+               bol = (*eol) ? (eol + 1) : eol;
+       }
+ }
+ static void show_signature(struct rev_info *opt, struct commit *commit)
+ {
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+       if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
+               goto out;
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               strbuf_addstr(&gpg_output, "No signature\n");
+       show_sig_lines(opt, status, gpg_output.buf);
+  out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+ }
+ static int which_parent(const unsigned char *sha1, const struct commit *commit)
+ {
+       int nth;
+       const struct commit_list *parent;
+       for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
+               if (!hashcmp(parent->item->object.sha1, sha1))
+                       return nth;
+               nth++;
+       }
+       return -1;
+ }
+ static int is_common_merge(const struct commit *commit)
+ {
+       return (commit->parents
+               && commit->parents->next
+               && !commit->parents->next->next);
+ }
+ static void show_one_mergetag(struct rev_info *opt,
+                             struct commit_extra_header *extra,
+                             struct commit *commit)
+ {
+       unsigned char sha1[20];
+       struct tag *tag;
+       struct strbuf verify_message;
+       int status, nth;
+       size_t payload_size, gpg_message_offset;
+       hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), sha1);
+       tag = lookup_tag(sha1);
+       if (!tag)
+               return; /* error message already given */
+       strbuf_init(&verify_message, 256);
+       if (parse_tag_buffer(tag, extra->value, extra->len))
+               strbuf_addstr(&verify_message, "malformed mergetag\n");
+       else if (is_common_merge(commit) &&
+                !hashcmp(tag->tagged->sha1,
+                         commit->parents->next->item->object.sha1))
+               strbuf_addf(&verify_message,
+                           "merged tag '%s'\n", tag->tag);
+       else if ((nth = which_parent(tag->tagged->sha1, commit)) < 0)
+               strbuf_addf(&verify_message, "tag %s names a non-parent %s\n",
+                                   tag->tag, tag->tagged->sha1);
+       else
+               strbuf_addf(&verify_message,
+                           "parent #%d, tagged '%s'\n", nth + 1, tag->tag);
+       gpg_message_offset = verify_message.len;
+       payload_size = parse_signature(extra->value, extra->len);
+       if ((extra->len <= payload_size) ||
+           (verify_signed_buffer(extra->value, payload_size,
+                                 extra->value + payload_size,
+                                 extra->len - payload_size,
+                                 &verify_message) &&
+            verify_message.len <= gpg_message_offset)) {
+               strbuf_addstr(&verify_message, "No signature\n");
+               status = -1;
+       }
+       else if (strstr(verify_message.buf + gpg_message_offset,
+                       ": Good signature from "))
+               status = 0;
+       else
+               status = -1;
+       show_sig_lines(opt, status, verify_message.buf);
+       strbuf_release(&verify_message);
+ }
+ static void show_mergetag(struct rev_info *opt, struct commit *commit)
+ {
+       struct commit_extra_header *extra, *to_free;
+       to_free = read_commit_extra_headers(commit, NULL);
+       for (extra = to_free; extra; extra = extra->next) {
+               if (strcmp(extra->key, "mergetag"))
+                       continue; /* not a merge tag */
+               show_one_mergetag(opt, extra, commit);
+       }
+       free_commit_extra_headers(to_free);
+ }
  void show_log(struct rev_info *opt)
  {
        struct strbuf msgbuf = STRBUF_INIT;
                }
        }
  
+       if (opt->show_signature) {
+               show_signature(opt, commit);
+               show_mergetag(opt, commit);
+       }
        if (!commit->buffer)
                return;
  
@@@ -599,7 -728,9 +728,7 @@@ int log_tree_diff_flush(struct rev_inf
  
  static int do_diff_combined(struct rev_info *opt, struct commit *commit)
  {
 -      unsigned const char *sha1 = commit->object.sha1;
 -
 -      diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
 +      diff_tree_combined_merge(commit, opt->dense_combined_merges, opt);
        return !opt->loginfo;
  }
  
diff --combined notes-cache.c
index bea013eeae70f360e12af01345058b1b217848c4,c36a960bc32a9bfa0d5da83b1f7c8cb6a315b17d..eabe4a0d9bf44050dba43cbd821642cdfd3fb40b
@@@ -48,7 -48,6 +48,7 @@@ int notes_cache_write(struct notes_cach
  {
        unsigned char tree_sha1[20];
        unsigned char commit_sha1[20];
 +      struct strbuf msg = STRBUF_INIT;
  
        if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref)
                return -1;
@@@ -57,9 -56,7 +57,9 @@@
  
        if (write_notes_tree(&c->tree, tree_sha1))
                return -1;
 -      if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
 +      strbuf_attach(&msg, c->validity,
 +                    strlen(c->validity), strlen(c->validity) + 1);
-       if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL) < 0)
++      if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
                return -1;
        if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
                       0, QUIET_ON_ERR) < 0)
diff --combined notes-merge.c
index 0d38a1b17d8399c7acf6355d598f0a50d0ec1d3b,61cf18eeab6b36535c7f84e88300a91143a2bad8..fb0832f97d218ecd1812361721800d6288935c06
@@@ -21,6 -21,14 +21,6 @@@ void init_notes_merge_options(struct no
        o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
  }
  
 -#define OUTPUT(o, v, ...) \
 -      do { \
 -              if ((o)->verbosity >= (v)) { \
 -                      printf(__VA_ARGS__); \
 -                      puts(""); \
 -              } \
 -      } while (0)
 -
  static int path_to_sha1(const char *path, unsigned char *sha1)
  {
        char hex_sha1[40];
@@@ -384,26 -392,21 +384,26 @@@ static int merge_one_change_manual(stru
  
        strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj));
  
 -      OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj));
 +      if (o->verbosity >= 2)
 +              printf("Auto-merging notes for %s\n", sha1_to_hex(p->obj));
        check_notes_merge_worktree(o);
        if (is_null_sha1(p->local)) {
                /* D/F conflict, checkout p->remote */
                assert(!is_null_sha1(p->remote));
 -              OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
 -                     "deleted in %s and modified in %s. Version from %s "
 -                     "left in tree.", sha1_to_hex(p->obj), lref, rref, rref);
 +              if (o->verbosity >= 1)
 +                      printf("CONFLICT (delete/modify): Notes for object %s "
 +                              "deleted in %s and modified in %s. Version from %s "
 +                              "left in tree.\n",
 +                              sha1_to_hex(p->obj), lref, rref, rref);
                write_note_to_worktree(p->obj, p->remote);
        } else if (is_null_sha1(p->remote)) {
                /* D/F conflict, checkout p->local */
                assert(!is_null_sha1(p->local));
 -              OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
 -                     "deleted in %s and modified in %s. Version from %s "
 -                     "left in tree.", sha1_to_hex(p->obj), rref, lref, lref);
 +              if (o->verbosity >= 1)
 +                      printf("CONFLICT (delete/modify): Notes for object %s "
 +                              "deleted in %s and modified in %s. Version from %s "
 +                              "left in tree.\n",
 +                              sha1_to_hex(p->obj), rref, lref, lref);
                write_note_to_worktree(p->obj, p->local);
        } else {
                /* "regular" conflict, checkout result of ll_merge() */
                        reason = "add/add";
                assert(!is_null_sha1(p->local));
                assert(!is_null_sha1(p->remote));
 -              OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for "
 -                     "object %s", reason, sha1_to_hex(p->obj));
 +              if (o->verbosity >= 1)
 +                      printf("CONFLICT (%s): Merge conflict in notes for "
 +                              "object %s\n", reason, sha1_to_hex(p->obj));
                ll_merge_in_worktree(o, p);
        }
  
@@@ -436,30 -438,24 +436,30 @@@ static int merge_one_change(struct note
        case NOTES_MERGE_RESOLVE_MANUAL:
                return merge_one_change_manual(o, p, t);
        case NOTES_MERGE_RESOLVE_OURS:
 -              OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj));
 +              if (o->verbosity >= 2)
 +                      printf("Using local notes for %s\n",
 +                                              sha1_to_hex(p->obj));
                /* nothing to do */
                return 0;
        case NOTES_MERGE_RESOLVE_THEIRS:
 -              OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj));
 +              if (o->verbosity >= 2)
 +                      printf("Using remote notes for %s\n",
 +                                              sha1_to_hex(p->obj));
                if (add_note(t, p->obj, p->remote, combine_notes_overwrite))
                        die("BUG: combine_notes_overwrite failed");
                return 0;
        case NOTES_MERGE_RESOLVE_UNION:
 -              OUTPUT(o, 2, "Concatenating local and remote notes for %s",
 -                     sha1_to_hex(p->obj));
 +              if (o->verbosity >= 2)
 +                      printf("Concatenating local and remote notes for %s\n",
 +                                                      sha1_to_hex(p->obj));
                if (add_note(t, p->obj, p->remote, combine_notes_concatenate))
                        die("failed to concatenate notes "
                            "(combine_notes_concatenate)");
                return 0;
        case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
 -              OUTPUT(o, 2, "Concatenating unique lines in local and remote "
 -                     "notes for %s", sha1_to_hex(p->obj));
 +              if (o->verbosity >= 2)
 +                      printf("Concatenating unique lines in local and remote "
 +                              "notes for %s\n", sha1_to_hex(p->obj));
                if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
                        die("failed to concatenate notes "
                            "(combine_notes_cat_sort_uniq)");
@@@ -522,15 -518,14 +522,15 @@@ static int merge_from_diffs(struct note
        conflicts = merge_changes(o, changes, &num_changes, t);
        free(changes);
  
 -      OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree",
 -             conflicts, t->dirty ? "dirty" : "clean");
 +      if (o->verbosity >= 4)
 +              printf("Merge result: %i unmerged notes and a %s notes tree\n",
 +                      conflicts, t->dirty ? "dirty" : "clean");
  
        return conflicts ? -1 : 1;
  }
  
  void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
 -                       const char *msg, unsigned char *result_sha1)
 +                       const struct strbuf *msg, unsigned char *result_sha1)
  {
        unsigned char tree_sha1[20];
  
                /* else: t->ref points to nothing, assume root/orphan commit */
        }
  
-       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+       if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
                die("Failed to commit notes tree to database");
  }
  
@@@ -573,7 -568,7 +573,7 @@@ int notes_merge(struct notes_merge_opti
               o->local_ref, o->remote_ref);
  
        /* Dereference o->local_ref into local_sha1 */
 -      if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
 +      if (read_ref_full(o->local_ref, local_sha1, 0, NULL))
                die("Failed to resolve local notes ref '%s'", o->local_ref);
        else if (!check_refname_format(o->local_ref, 0) &&
                is_null_sha1(local_sha1))
        if (!bases) {
                base_sha1 = null_sha1;
                base_tree_sha1 = EMPTY_TREE_SHA1_BIN;
 -              OUTPUT(o, 4, "No merge base found; doing history-less merge");
 +              if (o->verbosity >= 4)
 +                      printf("No merge base found; doing history-less merge\n");
        } else if (!bases->next) {
                base_sha1 = bases->item->object.sha1;
                base_tree_sha1 = bases->item->tree->object.sha1;
 -              OUTPUT(o, 4, "One merge base found (%.7s)",
 -                     sha1_to_hex(base_sha1));
 +              if (o->verbosity >= 4)
 +                      printf("One merge base found (%.7s)\n",
 +                              sha1_to_hex(base_sha1));
        } else {
                /* TODO: How to handle multiple merge-bases? */
                base_sha1 = bases->item->object.sha1;
                base_tree_sha1 = bases->item->tree->object.sha1;
 -              OUTPUT(o, 3, "Multiple merge bases found. Using the first "
 -                     "(%.7s)", sha1_to_hex(base_sha1));
 +              if (o->verbosity >= 3)
 +                      printf("Multiple merge bases found. Using the first "
 +                              "(%.7s)\n", sha1_to_hex(base_sha1));
        }
  
 -      OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with "
 -             "merge-base %.7s", sha1_to_hex(remote->object.sha1),
 -             sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1));
 +      if (o->verbosity >= 4)
 +              printf("Merging remote commit %.7s into local commit %.7s with "
 +                      "merge-base %.7s\n", sha1_to_hex(remote->object.sha1),
 +                      sha1_to_hex(local->object.sha1),
 +                      sha1_to_hex(base_sha1));
  
        if (!hashcmp(remote->object.sha1, base_sha1)) {
                /* Already merged; result == local commit */
 -              OUTPUT(o, 2, "Already up-to-date!");
 +              if (o->verbosity >= 2)
 +                      printf("Already up-to-date!\n");
                hashcpy(result_sha1, local->object.sha1);
                goto found_result;
        }
        if (!hashcmp(local->object.sha1, base_sha1)) {
                /* Fast-forward; result == remote commit */
 -              OUTPUT(o, 2, "Fast-forward");
 +              if (o->verbosity >= 2)
 +                      printf("Fast-forward\n");
                hashcpy(result_sha1, remote->object.sha1);
                goto found_result;
        }
                struct commit_list *parents = NULL;
                commit_list_insert(remote, &parents); /* LIFO order */
                commit_list_insert(local, &parents);
 -              create_notes_commit(local_tree, parents, o->commit_msg.buf,
 +              create_notes_commit(local_tree, parents, &o->commit_msg,
                                    result_sha1);
        }
  
@@@ -695,12 -683,10 +695,12 @@@ int notes_merge_commit(struct notes_mer
        struct dir_struct dir;
        char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
        int path_len = strlen(path), i;
 -      const char *msg = strstr(partial_commit->buffer, "\n\n");
 +      char *msg = strstr(partial_commit->buffer, "\n\n");
 +      struct strbuf sb_msg = STRBUF_INIT;
  
 -      OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s",
 -             path_len - 1, path);
 +      if (o->verbosity >= 3)
 +              printf("Committing notes in notes merge worktree at %.*s\n",
 +                      path_len - 1, path);
  
        if (!msg || msg[2] == '\0')
                die("partial notes commit has empty message");
                unsigned char obj_sha1[20], blob_sha1[20];
  
                if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
 -                      OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name);
 +                      if (o->verbosity >= 3)
 +                              printf("Skipping non-SHA1 entry '%s'\n",
 +                                                              ent->name);
                        continue;
                }
  
                if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
                        die("Failed to add resolved note '%s' to notes tree",
                            ent->name);
 -              OUTPUT(o, 4, "Added resolved note for object %s: %s",
 -                     sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
 +              if (o->verbosity >= 4)
 +                      printf("Added resolved note for object %s: %s\n",
 +                              sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
        }
  
 -      create_notes_commit(partial_tree, partial_commit->parents, msg,
 +      strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
 +      create_notes_commit(partial_tree, partial_commit->parents, &sb_msg,
                            result_sha1);
 -      OUTPUT(o, 4, "Finalized notes merge commit: %s",
 -             sha1_to_hex(result_sha1));
 +      if (o->verbosity >= 4)
 +              printf("Finalized notes merge commit: %s\n",
 +                      sha1_to_hex(result_sha1));
        free(path);
        return 0;
  }
@@@ -751,8 -732,7 +751,8 @@@ int notes_merge_abort(struct notes_merg
        int ret;
  
        strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
 -      OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf);
 +      if (o->verbosity >= 3)
 +              printf("Removing notes merge worktree at %s\n", buf.buf);
        ret = remove_dir_recursively(&buf, 0);
        strbuf_release(&buf);
        return ret;
diff --combined pretty.c
index 1580299d4037ada9e1f0e3c11c6c5578f44f52dd,392d6565955febf10a5f03863d71a976ca3fe8dc..8688b8f2d45a493aa8b51b29f7d46b2abff7f30e
+++ b/pretty.c
@@@ -9,6 -9,7 +9,7 @@@
  #include "notes.h"
  #include "color.h"
  #include "reflog-walk.h"
+ #include "gpg-interface.h"
  
  static char *user_format;
  static struct cmt_fmt_map {
@@@ -640,6 -641,12 +641,12 @@@ struct format_commit_context 
        const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
+       unsigned commit_signature_parsed:1;
+       struct {
+               char *gpg_output;
+               char good_bad;
+               char *signer;
+       } signature;
        char *message;
        size_t width, indent1, indent2;
  
@@@ -822,23 -829,59 +829,76 @@@ static void rewrap_message_tail(struct 
        c->indent2 = new_indent2;
  }
  
+ static struct {
+       char result;
+       const char *check;
+ } signature_check[] = {
+       { 'G', ": Good signature from " },
+       { 'B', ": BAD signature from " },
+ };
+ static void parse_signature_lines(struct format_commit_context *ctx)
+ {
+       const char *buf = ctx->signature.gpg_output;
+       int i;
+       for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+               const char *found = strstr(buf, signature_check[i].check);
+               const char *next;
+               if (!found)
+                       continue;
+               ctx->signature.good_bad = signature_check[i].result;
+               found += strlen(signature_check[i].check);
+               next = strchrnul(found, '\n');
+               ctx->signature.signer = xmemdupz(found, next - found);
+               break;
+       }
+ }
+ static void parse_commit_signature(struct format_commit_context *ctx)
+ {
+       struct strbuf payload = STRBUF_INIT;
+       struct strbuf signature = STRBUF_INIT;
+       struct strbuf gpg_output = STRBUF_INIT;
+       int status;
+       ctx->commit_signature_parsed = 1;
+       if (parse_signed_commit(ctx->commit->object.sha1,
+                               &payload, &signature) <= 0)
+               goto out;
+       status = verify_signed_buffer(payload.buf, payload.len,
+                                     signature.buf, signature.len,
+                                     &gpg_output);
+       if (status && !gpg_output.len)
+               goto out;
+       ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+       parse_signature_lines(ctx);
+  out:
+       strbuf_release(&gpg_output);
+       strbuf_release(&payload);
+       strbuf_release(&signature);
+ }
 +static int format_reflog_person(struct strbuf *sb,
 +                              char part,
 +                              struct reflog_walk_info *log,
 +                              enum date_mode dmode)
 +{
 +      const char *ident;
 +
 +      if (!log)
 +              return 2;
 +
 +      ident = get_reflog_ident(log);
 +      if (!ident)
 +              return 2;
 +
 +      return format_person_part(sb, part, ident, strlen(ident), dmode);
 +}
 +
  static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
                                void *context)
  {
                        if (c->pretty_ctx->reflog_info)
                                get_reflog_message(sb, c->pretty_ctx->reflog_info);
                        return 2;
 +              case 'n':
 +              case 'N':
 +              case 'e':
 +              case 'E':
 +                      return format_reflog_person(sb,
 +                                                  placeholder[1],
 +                                                  c->pretty_ctx->reflog_info,
 +                                                  c->pretty_ctx->date_mode);
                }
                return 0;       /* unknown %g placeholder */
        case 'N':
                return 0;
        }
  
+       if (placeholder[0] == 'G') {
+               if (!c->commit_signature_parsed)
+                       parse_commit_signature(c);
+               switch (placeholder[1]) {
+               case 'G':
+                       if (c->signature.gpg_output)
+                               strbuf_addstr(sb, c->signature.gpg_output);
+                       break;
+               case '?':
+                       switch (c->signature.good_bad) {
+                       case 'G':
+                       case 'B':
+                               strbuf_addch(sb, c->signature.good_bad);
+                       }
+                       break;
+               case 'S':
+                       if (c->signature.signer)
+                               strbuf_addstr(sb, c->signature.signer);
+                       break;
+               }
+               return 2;
+       }
        /* For the rest we have to parse the commit header. */
        if (!c->commit_header_parsed)
                parse_commit_header(c);
@@@ -1119,6 -1178,7 +1203,6 @@@ void format_commit_message(const struc
  {
        struct format_commit_context context;
        static const char utf8[] = "UTF-8";
 -      const char *enc;
        const char *output_enc = pretty_ctx->output_encoding;
  
        memset(&context, 0, sizeof(context));
        context.wrap_start = sb->len;
        context.message = commit->buffer;
        if (output_enc) {
 -              enc = get_header(commit, "encoding");
 -              enc = enc ? enc : utf8;
 -              if (strcmp(enc, output_enc))
 +              char *enc = get_header(commit, "encoding");
 +              if (strcmp(enc ? enc : utf8, output_enc)) {
                        context.message = logmsg_reencode(commit, output_enc);
 +                      if (!context.message)
 +                              context.message = commit->buffer;
 +              }
 +              free(enc);
        }
  
        strbuf_expand(sb, format, format_commit_item, &context);
  
        if (context.message != commit->buffer)
                free(context.message);
+       free(context.signature.gpg_output);
+       free(context.signature.signer);
  }
  
  static void pp_header(const struct pretty_print_context *pp,