Code

Merge branch 'jc/request-pull-show-head-4'
authorJunio C Hamano <gitster@pobox.com>
Fri, 9 Dec 2011 21:37:05 +0000 (13:37 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 9 Dec 2011 21:37:05 +0000 (13:37 -0800)
* jc/request-pull-show-head-4:
  request-pull: use the annotated tag contents
  fmt-merge-msg.c: Fix an "dubious one-bit signed bitfield" sparse error
  environment.c: Fix an sparse "symbol not declared" warning
  builtin/log.c: Fix an "Using plain integer as NULL pointer" warning
  fmt-merge-msg: use branch.$name.description
  request-pull: use the branch description
  request-pull: state what commit to expect
  request-pull: modernize style
  branch: teach --edit-description option
  format-patch: use branch description in cover letter
  branch: add read_branch_desc() helper function

Conflicts:
builtin/branch.c

1  2 
Documentation/git-branch.txt
Makefile
branch.c
builtin/branch.c
builtin/fmt-merge-msg.c
builtin/merge.c
environment.c

index f46013c91fcbbe4eecffe09d9b43c132daea093f,8871a4ec2143bc8683004cf32847f2d93fb39040..0427e80a35601e689a47299dce9c5517959bddf8
@@@ -9,22 -9,19 +9,23 @@@ SYNOPSI
  --------
  [verse]
  'git branch' [--color[=<when>] | --no-color] [-r | -a]
 -      [-v [--abbrev=<length> | --no-abbrev]]
 -      [(--merged | --no-merged | --contains) [<commit>]]
 +      [--list] [-v [--abbrev=<length> | --no-abbrev]]
 +      [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
  'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
  'git branch' (-m | -M) [<oldbranch>] <newbranch>
  'git branch' (-d | -D) [-r] <branchname>...
+ 'git branch' --edit-description [<branchname>]
  
  DESCRIPTION
  -----------
  
  With no arguments, existing branches are listed and the current branch will
  be highlighted with an asterisk.  Option `-r` causes the remote-tracking
 -branches to be listed, and option `-a` shows both.
 +branches to be listed, and option `-a` shows both. This list mode is also
 +activated by the `--list` option (see below).
 +<pattern> restricts the output to matching branches, the pattern is a shell
 +wildcard (i.e., matched using fnmatch(3))
 +Multiple patterns may be given; if any of them matches, the tag is shown.
  
  With `--contains`, shows only the branches that contain the named commit
  (in other words, the branches whose tip commits are descendants of the
@@@ -68,7 -65,6 +69,7 @@@ way to clean up all obsolete remote-tra
  OPTIONS
  -------
  -d::
 +--delete::
        Delete a branch. The branch must be fully merged in its
        upstream branch, or in `HEAD` if no upstream was set with
        `--track` or `--set-upstream`.
@@@ -77,7 -73,6 +78,7 @@@
        Delete a branch irrespective of its merged status.
  
  -l::
 +--create-reflog::
        Create the branch's reflog.  This activates recording of
        all changes made to the branch ref, enabling use of date
        based sha1 expressions such as "<branchname>@\{yesterday}".
@@@ -90,7 -85,6 +91,7 @@@
        already. Without `-f` 'git branch' refuses to change an existing branch.
  
  -m::
 +--move::
        Move/rename a branch and the corresponding reflog.
  
  -M::
        Same as `--color=never`.
  
  -r::
 +--remotes::
        List or delete (if used with -d) the remote-tracking branches.
  
  -a::
 +--all::
        List both remote-tracking branches and local branches.
  
 +--list::
 +      Activate the list mode. `git branch <pattern>` would try to create a branch,
 +      use `git branch --list <pattern>` to list matching branches.
 +
  -v::
  --verbose::
 -      Show sha1 and commit subject line for each head, along with
 +      When in list mode,
 +      show sha1 and commit subject line for each head, along with
        relationship to upstream branch (if any). If given twice, print
        the name of the upstream branch, as well.
  
@@@ -158,6 -145,10 +159,10 @@@ start-point is either a local or remote
        like '--track' would when creating the branch, except that where
        branch points to is not changed.
  
+ --edit-description::
+       Open an editor and edit the text to explain what the branch is
+       for, to be used by various other commands (e.g. `request-pull`).
  --contains <commit>::
        Only list branches which contain the specified commit.
  
diff --combined Makefile
index b21d2f14176d08011cea79790eb2246d50787ec9,b499049ae7657d5a7f0d13d4628b1c571bf5a485..d78bd7693524a4d1c64df0f7b1a02c6f9fbc512a
+++ b/Makefile
@@@ -57,8 -57,8 +57,8 @@@ all:
  #
  # Define NO_STRLCPY if you don't have strlcpy.
  #
 -# Define NO_STRTOUMAX if you don't have strtoumax in the C library.
 -# If your compiler also does not support long long or does not have
 +# Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the
 +# C library. If your compiler also does not support long long or does not have
  # strtoull, define NO_STRTOULL.
  #
  # Define NO_SETENV if you don't have setenv in the C library.
  #   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
  #   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
  #
 -# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option
 -# and you want to avoid rebuilding objects when an unrelated header file
 -# changes.
 +# Define COMPUTE_HEADER_DEPENDENCIES to "yes" if you want dependencies on
 +# header files to be automatically computed, to avoid rebuilding objects when
 +# an unrelated header file changes.  Define it to "no" to use the hard-coded
 +# dependency rules.  The default is "auto", which means to use computed header
 +# dependencies if your compiler is detected to support it.
  #
  # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
  # dependency rules.
@@@ -507,7 -505,6 +507,7 @@@ VCSSVN_LIB=vcs-svn/lib.
  
  LIB_H += advice.h
  LIB_H += archive.h
 +LIB_H += argv-array.h
  LIB_H += attr.h
  LIB_H += blob.h
  LIB_H += builtin.h
@@@ -521,10 -518,8 +521,10 @@@ LIB_H += compat/mingw.
  LIB_H += compat/obstack.h
  LIB_H += compat/win32/pthread.h
  LIB_H += compat/win32/syslog.h
 -LIB_H += compat/win32/sys/poll.h
 +LIB_H += compat/win32/poll.h
  LIB_H += compat/win32/dirent.h
 +LIB_H += connected.h
 +LIB_H += convert.h
  LIB_H += csum-file.h
  LIB_H += decorate.h
  LIB_H += delta.h
@@@ -532,6 -527,7 +532,7 @@@ LIB_H += diffcore.
  LIB_H += diff.h
  LIB_H += dir.h
  LIB_H += exec_cmd.h
+ LIB_H += fmt-merge-msg.h
  LIB_H += fsck.h
  LIB_H += gettext.h
  LIB_H += git-compat-util.h
@@@ -566,7 -562,6 +567,7 @@@ LIB_H += rerere.
  LIB_H += resolve-undo.h
  LIB_H += revision.h
  LIB_H += run-command.h
 +LIB_H += sequencer.h
  LIB_H += sha1-array.h
  LIB_H += sha1-lookup.h
  LIB_H += sideband.h
@@@ -592,7 -587,6 +593,7 @@@ LIB_OBJS += alloc.
  LIB_OBJS += archive.o
  LIB_OBJS += archive-tar.o
  LIB_OBJS += archive-zip.o
 +LIB_OBJS += argv-array.o
  LIB_OBJS += attr.o
  LIB_OBJS += base85.o
  LIB_OBJS += bisect.o
@@@ -606,7 -600,6 +607,7 @@@ LIB_OBJS += commit.
  LIB_OBJS += compat/obstack.o
  LIB_OBJS += config.o
  LIB_OBJS += connect.o
 +LIB_OBJS += connected.o
  LIB_OBJS += convert.o
  LIB_OBJS += copy.o
  LIB_OBJS += csum-file.o
@@@ -676,7 -669,6 +677,7 @@@ LIB_OBJS += revision.
  LIB_OBJS += run-command.o
  LIB_OBJS += server-info.o
  LIB_OBJS += setup.o
 +LIB_OBJS += sequencer.o
  LIB_OBJS += sha1-array.o
  LIB_OBJS += sha1-lookup.o
  LIB_OBJS += sha1_file.o
@@@ -829,7 -821,6 +830,7 @@@ ifeq ($(uname_S),GNU/kFreeBSD
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
 +      DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
  endif
  ifeq ($(uname_S),UnixWare)
        CC = cc
@@@ -1095,7 -1086,6 +1096,7 @@@ ifeq ($(uname_S),Windows
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
 +      NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_IPV6 = YesPlease
        NO_SETENV = YesPlease
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
        COMPAT_OBJS = compat/msvc.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
 -              compat/win32/sys/poll.o compat/win32/dirent.o
 +              compat/win32/poll.o compat/win32/dirent.o
        COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@@ -1189,7 -1179,6 +1190,7 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
 +      NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
 -              compat/win32/sys/poll.o compat/win32/dirent.o
 +              compat/win32/poll.o compat/win32/dirent.o
        EXTLIBS += -lws2_32
        PTHREAD_LIBS =
        X = .exe
@@@ -1252,32 -1241,12 +1253,32 @@@ endi
  endif
  
  ifdef CHECK_HEADER_DEPENDENCIES
 -COMPUTE_HEADER_DEPENDENCIES =
 +COMPUTE_HEADER_DEPENDENCIES = no
  USE_COMPUTED_HEADER_DEPENDENCIES =
  endif
  
 -ifdef COMPUTE_HEADER_DEPENDENCIES
 +ifndef COMPUTE_HEADER_DEPENDENCIES
 +COMPUTE_HEADER_DEPENDENCIES = auto
 +endif
 +
 +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
 +dep_check = $(shell $(CC) $(ALL_CFLAGS) \
 +      -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \
 +      echo $$?)
 +ifeq ($(dep_check),0)
 +override COMPUTE_HEADER_DEPENDENCIES = yes
 +else
 +override COMPUTE_HEADER_DEPENDENCIES = no
 +endif
 +endif
 +
 +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
  USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
 +else
 +ifneq ($(COMPUTE_HEADER_DEPENDENCIES),no)
 +$(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
 +(not "$(COMPUTE_HEADER_DEPENDENCIES)"))
 +endif
  endif
  
  ifdef SANE_TOOL_PATH
@@@ -1478,7 -1447,7 +1479,7 @@@ ifdef NO_STRLCP
  endif
  ifdef NO_STRTOUMAX
        COMPAT_CFLAGS += -DNO_STRTOUMAX
 -      COMPAT_OBJS += compat/strtoumax.o
 +      COMPAT_OBJS += compat/strtoumax.o compat/strtoimax.o
  endif
  ifdef NO_STRTOULL
        COMPAT_CFLAGS += -DNO_STRTOULL
@@@ -1924,9 -1893,9 +1925,9 @@@ OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(
  dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
  dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
  
 -ifdef COMPUTE_HEADER_DEPENDENCIES
 +ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
  $(dep_dirs):
 -      mkdir -p $@
 +      @mkdir -p $@
  
  missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
  dep_file = $(dir $@).depend/$(notdir $@).d
@@@ -1937,7 -1906,7 +1938,7 @@@ Please unset CHECK_HEADER_DEPENDENCIES 
  endif
  endif
  
 -ifndef COMPUTE_HEADER_DEPENDENCIES
 +ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
  ifndef CHECK_HEADER_DEPENDENCIES
  dep_dirs =
  missing_dep_dirs =
@@@ -2027,13 -1996,13 +2028,13 @@@ builtin/branch.o builtin/checkout.o bui
  builtin/bundle.o bundle.o transport.o: bundle.h
  builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
  builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
 -builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
 +builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
  builtin/send-pack.o transport.o: send-pack.h
  builtin/log.o builtin/shortlog.o: shortlog.h
  builtin/prune.o builtin/reflog.o reachable.o: reachable.h
  builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
  builtin/tar-tree.o archive-tar.o: tar.h
 -connect.o transport.o http-backend.o: url.h
 +connect.o transport.o url.o http-backend.o: url.h
  http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
  http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
  
@@@ -2147,21 -2116,17 +2148,21 @@@ po/git.pot: $(LOCALIZED_C
  
  pot: po/git.pot
  
 +FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
 +                      $(FIND) . \( -name .git -type d -prune \) \
 +                              -o \( -name '*.[hcS]' -type f -print \) )
 +
  $(ETAGS_TARGET): FORCE
        $(RM) $(ETAGS_TARGET)
 -      $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET)
 +      $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET)
  
  tags: FORCE
        $(RM) tags
 -      $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
 +      $(FIND_SOURCE_FILES) | xargs ctags -a
  
  cscope:
        $(RM) cscope*
 -      $(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 +      $(FIND_SOURCE_FILES) | xargs cscope -b
  
  ### Detect prefix changes
  TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
@@@ -2316,7 -2281,8 +2317,7 @@@ install: al
        $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
 -      (cd mergetools && $(TAR) cf - .) | \
 -      (cd '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' && umask 022 && $(TAR) xof -)
 +      $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
  ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C gitweb install
diff --combined branch.c
index 025a97be0281b9fd3ccff2b36c03495b005ac4fe,50088a4190075cadba459effac2797906bd45545..d91a099fdd22b9131a1d2ddaf3778b645c53eca0
+++ b/branch.c
@@@ -3,7 -3,6 +3,7 @@@
  #include "refs.h"
  #include "remote.h"
  #include "commit.h"
 +#include "sequencer.h"
  
  struct tracking {
        struct refspec spec;
@@@ -136,6 -135,37 +136,37 @@@ static int setup_tracking(const char *n
        return 0;
  }
  
+ struct branch_desc_cb {
+       const char *config_name;
+       const char *value;
+ };
+ static int read_branch_desc_cb(const char *var, const char *value, void *cb)
+ {
+       struct branch_desc_cb *desc = cb;
+       if (strcmp(desc->config_name, var))
+               return 0;
+       free((char *)desc->value);
+       return git_config_string(&desc->value, var, value);
+ }
+ int read_branch_desc(struct strbuf *buf, const char *branch_name)
+ {
+       struct branch_desc_cb cb;
+       struct strbuf name = STRBUF_INIT;
+       strbuf_addf(&name, "branch.%s.description", branch_name);
+       cb.config_name = name.buf;
+       cb.value = NULL;
+       if (git_config(read_branch_desc_cb, &cb) < 0) {
+               strbuf_release(&name);
+               return -1;
+       }
+       if (cb.value)
+               strbuf_addstr(buf, cb.value);
+       strbuf_release(&name);
+       return 0;
+ }
  int validate_new_branchname(const char *name, struct strbuf *ref,
                            int force, int attr_only)
  {
@@@ -241,11 -271,9 +272,11 @@@ void create_branch(const char *head
  void remove_branch_state(void)
  {
        unlink(git_path("CHERRY_PICK_HEAD"));
 +      unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_RR"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
        unlink(git_path("SQUASH_MSG"));
 +      remove_sequencer_state(0);
  }
diff --combined builtin/branch.c
index 55cad766c7e3d284b1361b1beca8c5d51de96083,fffa319e0e2e0ca86815e14641213a581634caf6..ad02f0fd7e8819f436baf27d7836cc181d613bab
@@@ -260,22 -260,9 +260,22 @@@ static char *resolve_symref(const char 
  
  struct append_ref_cb {
        struct ref_list *ref_list;
 +      const char **pattern;
        int ret;
  };
  
 +static int match_patterns(const char **pattern, const char *refname)
 +{
 +      if (!*pattern)
 +              return 1; /* no pattern always matches */
 +      while (*pattern) {
 +              if (!fnmatch(*pattern, refname, 0))
 +                      return 1;
 +              pattern++;
 +      }
 +      return 0;
 +}
 +
  static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
  {
        struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
        if ((kind & ref_list->kinds) == 0)
                return 0;
  
 +      if (!match_patterns(cb->pattern, refname))
 +              return 0;
 +
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
                commit = lookup_commit_reference_gently(sha1, 1);
@@@ -508,7 -492,7 +508,7 @@@ static void show_detached(struct ref_li
        }
  }
  
 -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
 +static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
  {
        int i;
        struct append_ref_cb cb;
        if (merge_filter != NO_FILTER)
                init_revisions(&ref_list.revs, NULL);
        cb.ref_list = &ref_list;
 +      cb.pattern = pattern;
        cb.ret = 0;
        for_each_rawref(append_ref, &cb);
        if (merge_filter != NO_FILTER) {
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
  
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
 -      if (detached)
 +      if (detached && match_patterns(pattern, "HEAD"))
                show_detached(&ref_list);
  
        for (i = 0; i < ref_list.index; i++) {
@@@ -623,11 -606,49 +623,49 @@@ static int opt_parse_merge_filter(cons
        return 0;
  }
  
+ static const char edit_description[] = "BRANCH_DESCRIPTION";
+ static int edit_branch_description(const char *branch_name)
+ {
+       FILE *fp;
+       int status;
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf name = STRBUF_INIT;
+       read_branch_desc(&buf, branch_name);
+       if (!buf.len || buf.buf[buf.len-1] != '\n')
+               strbuf_addch(&buf, '\n');
+       strbuf_addf(&buf,
+                   "# Please edit the description for the branch\n"
+                   "#   %s\n"
+                   "# Lines starting with '#' will be stripped.\n",
+                   branch_name);
+       fp = fopen(git_path(edit_description), "w");
+       if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+               strbuf_release(&buf);
+               return error(_("could not write branch description template: %s\n"),
+                            strerror(errno));
+       }
+       strbuf_reset(&buf);
+       if (launch_editor(git_path(edit_description), &buf, NULL)) {
+               strbuf_release(&buf);
+               return -1;
+       }
+       stripspace(&buf, 1);
+       strbuf_addf(&name, "branch.%s.description", branch_name);
+       status = git_config_set(name.buf, buf.buf);
+       strbuf_release(&name);
+       strbuf_release(&buf);
+       return status;
+ }
  int cmd_branch(int argc, const char **argv, const char *prefix)
  {
 -      int delete = 0, rename = 0, force_create = 0;
 +      int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
-       int reflog = 0;
+       int reflog = 0, edit_description = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
                OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
                        BRANCH_TRACK_OVERRIDE),
                OPT__COLOR(&branch_use_color, "use colored output"),
 -              OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
 +              OPT_SET_INT('r', "remotes",     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
                {
                        OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
                OPT__ABBREV(&abbrev),
  
                OPT_GROUP("Specific git-branch actions:"),
 -              OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
 +              OPT_SET_INT('a', "all", &kinds, "list both remote-tracking and local branches",
                        REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
 -              OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
 +              OPT_BIT('d', "delete", &delete, "delete fully merged branch", 1),
                OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
 -              OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
 +              OPT_BIT('m', "move", &rename, "move/rename a branch and its reflog", 1),
                OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
 -              OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
 +              OPT_BOOLEAN(0, "list", &list, "list branch names"),
 +              OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"),
+               OPT_BOOLEAN(0, "edit-description", &edit_description,
+                           "edit the description for the branch"),
                OPT__FORCE(&force_create, "force creation (when already exists)"),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
  
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
 -      if (!!delete + !!rename + !!force_create > 1)
 +
-       if (!delete && !rename && argc == 0)
++      if (!delete && !rename && !edit_description && argc == 0)
 +              list = 1;
 +
 +      if (!!delete + !!rename + !!force_create + !!list > 1)
                usage_with_options(builtin_branch_usage, options);
  
        if (abbrev == -1)
  
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
-       else if (rename) {
 +      else if (list)
 +              return print_ref_list(kinds, detached, verbose, abbrev,
 +                                    with_commit, argv);
 -      } else if (argc == 0)
 -              return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
 -      else if (rename && (argc == 1))
 -              rename_branch(head, argv[0], rename > 1);
 -      else if (rename && (argc == 2))
 -              rename_branch(argv[0], argv[1], rename > 1);
 -      else if (argc <= 2) {
+       else if (edit_description) {
+               const char *branch_name;
+               if (detached)
+                       die("Cannot give description to detached HEAD");
+               if (!argc)
+                       branch_name = head;
+               else if (argc == 1)
+                       branch_name = argv[0];
+               else
+                       usage_with_options(builtin_branch_usage, options);
+               if (edit_branch_description(branch_name))
+                       return 1;
++      } else if (rename) {
 +              if (argc == 1)
 +                      rename_branch(head, argv[0], rename > 1);
 +              else if (argc == 2)
 +                      rename_branch(argv[0], argv[1], rename > 1);
 +              else
 +                      usage_with_options(builtin_branch_usage, options);
 +      } else if (argc > 0 && argc <= 2) {
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
diff --combined builtin/fmt-merge-msg.c
index 7e2f22589dcb14d5ba95ce1331ef816a458533d0,f6d92e263ce6c8b37c1d554e15d6693a049be071..26e74126397e576577b793c11da7dbcdaba7f727
@@@ -5,23 -5,27 +5,27 @@@
  #include "revision.h"
  #include "tag.h"
  #include "string-list.h"
+ #include "branch.h"
+ #include "fmt-merge-msg.h"
  
  static const char * const fmt_merge_msg_usage[] = {
        "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
        NULL
  };
  
- static int shortlog_len;
+ static int use_branch_desc;
  
static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
  {
        if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
                int is_bool;
-               shortlog_len = git_config_bool_or_int(key, value, &is_bool);
-               if (!is_bool && shortlog_len < 0)
+               merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+               if (!is_bool && merge_log_config < 0)
                        return error("%s: negative length %s", key, value);
-               if (is_bool && shortlog_len)
-                       shortlog_len = DEFAULT_MERGE_LOG_LEN;
+               if (is_bool && merge_log_config)
+                       merge_log_config = DEFAULT_MERGE_LOG_LEN;
+       } else if (!strcmp(key, "merge.branchdesc")) {
+               use_branch_desc = git_config_bool(key, value);
        }
        return 0;
  }
@@@ -31,6 -35,11 +35,11 @@@ struct src_data 
        int head_status;
  };
  
+ struct origin_data {
+       unsigned char sha1[20];
+       unsigned is_local_branch:1;
+ };
  static void init_src_data(struct src_data *data)
  {
        data->branch.strdup_strings = 1;
@@@ -45,7 -54,7 +54,7 @@@ static struct string_list origins = STR
  static int handle_line(char *line)
  {
        int i, len = strlen(line);
-       unsigned char *sha1;
+       struct origin_data *origin_data;
        char *src, *origin;
        struct src_data *src_data;
        struct string_list_item *item;
                return 2;
  
        line[40] = 0;
-       sha1 = xmalloc(20);
-       i = get_sha1(line, sha1);
+       origin_data = xcalloc(1, sizeof(struct origin_data));
+       i = get_sha1(line, origin_data->sha1);
        line[40] = '\t';
-       if (i)
+       if (i) {
+               free(origin_data);
                return 3;
+       }
  
        if (line[len - 1] == '\n')
                line[len - 1] = 0;
                origin = src;
                src_data->head_status |= 1;
        } else if (!prefixcmp(line, "branch ")) {
+               origin_data->is_local_branch = 1;
                origin = line + 7;
                string_list_append(&src_data->branch, origin);
                src_data->head_status |= 2;
                sprintf(new_origin, "%s of %s", origin, src);
                origin = new_origin;
        }
-       string_list_append(&origins, origin)->util = sha1;
+       if (strcmp(".", src))
+               origin_data->is_local_branch = 0;
+       string_list_append(&origins, origin)->util = origin_data;
        return 0;
  }
  
@@@ -140,9 -154,30 +154,30 @@@ static void print_joined(const char *si
        }
  }
  
- static void shortlog(const char *name, unsigned char *sha1,
-               struct commit *head, struct rev_info *rev, int limit,
-               struct strbuf *out)
+ static void add_branch_desc(struct strbuf *out, const char *name)
+ {
+       struct strbuf desc = STRBUF_INIT;
+       if (!read_branch_desc(&desc, name)) {
+               const char *bp = desc.buf;
+               while (*bp) {
+                       const char *ep = strchrnul(bp, '\n');
+                       if (*ep)
+                               ep++;
+                       strbuf_addf(out, "  : %.*s", (int)(ep - bp), bp);
+                       bp = ep;
+               }
+               if (out->buf[out->len - 1] != '\n')
+                       strbuf_addch(out, '\n');
+       }
+       strbuf_release(&desc);
+ }
+ static void shortlog(const char *name,
+                    struct origin_data *origin_data,
+                    struct commit *head,
+                    struct rev_info *rev, int limit,
+                    struct strbuf *out)
  {
        int i, count = 0;
        struct commit *commit;
        struct string_list subjects = STRING_LIST_INIT_DUP;
        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
        struct strbuf sb = STRBUF_INIT;
+       const unsigned char *sha1 = origin_data->sha1;
  
        branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
        if (!branch || branch->type != OBJ_COMMIT)
        else
                strbuf_addf(out, "\n* %s:\n", name);
  
+       if (origin_data->is_local_branch && use_branch_desc)
+               add_branch_desc(out, name);
        for (i = 0; i < subjects.nr; i++)
                if (i >= limit)
                        strbuf_addf(out, "  ...\n");
@@@ -293,7 -332,7 +332,7 @@@ static int do_fmt_merge_msg(int merge_t
                struct commit *head;
                struct rev_info rev;
  
 -              head = lookup_commit(head_sha1);
 +              head = lookup_commit_or_die(head_sha1, "HEAD");
                init_revisions(&rev, NULL);
                rev.commit_format = CMIT_FMT_ONELINE;
                rev.ignore_merges = 1;
                        strbuf_addch(out, '\n');
  
                for (i = 0; i < origins.nr; i++)
-                       shortlog(origins.items[i].string, origins.items[i].util,
-                                       head, &rev, shortlog_len, out);
+                       shortlog(origins.items[i].string,
+                                origins.items[i].util,
+                                head, &rev, shortlog_len, out);
        }
        return 0;
  }
@@@ -318,6 -358,7 +358,7 @@@ int cmd_fmt_merge_msg(int argc, const c
  {
        const char *inpath = NULL;
        const char *message = NULL;
+       int shortlog_len = -1;
        struct option options[] = {
                { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
                  "populate log with at most <n> entries from shortlog",
                             0);
        if (argc > 0)
                usage_with_options(fmt_merge_msg_usage, options);
+       if (shortlog_len < 0)
+               shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
        if (message && !shortlog_len) {
                char nl = '\n';
                write_in_full(STDOUT_FILENO, message, strlen(message));
diff --combined builtin/merge.c
index 8a7bebd96acfb8f627352216ca2f8e344e8d4a53,b8f25dd44e2d85fc607dd18c540db443036f08f9..3fc54923b500712097a4918f14a1e58b261e34c4
@@@ -26,6 -26,7 +26,7 @@@
  #include "merge-recursive.h"
  #include "resolve-undo.h"
  #include "remote.h"
+ #include "fmt-merge-msg.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -44,12 -45,13 +45,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;
 +static int fast_forward_only, option_edit;
  static int allow_trivial = 1, have_message;
  static struct strbuf merge_msg;
  static struct commit_list *remoteheads;
 -static unsigned char head[20], stash[20];
  static struct strategy **use_strategies;
  static size_t use_strategies_nr, use_strategies_alloc;
  static const char **xopts;
@@@ -189,8 -191,6 +190,8 @@@ static struct option builtin_merge_opti
                "create a single commit instead of doing a merge"),
        OPT_BOOLEAN(0, "commit", &option_commit,
                "perform a commit if the merge succeeds (default)"),
 +      OPT_BOOLEAN('e', "edit", &option_edit,
 +              "edit message before committing"),
        OPT_BOOLEAN(0, "ff", &allow_fast_forward,
                "allow fast-forward (default)"),
        OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@@ -218,7 -218,7 +219,7 @@@ static void drop_save(void
        unlink(git_path("MERGE_MODE"));
  }
  
 -static void save_state(void)
 +static int save_state(unsigned char *stash)
  {
        int len;
        struct child_process cp;
  
        if (finish_command(&cp) || len < 0)
                die(_("stash failed"));
 -      else if (!len)
 -              return;
 +      else if (!len)          /* no changes */
 +              return -1;
        strbuf_setlen(&buffer, buffer.len-1);
        if (get_sha1(buffer.buf, stash))
                die(_("not a valid object: %s"), buffer.buf);
 +      return 0;
  }
  
  static void read_empty(unsigned const char *sha1, int verbose)
@@@ -280,8 -279,7 +281,8 @@@ static void reset_hard(unsigned const c
                die(_("read-tree failed"));
  }
  
 -static void restore_state(void)
 +static void restore_state(const unsigned char *head,
 +                        const unsigned char *stash)
  {
        struct strbuf sb = STRBUF_INIT;
        const char *args[] = { "stash", "apply", NULL, NULL };
@@@ -311,25 -309,25 +312,25 @@@ static void finish_up_to_date(const cha
        drop_save();
  }
  
 -static void squash_message(void)
 +static void squash_message(struct commit *commit)
  {
        struct rev_info rev;
 -      struct commit *commit;
        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;
        rev.commit_format = CMIT_FMT_MEDIUM;
  
 -      commit = lookup_commit(head);
        commit->object.flags |= UNINTERESTING;
        add_pending_object(&rev, &commit->object, NULL);
  
        strbuf_release(&out);
  }
  
 -static void finish(const unsigned char *new_head, const char *msg)
 +static void finish(struct commit *head_commit,
 +                 const unsigned char *new_head, const char *msg)
  {
        struct strbuf reflog_message = STRBUF_INIT;
 +      const unsigned char *head = head_commit->object.sha1;
  
        if (!msg)
                strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
                        getenv("GIT_REFLOG_ACTION"), msg);
        }
        if (squash) {
 -              squash_message();
 +              squash_message(head_commit);
        } else {
                if (verbosity >= 0 && !merge_msg.len)
                        printf(_("No merge message -- not updating HEAD\n"));
        strbuf_release(&reflog_message);
  }
  
 +static struct object *want_commit(const char *name)
 +{
 +      struct object *obj;
 +      unsigned char sha1[20];
 +      if (get_sha1(name, sha1))
 +              return NULL;
 +      obj = parse_object(sha1);
 +      return peel_to_type(name, 0, obj, OBJ_COMMIT);
 +}
 +
  /* Get the name for the merge commit's message. */
  static void merge_name(const char *remote, struct strbuf *msg)
  {
        remote = bname.buf;
  
        memset(branch_head, 0, sizeof(branch_head));
 -      remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
 +      remote_head = want_commit(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
  
  
        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");
@@@ -542,6 -526,8 +543,8 @@@ static void parse_branch_merge_options(
  
  static int git_merge_config(const char *k, const char *v, void *cb)
  {
+       int status;
        if (branch && !prefixcmp(k, "branch.") &&
                !prefixcmp(k + 7, branch) &&
                !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
                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;
        return git_diff_ui_config(k, v, cb);
  }
  
@@@ -678,7 -659,7 +676,7 @@@ int try_merge_command(const char *strat
  }
  
  static int try_merge_strategy(const char *strategy, struct commit_list *common,
 -                            const char *head_arg)
 +                            struct commit *head, const char *head_arg)
  {
        int index_fd;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
                        commit_list_insert(j->item, &reversed);
  
                index_fd = hold_locked_index(lock, 1);
 -              clean = merge_recursive(&o, lookup_commit(head),
 +              clean = merge_recursive(&o, head,
                                remoteheads->item, reversed, &result);
                if (active_cache_changed &&
                                (write_cache(index_fd, active_cache, active_nr) ||
@@@ -775,7 -756,7 +773,7 @@@ int checkout_fast_forward(const unsigne
        memset(&t, 0, sizeof(t));
        memset(&dir, 0, sizeof(dir));
        dir.flags |= DIR_SHOW_IGNORED;
 -      dir.exclude_per_dir = ".gitignore";
 +      setup_standard_excludes(&dir);
        opts.dir = &dir;
  
        opts.head_idx = 1;
@@@ -849,78 -830,51 +847,78 @@@ static void add_strategies(const char *
  
  }
  
 -static void write_merge_msg(void)
 +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"));
 -      if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
 -              die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
 +                        filename);
 +      if (write_in_full(fd, msg->buf, msg->len) != msg->len)
 +              die_errno(_("Could not write to '%s'"), filename);
        close(fd);
  }
  
 -static void read_merge_msg(void)
 +static void read_merge_msg(struct strbuf *msg)
  {
 -      strbuf_reset(&merge_msg);
 -      if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
 -              die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
 +      const char *filename = git_path("MERGE_MSG");
 +      strbuf_reset(msg);
 +      if (strbuf_read_file(msg, filename, 0) < 0)
 +              die_errno(_("Could not read from '%s'"), filename);
  }
  
 -static void run_prepare_commit_msg(void)
 +static void write_merge_state(void);
 +static void abort_commit(const char *err_msg)
  {
 -      write_merge_msg();
 +      if (err_msg)
 +              error("%s", err_msg);
 +      fprintf(stderr,
 +              _("Not committing merge; use 'git commit' to complete the merge.\n"));
 +      write_merge_state();
 +      exit(1);
 +}
 +
 +static void prepare_to_commit(void)
 +{
 +      struct strbuf msg = STRBUF_INIT;
 +      strbuf_addbuf(&msg, &merge_msg);
 +      strbuf_addch(&msg, '\n');
 +      write_merge_msg(&msg);
        run_hook(get_index_file(), "prepare-commit-msg",
                 git_path("MERGE_MSG"), "merge", NULL, NULL);
 -      read_merge_msg();
 +      if (option_edit) {
 +              if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
 +                      abort_commit(NULL);
 +      }
 +      read_merge_msg(&msg);
 +      stripspace(&msg, option_edit);
 +      if (!msg.len)
 +              abort_commit(_("Empty commit message."));
 +      strbuf_release(&merge_msg);
 +      strbuf_addbuf(&merge_msg, &msg);
 +      strbuf_release(&msg);
  }
  
 -static int merge_trivial(void)
 +static int merge_trivial(struct commit *head)
  {
        unsigned char result_tree[20], result_commit[20];
        struct commit_list *parent = xmalloc(sizeof(*parent));
  
        write_tree_trivial(result_tree);
        printf(_("Wonderful.\n"));
 -      parent->item = lookup_commit(head);
 +      parent->item = head;
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
 -      run_prepare_commit_msg();
 +      prepare_to_commit();
        commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
 -      finish(result_commit, "In-index merge");
 +      finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
  }
  
 -static int finish_automerge(struct commit_list *common,
 +static int finish_automerge(struct commit *head,
 +                          struct commit_list *common,
                            unsigned char *result_tree,
                            const char *wt_strategy)
  {
        free_commit_list(common);
        if (allow_fast_forward) {
                parents = remoteheads;
 -              commit_list_insert(lookup_commit(head), &parents);
 +              commit_list_insert(head, &parents);
                parents = reduce_heads(parents);
        } else {
                struct commit_list **pptr = &parents;
  
 -              pptr = &commit_list_insert(lookup_commit(head),
 +              pptr = &commit_list_insert(head,
                                pptr)->next;
                for (j = remoteheads; j; j = j->next)
                        pptr = &commit_list_insert(j->item, pptr)->next;
        }
 -      free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
 -      run_prepare_commit_msg();
 +      prepare_to_commit();
 +      free_commit_list(remoteheads);
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
 -      finish(result_commit, buf.buf);
 +      finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
        return 0;
  
  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];
        return 1;
  }
  
 -static struct commit *is_old_style_invocation(int argc, const char **argv)
 +static struct commit *is_old_style_invocation(int argc, const char **argv,
 +                                            const unsigned char *head)
  {
        struct commit *second_token = NULL;
        if (argc > 2) {
@@@ -1051,47 -1003,12 +1049,47 @@@ static int setup_with_upstream(const ch
        return i;
  }
  
 +static void write_merge_state(void)
 +{
 +      const char *filename;
 +      int fd;
 +      struct commit_list *j;
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      for (j = remoteheads; j; j = j->next)
 +              strbuf_addf(&buf, "%s\n",
 +                      sha1_to_hex(j->item->object.sha1));
 +      filename = git_path("MERGE_HEAD");
 +      fd = open(filename, O_WRONLY | O_CREAT, 0666);
 +      if (fd < 0)
 +              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'"), filename);
 +      close(fd);
 +      strbuf_addch(&merge_msg, '\n');
 +      write_merge_msg(&merge_msg);
 +
 +      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"), 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'"), filename);
 +      close(fd);
 +}
 +
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
        unsigned char result_tree[20];
 +      unsigned char stash[20];
 +      unsigned char head_sha1[20];
 +      struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
 -      int flag, head_invalid = 0, i;
 +      int flag, i;
        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;
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
 -      branch = resolve_ref("HEAD", head, 0, &flag);
 +      branch = resolve_ref("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
 -      if (is_null_sha1(head))
 -              head_invalid = 1;
 +      if (!branch || is_null_sha1(head_sha1))
 +              head_commit = NULL;
 +      else
 +              head_commit = lookup_commit_or_die(head_sha1, "HEAD");
  
        git_config(git_merge_config, NULL);
  
                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(_("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)
         * additional safety measure to check for it.
         */
  
 -      if (!have_message && is_old_style_invocation(argc, argv)) {
 +      if (!have_message && head_commit &&
 +          is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
                strbuf_addstr(&merge_msg, argv[0]);
                head_arg = argv[1];
                argv += 2;
                argc -= 2;
 -      } else if (head_invalid) {
 +      } else if (!head_commit) {
                struct object *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
                if (!allow_fast_forward)
                        die(_("Non-fast-forward commit does not make sense into "
                            "an empty head"));
 -              remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
 +              remote_head = want_commit(argv[0]);
                if (!remote_head)
                        die(_("%s - not something we can merge"), argv[0]);
                read_empty(remote_head->sha1, 0);
                }
        }
  
 -      if (head_invalid || !argc)
 +      if (!head_commit || !argc)
                usage_with_options(builtin_merge_usage,
                        builtin_merge_options);
  
                struct object *o;
                struct commit *commit;
  
 -              o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
 +              o = want_commit(argv[i]);
                if (!o)
                        die(_("%s - not something we can merge"), argv[i]);
                commit = lookup_commit(o->sha1);
        }
  
        if (!remoteheads->next)
 -              common = get_merge_bases(lookup_commit(head),
 -                              remoteheads->item, 1);
 +              common = get_merge_bases(head_commit, remoteheads->item, 1);
        else {
                struct commit_list *list = remoteheads;
 -              commit_list_insert(lookup_commit(head), &list);
 +              commit_list_insert(head_commit, &list);
                common = get_octopus_merge_bases(list);
                free(list);
        }
  
 -      update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
 -              DIE_ON_ERR);
 +      update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
 +                 NULL, 0, DIE_ON_ERR);
  
        if (!common)
                ; /* No common ancestors found. We need a real merge. */
                return 0;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
 -                      !hashcmp(common->item->object.sha1, head)) {
 +                      !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
                struct object *o;
                char hex[41];
  
 -              strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
 +              strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
  
                if (verbosity >= 0)
                        printf(_("Updating %s..%s\n"),
                if (have_message)
                        strbuf_addstr(&msg,
                                " (no commit created; -m option ignored)");
 -              o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
 -                      0, NULL, OBJ_COMMIT);
 +              o = want_commit(sha1_to_hex(remoteheads->item->object.sha1));
                if (!o)
                        return 1;
  
 -              if (checkout_fast_forward(head, remoteheads->item->object.sha1))
 +              if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
                        return 1;
  
 -              finish(o->sha1, msg.buf);
 +              finish(head_commit, o->sha1, msg.buf);
                drop_save();
                return 0;
        } 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, remoteheads->item->object.sha1))
 -                              return merge_trivial();
 +                                      head_commit->object.sha1, remoteheads->item->object.sha1))
 +                              return merge_trivial(head_commit);
                        printf(_("Nope.\n"));
                }
        } else {
                         * merge_bases again, otherwise "git merge HEAD^
                         * HEAD^^" would be missed.
                         */
 -                      common_one = get_merge_bases(lookup_commit(head),
 -                              j->item, 1);
 +                      common_one = get_merge_bases(head_commit, j->item, 1);
                        if (hashcmp(common_one->item->object.sha1,
                                j->item->object.sha1)) {
                                up_to_date = 0;
         * sync with the head commit.  The strategies are responsible
         * to ensure this.
         */
 -      if (use_strategies_nr != 1) {
 -              /*
 -               * Stash away the local changes so that we can try more
 -               * than one.
 -               */
 -              save_state();
 -      } else {
 -              memcpy(stash, null_sha1, 20);
 -      }
 +      if (use_strategies_nr == 1 ||
 +          /*
 +           * Stash away the local changes so that we can try more than one.
 +           */
 +          save_state(stash))
 +              hashcpy(stash, null_sha1);
  
        for (i = 0; i < use_strategies_nr; i++) {
                int ret;
                if (i) {
                        printf(_("Rewinding the tree to pristine...\n"));
 -                      restore_state();
 +                      restore_state(head_commit->object.sha1, stash);
                }
                if (use_strategies_nr != 1)
                        printf(_("Trying merge strategy %s...\n"),
                wt_strategy = use_strategies[i]->name;
  
                ret = try_merge_strategy(use_strategies[i]->name,
 -                      common, head_arg);
 +                                       common, head_commit, head_arg);
                if (!option_commit && !ret) {
                        merge_was_ok = 1;
                        /*
         * auto resolved the merge cleanly.
         */
        if (automerge_was_ok)
 -              return finish_automerge(common, result_tree, wt_strategy);
 +              return finish_automerge(head_commit, common, result_tree,
 +                                      wt_strategy);
  
        /*
         * Pick the result from the best strategy and have the user fix
         * it up.
         */
        if (!best_strategy) {
 -              restore_state();
 +              restore_state(head_commit->object.sha1, stash);
                if (use_strategies_nr > 1)
                        fprintf(stderr,
                                _("No merge strategy handled the merge.\n"));
                ; /* We already have its result in the working tree. */
        else {
                printf(_("Rewinding the tree to pristine...\n"));
 -              restore_state();
 +              restore_state(head_commit->object.sha1, stash);
                printf(_("Using the %s to prepare resolving by hand.\n"),
                        best_strategy);
 -              try_merge_strategy(best_strategy, common, head_arg);
 +              try_merge_strategy(best_strategy, common, head_commit, head_arg);
        }
  
        if (squash)
 -              finish(NULL, NULL);
 -      else {
 -              int fd;
 -              struct commit_list *j;
 -
 -              for (j = remoteheads; j; j = j->next)
 -                      strbuf_addf(&buf, "%s\n",
 -                              sha1_to_hex(j->item->object.sha1));
 -              fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
 -              if (fd < 0)
 -                      die_errno(_("Could not open '%s' for writing"),
 -                                git_path("MERGE_HEAD"));
 -              if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 -                      die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
 -              close(fd);
 -              strbuf_addch(&merge_msg, '\n');
 -              write_merge_msg();
 -              fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
 -              if (fd < 0)
 -                      die_errno(_("Could not open '%s' for writing"),
 -                                git_path("MERGE_MODE"));
 -              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"));
 -              close(fd);
 -      }
 +              finish(head_commit, NULL, NULL);
 +      else
 +              write_merge_state();
  
        if (merge_was_ok) {
                fprintf(stderr, _("Automatic merge went well; "
diff --combined environment.c
index 0bee6a7a88299f8c89eedeee25cece1d3cdafef0,4b7ecab8b558914091dd60a1b7f89dd17558404b..2c41d7d6cb66832197d69d49065a3d5b3bb2e5da
@@@ -9,6 -9,7 +9,7 @@@
   */
  #include "cache.h"
  #include "refs.h"
+ #include "fmt-merge-msg.h"
  
  char git_default_email[MAX_GITNAME];
  char git_default_name[MAX_GITNAME];
@@@ -29,7 -30,6 +30,7 @@@ const char *git_log_output_encoding
  int shared_repository = PERM_UMASK;
  const char *apply_default_whitespace;
  const char *apply_default_ignorewhitespace;
 +const char *git_attributes_file;
  int zlib_compression_level = Z_BEST_SPEED;
  int core_compression_level;
  int core_compression_seen;
@@@ -59,6 -59,7 +60,7 @@@ enum object_creation_mode object_creati
  char *notes_ref_name;
  int grafts_replace_parents = 1;
  int core_apply_sparse_checkout;
+ int merge_log_config = -1;
  struct startup_info *startup_info;
  
  /* Parallel index stat data preload? */
@@@ -107,7 -108,7 +109,7 @@@ static char *expand_namespace(const cha
                if (strcmp((*c)->buf, "/") != 0)
                        strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
        strbuf_list_free(components);
 -      if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK)
 +      if (check_refname_format(buf.buf, 0))
                die("bad git namespace path \"%s\"", raw_namespace);
        strbuf_addch(&buf, '/');
        return strbuf_detach(&buf, NULL);