Merge branch 'js/notes'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 Feb 2009 03:40:39 +0000 (19:40 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 Feb 2009 03:40:39 +0000 (19:40 -0800)
* js/notes:
git-notes: fix printing of multi-line notes
notes: fix core.notesRef documentation
Add an expensive test for git-notes
Speed up git notes lookup
Add a script to edit/inspect notes
Introduce commit notes

Conflicts:
pretty.c

1  2 
.gitignore
Documentation/config.txt
Makefile
cache.h
commit.c
pretty.c
diff --combined .gitignore
index 1c57d4c958bc5e8ff539c5f5ddb1c784d923271b,f89428b9689e2973d9e94dfd985713e2de8b4d66..13311f1d5e32db381cc555a97e6eb405e63d5aba
@@@ -82,6 -82,7 +82,7 @@@ git-mkta
  git-mktree
  git-name-rev
  git-mv
+ git-notes
  git-pack-redundant
  git-pack-objects
  git-pack-refs
@@@ -144,7 -145,6 +145,7 @@@ git-core-*/?
  gitk-wish
  gitweb/gitweb.cgi
  test-chmtime
 +test-ctype
  test-date
  test-delta
  test-dump-cache-tree
@@@ -153,7 -153,6 +154,7 @@@ test-match-tree
  test-parse-options
  test-path-utils
  test-sha1
 +test-sigchain
  common-cmds.h
  *.tar.gz
  *.dsc
diff --combined Documentation/config.txt
index e2b8775dd308d66027494d8705643c30581e2e65,2fdca0ea300d689130c5dc38eddc331e8a44d739..7fbf64d24cabbe609cf10b34055165a323d4e8b0
@@@ -422,6 -422,19 +422,19 @@@ relatively high IO latencies.  With thi
  index comparison to the filesystem data in parallel, allowing
  overlapping IO's.
  
+ core.notesRef::
+       When showing commit messages, also show notes which are stored in
+       the given ref.  This ref is expected to contain files named
+       after the full SHA-1 of the commit they annotate.
+ +
+ If such a file exists in the given ref, the referenced blob is read, and
+ appended to the commit message, separated by a "Notes:" line.  If the
+ given ref itself does not exist, it is not an error, but means that no
+ notes should be printed.
+ +
+ This setting defaults to "refs/notes/commits", and can be overridden by
+ the `GIT_NOTES_REF` environment variable.
  alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
@@@ -601,6 -614,10 +614,6 @@@ diff.autorefreshindex:
        affects only 'git-diff' Porcelain, and not lower level
        'diff' commands, such as 'git-diff-files'.
  
 -diff.suppress-blank-empty::
 -      A boolean to inhibit the standard behavior of printing a space
 -      before each empty output line. Defaults to false.
 -
  diff.external::
        If this config variable is set, diff generation is not
        performed using the internal diff machinery, but using the
@@@ -635,16 -652,6 +648,16 @@@ diff.renames:
        will enable basic rename detection.  If set to "copies" or
        "copy", it will detect copies, as well.
  
 +diff.suppressBlankEmpty::
 +      A boolean to inhibit the standard behavior of printing a space
 +      before each empty output line. Defaults to false.
 +
 +diff.wordRegex::
 +      A POSIX Extended Regular Expression used to determine what is a "word"
 +      when performing word-by-word difference calculations.  Character
 +      sequences that match the regular expression are "words", all other
 +      characters are *ignorable* whitespace.
 +
  fetch.unpackLimit::
        If the number of objects fetched over the git native
        transfer is below this
@@@ -708,9 -715,7 +721,9 @@@ gc.packrefs:
  
  gc.pruneexpire::
        When 'git-gc' is run, it will call 'prune --expire 2.weeks.ago'.
 -      Override the grace period with this config variable.
 +      Override the grace period with this config variable.  The value
 +      "now" may be used to disable this  grace period and always prune
 +      unreachable objects immediately.
  
  gc.reflogexpire::
        'git-reflog expire' removes reflog entries older than
@@@ -731,10 -736,6 +744,10 @@@ gc.rerereunresolved:
        kept for this many days when 'git-rerere gc' is run.
        The default is 15 days.  See linkgit:git-rerere[1].
  
 +gitcvs.commitmsgannotation::
 +      Append this string to each commit message. Set to empty string
 +      to disable this feature. Defaults to "via git-CVS emulator".
 +
  gitcvs.enabled::
        Whether the CVS server interface is enabled for this repository.
        See linkgit:git-cvsserver[1].
@@@ -1056,16 -1057,6 +1069,16 @@@ mergetool.keepBackup:
        is set to `false` then this file is not preserved.  Defaults to
        `true` (i.e. keep the backup files).
  
 +mergetool.keepTemporaries::
 +      When invoking a custom merge tool, git uses a set of temporary
 +      files to pass to the tool. If the tool returns an error and this
 +      variable is set to `true`, then these temporary files will be
 +      preserved, otherwise they will be removed after the tool has
 +      exited. Defaults to `false`.
 +
 +mergetool.prompt::
 +      Prompt before each invocation of the merge resolution program.
 +
  pack.window::
        The size of the window used by linkgit:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
diff --combined Makefile
index 627d4d09ba55259d23a2270a2d464ac97348d410,69e1073e3e4547faf956f0e452107f11bad70387..27b9569746179e68c635bdaab8e57395f63faf01
+++ b/Makefile
@@@ -23,9 -23,6 +23,9 @@@ all:
  # Define NO_EXPAT if you do not have expat installed.  git-http-push is
  # not built, and you cannot push using http:// and https:// transports.
  #
 +# Define EXPATDIR=/foo/bar if your expat header and library files are in
 +# /foo/bar/include and /foo/bar/lib directories.
 +#
  # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
  #
  # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
@@@ -182,32 -179,28 +182,32 @@@ STRIP ?= stri
  # Among the variables below, these:
  #   gitexecdir
  #   template_dir
 +#   mandir
 +#   infodir
  #   htmldir
  #   ETC_GITCONFIG (but not sysconfdir)
 -# can be specified as a relative path ../some/where/else (which must begin
 -# with ../); this is interpreted as relative to $(bindir) and "git" at
 +# can be specified as a relative path some/where/else;
 +# this is interpreted as relative to $(prefix) and "git" at
  # runtime figures out where they are based on the path to the executable.
  # This can help installing the suite in a relocatable way.
  
  prefix = $(HOME)
 -bindir = $(prefix)/bin
 -mandir = $(prefix)/share/man
 -infodir = $(prefix)/share/info
 -gitexecdir = $(prefix)/libexec/git-core
 +bindir_relative = bin
 +bindir = $(prefix)/$(bindir_relative)
 +mandir = share/man
 +infodir = share/info
 +gitexecdir = libexec/git-core
  sharedir = $(prefix)/share
 -template_dir = $(sharedir)/git-core/templates
 -htmldir=$(sharedir)/doc/git-doc
 +template_dir = share/git-core/templates
 +htmldir = share/doc/git-doc
  ifeq ($(prefix),/usr)
  sysconfdir = /etc
 +ETC_GITCONFIG = $(sysconfdir)/gitconfig
  else
  sysconfdir = $(prefix)/etc
 +ETC_GITCONFIG = etc/gitconfig
  endif
  lib = lib
 -ETC_GITCONFIG = $(sysconfdir)/gitconfig
  # DESTDIR=
  
  # default configuration for gitweb
@@@ -265,6 -258,7 +265,7 @@@ SCRIPT_SH += git-merge-octopus.s
  SCRIPT_SH += git-merge-one-file.sh
  SCRIPT_SH += git-merge-resolve.sh
  SCRIPT_SH += git-mergetool.sh
+ SCRIPT_SH += git-notes.sh
  SCRIPT_SH += git-parse-remote.sh
  SCRIPT_SH += git-pull.sh
  SCRIPT_SH += git-quiltimport.sh
@@@ -377,6 -371,7 +378,7 @@@ LIB_H += ll-merge.
  LIB_H += log-tree.h
  LIB_H += mailmap.h
  LIB_H += merge-recursive.h
+ LIB_H += notes.h
  LIB_H += object.h
  LIB_H += pack.h
  LIB_H += pack-refs.h
@@@ -395,7 -390,6 +397,7 @@@ LIB_H += revision.
  LIB_H += run-command.h
  LIB_H += sha1-lookup.h
  LIB_H += sideband.h
 +LIB_H += sigchain.h
  LIB_H += strbuf.h
  LIB_H += tag.h
  LIB_H += transport.h
@@@ -459,6 -453,7 +461,7 @@@ LIB_OBJS += match-trees.
  LIB_OBJS += merge-file.o
  LIB_OBJS += merge-recursive.o
  LIB_OBJS += name-hash.o
+ LIB_OBJS += notes.o
  LIB_OBJS += object.o
  LIB_OBJS += pack-check.o
  LIB_OBJS += pack-refs.o
@@@ -489,7 -484,6 +492,7 @@@ LIB_OBJS += sha1-lookup.
  LIB_OBJS += sha1_name.o
  LIB_OBJS += shallow.o
  LIB_OBJS += sideband.o
 +LIB_OBJS += sigchain.o
  LIB_OBJS += strbuf.o
  LIB_OBJS += symlinks.o
  LIB_OBJS += tag.o
@@@ -649,12 -643,10 +652,12 @@@ endi
  ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
 -      ifneq ($(shell expr "$(uname_R)" : '9\.'),2)
 +      ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
                OLD_ICONV = UnfortunatelyYes
        endif
 -      NO_STRLCPY = YesPlease
 +      ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2)
 +              NO_STRLCPY = YesPlease
 +      endif
        NO_MEMMEM = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
  endif
@@@ -796,7 -788,6 +799,7 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        SNPRINTF_RETURNS_BOGUS = YesPlease
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
 +      RUNTIME_PREFIX = YesPlease
        NO_POSIX_ONLY_PROGRAMS = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
        COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o
        EXTLIBS += -lws2_32
        X = .exe
 -      gitexecdir = ../libexec/git-core
 -      template_dir = ../share/git-core/templates/
 -      ETC_GITCONFIG = ../etc/gitconfig
  endif
  ifneq (,$(findstring arm,$(uname_M)))
        ARM_SHA1 = YesPlease
@@@ -826,7 -820,6 +829,7 @@@ ifeq ($(uname_S),Darwin
                        BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
 +      PTHREAD_LIBS =
  endif
  
  ifndef CC_LD_DYNPATH
@@@ -859,12 -852,7 +862,12 @@@ els
                endif
        endif
        ifndef NO_EXPAT
 -              EXPAT_LIBEXPAT = -lexpat
 +              ifdef EXPATDIR
 +                      BASIC_CFLAGS += -I$(EXPATDIR)/include
 +                      EXPAT_LIBEXPAT = -L$(EXPATDIR)/$(lib) $(CC_LD_DYNPATH)$(EXPATDIR)/$(lib) -lexpat
 +              else
 +                      EXPAT_LIBEXPAT = -lexpat
 +              endif
        endif
  endif
  
@@@ -1042,9 -1030,6 +1045,9 @@@ ifdef INTERNAL_QSOR
        COMPAT_CFLAGS += -DINTERNAL_QSORT
        COMPAT_OBJS += compat/qsort.o
  endif
 +ifdef RUNTIME_PREFIX
 +      COMPAT_CFLAGS += -DRUNTIME_PREFIX
 +endif
  
  ifdef NO_PTHREADS
        THREADED_DELTA_SEARCH =
@@@ -1104,7 -1089,6 +1107,7 @@@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC
  
  DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
  bindir_SQ = $(subst ','\'',$(bindir))
 +bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
  mandir_SQ = $(subst ','\'',$(mandir))
  infodir_SQ = $(subst ','\'',$(infodir))
  gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
@@@ -1270,12 -1254,7 +1273,12 @@@ git.o git.spec 
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
  
  exec_cmd.o: exec_cmd.c GIT-CFLAGS
 -      $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
 +      $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
 +              '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
 +              '-DBINDIR="$(bindir_relative_SQ)"' \
 +              '-DPREFIX="$(prefix_SQ)"' \
 +              $<
 +
  builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
  
@@@ -1311,7 -1290,7 +1314,7 @@@ $(LIB_FILE): $(LIB_OBJS
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
  
  XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
 -      xdiff/xmerge.o
 +      xdiff/xmerge.o xdiff/xpatience.o
  $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
        xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
  
@@@ -1331,9 -1310,6 +1334,9 @@@ html
  info:
        $(MAKE) -C Documentation info
  
 +pdf:
 +      $(MAKE) -C Documentation pdf
 +
  TAGS:
        $(RM) TAGS
        $(FIND) . -name '*.[hcS]' -print | xargs etags -a
@@@ -1380,17 -1356,7 +1383,17 @@@ endi
  
  ### Testing rules
  
 -TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X
 +TEST_PROGRAMS += test-chmtime$X
 +TEST_PROGRAMS += test-ctype$X
 +TEST_PROGRAMS += test-date$X
 +TEST_PROGRAMS += test-delta$X
 +TEST_PROGRAMS += test-dump-cache-tree$X
 +TEST_PROGRAMS += test-genrandom$X
 +TEST_PROGRAMS += test-match-trees$X
 +TEST_PROGRAMS += test-parse-options$X
 +TEST_PROGRAMS += test-path-utils$X
 +TEST_PROGRAMS += test-sha1$X
 +TEST_PROGRAMS += test-sigchain$X
  
  all:: $(TEST_PROGRAMS)
  
@@@ -1403,8 -1369,6 +1406,8 @@@ export NO_SVN_TEST
  test: all
        $(MAKE) -C t/ all
  
 +test-ctype$X: ctype.o
 +
  test-date$X: date.o ctype.o
  
  test-delta$X: diff-delta.o patch-delta.o
@@@ -1436,17 -1400,17 +1439,17 @@@ remove-dashes
  
  ### Installation rules
  
 -ifeq ($(firstword $(subst /, ,$(template_dir))),..)
 -template_instdir = $(bindir)/$(template_dir)
 -else
 +ifneq ($(filter /%,$(firstword $(template_dir))),)
  template_instdir = $(template_dir)
 +else
 +template_instdir = $(prefix)/$(template_dir)
  endif
  export template_instdir
  
 -ifeq ($(firstword $(subst /, ,$(gitexecdir))),..)
 -gitexec_instdir = $(bindir)/$(gitexecdir)
 -else
 +ifneq ($(filter /%,$(firstword $(gitexecdir))),)
  gitexec_instdir = $(gitexecdir)
 +else
 +gitexec_instdir = $(prefix)/$(gitexecdir)
  endif
  gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
  export gitexec_instdir
@@@ -1470,12 -1434,10 +1473,12 @@@ endi
        { $(RM) "$$execdir/git-add$X" && \
                ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \
                cp git-add$X "$$execdir/git-add$X"; } && \
 -      { $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
 -              ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \
 -              ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \
 -              cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \
 +      { for p in $(filter-out git-add$X,$(BUILT_INS)); do \
 +              $(RM) "$$execdir/$$p" && \
 +              ln "$$execdir/git-add$X" "$$execdir/$$p" 2>/dev/null || \
 +              ln -s "git-add$X" "$$execdir/$$p" 2>/dev/null || \
 +              cp "$$execdir/git-add$X" "$$execdir/$$p" || exit; \
 +        done } && \
        ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
  
  install-doc:
@@@ -1490,9 -1452,6 +1493,9 @@@ install-html
  install-info:
        $(MAKE) -C Documentation install-info
  
 +install-pdf:
 +      $(MAKE) -C Documentation install-pdf
 +
  quick-install-doc:
        $(MAKE) -C Documentation quick-install
  
diff --combined cache.h
index 45e713e9283dcf7b241291ac121e4d4a771f5796,6158d5546bdf4f8dbd16fa5e3107a3a214d612c9..2d889deb264f189e16c2e9d2fc9d6fb795180773
+++ b/cache.h
  #define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
  #endif
  
 +void git_inflate_init(z_streamp strm);
 +void git_inflate_end(z_streamp strm);
 +int git_inflate(z_streamp strm, int flush);
 +
  #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
  #define DTYPE(de)     ((de)->d_type)
  #else
@@@ -371,6 -367,8 +371,8 @@@ static inline enum object_type object_t
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
+ #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
+ #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
  
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
@@@ -542,6 -540,7 +544,7 @@@ enum rebase_setup_type 
  
  extern enum branch_track git_branch_track;
  extern enum rebase_setup_type autorebase;
+ extern char *notes_ref_name;
  
  #define GIT_REPO_VERSION 0
  extern int repository_format_version;
@@@ -635,6 -634,9 +638,6 @@@ extern int write_sha1_file(void *buf, u
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
  
 -/* just like read_sha1_file(), but non fatal in presence of bad objects */
 -extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size);
 -
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
@@@ -667,7 -669,6 +670,7 @@@ extern int read_ref(const char *filenam
  extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
  extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
  extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 +extern int interpret_nth_last_branch(const char *str, struct strbuf *);
  
  extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
  extern const char *ref_rev_parse_rules[];
@@@ -722,10 -723,6 +725,10 @@@ struct checkout 
  
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  extern int has_symlink_leading_path(int len, const char *name);
 +extern int has_symlink_or_noent_leading_path(int len, const char *name);
 +extern int has_dirs_only_path(int len, const char *name, int prefix_len);
 +extern void invalidate_lstat_cache(int len, const char *name);
 +extern void clear_lstat_cache(void);
  
  extern struct alternate_object_database {
        struct alternate_object_database *next;
@@@ -942,6 -939,7 +945,6 @@@ extern int ws_fix_copy(char *, const ch
  extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
  
  /* ls-files */
 -int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
  int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
diff --combined commit.c
index aa3b35b6a86891ac9d0628e20a6a46d506bf7700,10e532afe1b383a2f49702f9ff0e932ccab43a43..cf72143f58346c93879a98709d5d6fa8a0981b40
+++ b/commit.c
@@@ -5,6 -5,7 +5,7 @@@
  #include "utf8.h"
  #include "diff.h"
  #include "revision.h"
+ #include "notes.h"
  
  int save_commit_buffer = 1;
  
@@@ -705,21 -706,6 +706,21 @@@ struct commit_list *get_merge_bases(str
        return get_merge_bases_many(one, 1, &two, cleanup);
  }
  
 +int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
 +{
 +      if (!with_commit)
 +              return 1;
 +      while (with_commit) {
 +              struct commit *other;
 +
 +              other = with_commit->item;
 +              with_commit = with_commit->next;
 +              if (in_merge_bases(other, &commit, 1))
 +                      return 1;
 +      }
 +      return 0;
 +}
 +
  int in_merge_bases(struct commit *commit, struct commit **reference, int num)
  {
        struct commit_list *bases, *b;
diff --combined pretty.c
index cc460b56970c3869e67feb816f76f4804e37e7ce,2d2872f3b5707c63c056c7591948db261a13b011..8d4dbc9fbbcffb73e8669ac08a1292ad2a0f0919
+++ b/pretty.c
@@@ -6,7 -6,7 +6,8 @@@
  #include "string-list.h"
  #include "mailmap.h"
  #include "log-tree.h"
+ #include "notes.h"
 +#include "color.h"
  
  static char *user_format;
  
@@@ -182,20 -182,6 +183,20 @@@ static int is_empty_line(const char *li
        return !len;
  }
  
 +static const char *skip_empty_lines(const char *msg)
 +{
 +      for (;;) {
 +              int linelen = get_one_line(msg);
 +              int ll = linelen;
 +              if (!linelen)
 +                      break;
 +              if (!is_empty_line(msg, &ll))
 +                      break;
 +              msg += linelen;
 +      }
 +      return msg;
 +}
 +
  static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
                        const struct commit *commit, int abbrev)
  {
@@@ -425,15 -411,13 +426,15 @@@ struct chunk 
  struct format_commit_context {
        const struct commit *commit;
        enum date_mode dmode;
 +      unsigned commit_header_parsed:1;
 +      unsigned commit_message_parsed:1;
  
        /* These offsets are relative to the start of the commit message. */
 -      int commit_header_parsed;
 -      struct chunk subject;
        struct chunk author;
        struct chunk committer;
        struct chunk encoding;
 +      size_t message_off;
 +      size_t subject_off;
        size_t body_off;
  
        /* The following ones are relative to the result struct strbuf. */
@@@ -463,14 -447,23 +464,14 @@@ static void parse_commit_header(struct 
  {
        const char *msg = context->commit->buffer;
        int i;
 -      enum { HEADER, SUBJECT, BODY } state;
  
 -      for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
 +      for (i = 0; msg[i]; i++) {
                int eol;
                for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
                        ; /* do nothing */
  
 -              if (state == SUBJECT) {
 -                      context->subject.off = i;
 -                      context->subject.len = eol - i;
 -                      i = eol;
 -              }
                if (i == eol) {
 -                      state++;
 -                      /* strip empty lines */
 -                      while (msg[eol] == '\n' && msg[eol + 1] == '\n')
 -                              eol++;
 +                      break;
                } else if (!prefixcmp(msg + i, "author ")) {
                        context->author.off = i + 7;
                        context->author.len = eol - i - 7;
                        context->encoding.len = eol - i - 9;
                }
                i = eol;
 -              if (!msg[i])
 -                      break;
        }
 -      context->body_off = i;
 +      context->message_off = i;
        context->commit_header_parsed = 1;
  }
  
 +const char *format_subject(struct strbuf *sb, const char *msg,
 +                         const char *line_separator)
 +{
 +      int first = 1;
 +
 +      for (;;) {
 +              const char *line = msg;
 +              int linelen = get_one_line(line);
 +
 +              msg += linelen;
 +              if (!linelen || is_empty_line(line, &linelen))
 +                      break;
 +
 +              if (!sb)
 +                      continue;
 +              strbuf_grow(sb, linelen + 2);
 +              if (!first)
 +                      strbuf_addstr(sb, line_separator);
 +              strbuf_add(sb, line, linelen);
 +              first = 0;
 +      }
 +      return msg;
 +}
 +
 +static void parse_commit_message(struct format_commit_context *c)
 +{
 +      const char *msg = c->commit->buffer + c->message_off;
 +      const char *start = c->commit->buffer;
 +
 +      msg = skip_empty_lines(msg);
 +      c->subject_off = msg - start;
 +
 +      msg = format_subject(NULL, msg, NULL);
 +      msg = skip_empty_lines(msg);
 +      c->body_off = msg - start;
 +
 +      c->commit_message_parsed = 1;
 +}
 +
  static void format_decoration(struct strbuf *sb, const struct commit *commit)
  {
        struct name_decoration *d;
@@@ -555,17 -511,6 +556,17 @@@ static size_t format_commit_item(struc
        /* these are independent of the commit */
        switch (placeholder[0]) {
        case 'C':
 +              if (placeholder[1] == '(') {
 +                      const char *end = strchr(placeholder + 2, ')');
 +                      char color[COLOR_MAXLEN];
 +                      if (!end)
 +                              return 0;
 +                      color_parse_mem(placeholder + 2,
 +                                      end - (placeholder + 2),
 +                                      "--pretty format", color);
 +                      strbuf_addstr(sb, color);
 +                      return end - placeholder + 1;
 +              }
                if (!prefixcmp(placeholder + 1, "red")) {
                        strbuf_addstr(sb, "\033[31m");
                        return 4;
                parse_commit_header(c);
  
        switch (placeholder[0]) {
 -      case 's':       /* subject */
 -              strbuf_add(sb, msg + c->subject.off, c->subject.len);
 -              return 1;
        case 'a':       /* author ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->author.off, c->author.len,
        case 'e':       /* encoding */
                strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
                return 1;
 +      }
 +
 +      /* Now we need to parse the commit message. */
 +      if (!c->commit_message_parsed)
 +              parse_commit_message(c);
 +
 +      switch (placeholder[0]) {
 +      case 's':       /* subject */
 +              format_subject(sb, msg + c->subject_off, " ");
 +              return 1;
        case 'b':       /* body */
                strbuf_addstr(sb, msg + c->body_off);
                return 1;
@@@ -767,11 -705,27 +768,11 @@@ void pp_title_line(enum cmit_fmt fmt
                   const char *encoding,
                   int need_8bit_cte)
  {
 +      const char *line_separator = (fmt == CMIT_FMT_EMAIL) ? "\n " : " ";
        struct strbuf title;
  
        strbuf_init(&title, 80);
 -
 -      for (;;) {
 -              const char *line = *msg_p;
 -              int linelen = get_one_line(line);
 -
 -              *msg_p += linelen;
 -              if (!linelen || is_empty_line(line, &linelen))
 -                      break;
 -
 -              strbuf_grow(&title, linelen + 2);
 -              if (title.len) {
 -                      if (fmt == CMIT_FMT_EMAIL) {
 -                              strbuf_addch(&title, '\n');
 -                      }
 -                      strbuf_addch(&title, ' ');
 -              }
 -              strbuf_add(&title, line, linelen);
 -      }
 +      *msg_p = format_subject(&title, *msg_p, line_separator);
  
        strbuf_grow(sb, title.len + 1024);
        if (subject) {
@@@ -897,7 -851,15 +898,7 @@@ void pretty_print_commit(enum cmit_fmt 
        }
  
        /* Skip excess blank lines at the beginning of body, if any... */
 -      for (;;) {
 -              int linelen = get_one_line(msg);
 -              int ll = linelen;
 -              if (!linelen)
 -                      break;
 -              if (!is_empty_line(msg, &ll))
 -                      break;
 -              msg += linelen;
 -      }
 +      msg = skip_empty_lines(msg);
  
        /* These formats treat the title line specially. */
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
         */
        if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
+       if (fmt != CMIT_FMT_ONELINE)
+               get_commit_notes(commit, sb, encoding);
        free(reencoded);
  }