Merge branch 'jg/tag-contains'
authorJunio C Hamano <gitster@pobox.com>
Sun, 1 Feb 2009 02:07:59 +0000 (18:07 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 1 Feb 2009 02:07:59 +0000 (18:07 -0800)
* jg/tag-contains:
git-tag: Add --contains option
Make has_commit() non-static
Make opt_parse_with_commit() non-static

65 files changed:
.gitignore
Documentation/RelNotes-1.6.1.2.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.txt
Documentation/git-checkout.txt
Documentation/git-rev-parse.txt
Documentation/git-shortlog.txt
Makefile
builtin-checkout.c
builtin-clone.c
builtin-fetch--tool.c
builtin-fetch.c
builtin-help.c
cache.h
compat/mingw.h
contrib/difftool/git-difftool-helper
contrib/difftool/git-difftool.txt
daemon.c
diff-no-index.c
diff.c
exec_cmd.c
exec_cmd.h
fast-import.c
git-cvsserver.perl
git-mergetool.sh
git-rebase--interactive.sh
git.c
gitweb/README
gitweb/gitweb.perl
hash-object.c
http-push.c
imap-send.c
index-pack.c
lockfile.c
merge-index.c
merge-tree.c
mktag.c
mktree.c
pack-redundant.c
pager.c
patch-id.c
refs.c
refs.h
sha1_file.c
sha1_name.c
sigchain.c [new file with mode: 0644]
sigchain.h [new file with mode: 0644]
t/README
t/lib-rebase.sh [new file with mode: 0644]
t/t0005-signals.sh [new file with mode: 0755]
t/t1505-rev-parse-last.sh [new file with mode: 0755]
t/t2012-checkout-last.sh [new file with mode: 0755]
t/t3404-rebase-interactive.sh
t/t3410-rebase-preserve-dropped-merges.sh
t/t3411-rebase-preserve-around-merges.sh
t/t3412-rebase-root.sh
t/t4011-diff-symlink.sh
t/t4030-diff-textconv.sh
t/t4114-apply-typechange.sh
t/test-lib.sh
test-sigchain.c [new file with mode: 0644]
unpack-file.c
unpack-trees.c
update-server-info.c
upload-pack.c
var.c
index e8f91ce8cd991800457f2f8387cdb22014cee169..1c57d4c958bc5e8ff539c5f5ddb1c784d923271b 100644 (file)
@@ -153,6 +153,7 @@ test-match-trees
 test-parse-options
 test-path-utils
 test-sha1
+test-sigchain
 common-cmds.h
 *.tar.gz
 *.dsc
diff --git a/Documentation/RelNotes-1.6.1.2.txt b/Documentation/RelNotes-1.6.1.2.txt
new file mode 100644 (file)
index 0000000..230aa3d
--- /dev/null
@@ -0,0 +1,39 @@
+GIT v1.6.1.2 Release Notes
+==========================
+
+Fixes since v1.6.1.1
+--------------------
+
+* The logic for rename detectin in internal diff used by commands like
+  "git diff" and "git blame" have been optimized to avoid loading the same
+  blob repeatedly.
+
+* We did not allow writing out a blob that is larger than 2GB for no good
+  reason.
+
+* "git format-patch -o $dir", when $dir is a relative directory, used it
+  as relative to the root of the work tree, not relative to the current
+  directory.
+
+* v1.6.1 introduced an optimization for "git push" into a repository (A)
+  that borrows its objects from another repository (B) to avoid sending
+  objects that are available in repository B, when they are not yet used
+  by repository A.  However the code on the "git push" sender side was
+  buggy and did not work when repository B had new objects that are not
+  known by the sender.  This caused pushing into a "forked" repository
+  served by v1.6.1 software using "git push" from v1.6.1 sometimes did not
+  work.  The bug was purely on the "git push" sender side, and has been
+  corrected.
+
+* "git status -v" did not paint its diff output in colour even when
+  color.ui configuration was set.
+
+* "git ls-tree" learned --full-tree option to help Porcelain scripts that
+  want to always see the full path regardless of the current working
+  directory.
+
+* "git grep" incorrectly searched in work tree paths even when they are
+  marked as assume-unchanged.  It now searches in the index entries.
+
+* "git gc" with no grace period needlessly ejected packed but unreachable
+  objects in their loose form, only to delete them right away.
index 296804301f38a21baa260c1e5fda41f03c51a88b..3151c85d880f520fbde338b8eaa0981e9ceaa510 100644 (file)
@@ -6,6 +6,11 @@ Updates since v1.6.1
 
 (subsystems)
 
+* git-svn updates.
+
+* gitweb updates, including a new patch view and RSS/Atom feed
+  improvements.
+
 (portability)
 
 (performance)
@@ -15,25 +20,63 @@ Updates since v1.6.1
 
 (usability, bells and whistles)
 
-* "git-add -p" learned 'g'oto action to jump directly to a hunk.
+* automatic typo correction works on aliases as well
+
+* @{-1} is a way to refer to the last branch you were on.  This is
+  accepted not only where an object name is expected, but anywhere
+  a branch name is expected.  E.g. "git branch --track mybranch @{-1}"
+  "git rev-parse --symbolic-full-name @{-1}".
+
+* "git add -p" learned 'g'oto action to jump directly to a hunk.
+
+* when "git am" stops upon a patch that does not apply, it shows the
+  title of the offending patch.
+
+* "git am --directory=<dir>" and "git am --reject" passes these options
+  to underlying "git apply".
+
+* "git clone" now makes its best effort when cloning from an empty
+  repository to set up configuration variables to refer to the remote
+  repository.
+
+* "git checkout -" is a shorthand for "git checkout @{-1}".
 
-* git-cherry defaults to HEAD when the <upstream> argument is not given.
+* "git cherry" defaults to whatever the current branch is tracking (if
+  exists) when the <upstream> argument is not given.
 
-* git-cvsserver can be told not to add extra "via git-CVS emulator" to the
-  commit log message it serves via gitcvs.commitmsgannotation configuration.
+* "git cvsserver" can be told not to add extra "via git-CVS emulator" to
+  the commit log message it serves via gitcvs.commitmsgannotation
+  configuration.
 
-* git-diff learned a new option --inter-hunk-context to coalesce close
+* "git diff" learned a new option --inter-hunk-context to coalesce close
   hunks together and show context between them.
 
-* git-filter-branch learned --prune-empty option that discards commits
+* The definition of what constitutes a word for "git diff --color-words"
+  can be customized via gitattributes, command line or a configuration.
+
+* "git diff" learned --patience to run "patience diff" algorithm.
+
+* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did
+  not work as expected.
+
+* "git filter-branch" learned --prune-empty option that discards commits
   that do not change the contents.
 
-* git-ls-tree learned --full-tree option that shows the path in full
+* "git grep -w" and "git grep" for fixed strings have been optimized.
+
+* "git log" and friends include HEAD to the set of starting points
+  when --all is given.  This makes a difference when you are not on
+  any branch.
+
+* "git ls-tree" learned --full-tree option that shows the path in full
   regardless of where in the work tree hierarchy the command was started.
 
-* git-mergetool learned -y(--no-prompt) option to disable prompting.
+* "git mergetool" learned -y(--no-prompt) option to disable prompting.
+
+* "git rebase -i" can transplant a history down to root to elsewhere
+  with --root option.
 
-* "git-reset --merge" is a new mode that works similar to the way
+* "git reset --merge" is a new mode that works similar to the way
   "git checkout" switches branches, taking the local changes while
   switching to another commit.
 
@@ -52,14 +95,12 @@ release, unless otherwise noted.
 * git-bundle did not exclude annotated tags even when a range given from the
   command line wanted to.
 
-* git-grep did not work correctly for index entries with assume-unchanged bit.
-
 * branch switching and merges had a silly bug that did not validate
   the correct directory when making sure an existing subdirectory is
   clean.
 
 --
 exec >/var/tmp/1
-O=v1.6.1-134-ge98c6a1
+O=v1.6.1.2-252-g8c95d3c
 echo O=$(git describe master)
 git shortlog --no-merges $O..master ^maint
index 9cd51514dbfd987db4423e254e8bab7284352525..3bccffae628e065a6cd92155a9283d5710991fd7 100644 (file)
@@ -133,6 +133,10 @@ the conflicted merge in the specified paths.
 +
 When this parameter names a non-branch (but still a valid commit object),
 your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching).  You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
 
 
 Detached HEAD
index 2921da320d2b84df4d15ec2745e6d94093dd6907..3ccef2f2b3295c6d5e14b8f3a9d354d7b57598da 100644 (file)
@@ -212,6 +212,9 @@ when you run 'git-merge'.
   reflog of the current branch. For example, if you are on the
   branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
 
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+  before the current one.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
index 8f7c0e226df8f58712e6a0d78ecec58c96aa2453..498bd289297803e372cf6bc0079d040c23b0a6bb 100644 (file)
@@ -82,7 +82,7 @@ her family name fully spelled out, a proper `.mailmap` file would look like:
 # Note how we don't need an entry for <jane@laptop.(none)>, because the
 # real name of that author is correct already, and coalesced directly.
 Jane Doe <jane@desktop.(none)>
-Joe R. Developer <joe@random.com>
+Joe R. Developer <joe@example.com>
 ------------
 
 Author
index 9d451cf3e54a02dd7057bc64448a8f8d4a2587b3..9f3a8ab0977bf23eb276e9cf5a6db96b206893e3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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
@@ -179,28 +182,32 @@ STRIP ?= strip
 # 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
@@ -388,6 +395,7 @@ LIB_H += revision.h
 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
@@ -481,6 +489,7 @@ LIB_OBJS += sha1-lookup.o
 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
@@ -785,6 +794,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
@@ -793,9 +803,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        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
@@ -850,7 +857,12 @@ else
                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
 
@@ -1028,6 +1040,9 @@ ifdef INTERNAL_QSORT
        COMPAT_CFLAGS += -DINTERNAL_QSORT
        COMPAT_OBJS += compat/qsort.o
 endif
+ifdef RUNTIME_PREFIX
+       COMPAT_CFLAGS += -DRUNTIME_PREFIX
+endif
 
 ifdef NO_PTHREADS
        THREADED_DELTA_SEARCH =
@@ -1087,6 +1102,7 @@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
 
 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))
@@ -1252,7 +1268,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)"' $<
 
@@ -1366,6 +1387,7 @@ 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)
 
@@ -1411,17 +1433,17 @@ remove-dashes:
 
 ### Installation rules
 
-ifeq ($(firstword $(subst /, ,$(template_dir))),..)
-template_instdir = $(bindir)/$(template_dir)
-else
+ifeq ($(abspath $(template_dir)),$(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
+ifeq ($(abspath $(gitexecdir)),$(gitexecdir))
 gitexec_instdir = $(gitexecdir)
+else
+gitexec_instdir = $(prefix)/$(gitexecdir)
 endif
 gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
 export gitexec_instdir
index 6cdb320ae72b438031049892a9bb78e5bc523c73..20b34ce6e10d9b863226b501cf5a35178b898995 100644 (file)
@@ -351,8 +351,16 @@ struct branch_info {
 static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
-       strbuf_addstr(&buf, "refs/heads/");
-       strbuf_addstr(&buf, branch->name);
+       int ret;
+
+       if ((ret = interpret_nth_last_branch(branch->name, &buf))
+           && ret == strlen(branch->name)) {
+               branch->name = xstrdup(buf.buf);
+               strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+       } else {
+               strbuf_addstr(&buf, "refs/heads/");
+               strbuf_addstr(&buf, branch->name);
+       }
        branch->path = strbuf_detach(&buf, NULL);
 }
 
@@ -661,6 +669,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                arg = argv[0];
                has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
 
+               if (!strcmp(arg, "-"))
+                       arg = "@{-1}";
+
                if (get_sha1(arg, rev)) {
                        if (has_dash_dash)          /* case (1) */
                                die("invalid reference: %s", arg);
index 1e9c9aa8446370070d56126ad851701c4c418de0..f73029e2ba285bfb20bce1760cef711f7a55fd02 100644 (file)
@@ -19,6 +19,7 @@
 #include "strbuf.h"
 #include "dir.h"
 #include "pack-refs.h"
+#include "sigchain.h"
 
 /*
  * Overall FIXMEs:
@@ -288,7 +289,7 @@ static void remove_junk(void)
 static void remove_junk_on_signal(int signo)
 {
        remove_junk();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -441,7 +442,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        }
        junk_git_dir = git_dir;
        atexit(remove_junk);
-       signal(SIGINT, remove_junk_on_signal);
+       sigchain_push_common(remove_junk_on_signal);
 
        setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1);
 
index 469b07e240953aa21fd67eb2563e094a7f0f3d42..29356d25db910c6d90df46da87aa374467611350 100644 (file)
@@ -2,6 +2,7 @@
 #include "cache.h"
 #include "refs.h"
 #include "commit.h"
+#include "sigchain.h"
 
 static char *get_stdin(void)
 {
@@ -186,7 +187,7 @@ static void remove_keep(void)
 static void remove_keep_on_signal(int signo)
 {
        remove_keep();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -245,7 +246,7 @@ static int fetch_native_store(FILE *fp,
        char buffer[1024];
        int err = 0;
 
-       signal(SIGINT, remove_keep_on_signal);
+       sigchain_push_common(remove_keep_on_signal);
        atexit(remove_keep);
 
        while (fgets(buffer, sizeof(buffer), stdin)) {
index de6f3074b1121fdbcbe8bf0593dee446a17fb08e..1e4a3d9c516c88d701819b7f4b73c722412d540f 100644 (file)
@@ -10,6 +10,7 @@
 #include "transport.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "sigchain.h"
 
 static const char * const builtin_fetch_usage[] = {
        "git fetch [options] [<repository> <refspec>...]",
@@ -58,7 +59,7 @@ static void unlock_pack(void)
 static void unlock_pack_on_signal(int signo)
 {
        unlock_pack();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -672,7 +673,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                ref_nr = j;
        }
 
-       signal(SIGINT, unlock_pack_on_signal);
+       sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
        exit_code = do_fetch(transport,
                        parse_fetch_refspec(ref_nr, refs), ref_nr);
index f076efa9211ddcffdc81ffd7c0c56fdb197a11b0..9b57a746185719eb43c962c4adb96283657692e9 100644 (file)
@@ -329,7 +329,7 @@ static void setup_man_path(void)
         * old_path, the ':' at the end will let 'man' to try
         * system-wide paths after ours to find the manual page. If
         * there is old_path, we need ':' as delimiter. */
-       strbuf_addstr(&new_path, GIT_MAN_PATH);
+       strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
        strbuf_addch(&new_path, ':');
        if (old_path)
                strbuf_addstr(&new_path, old_path);
@@ -375,7 +375,7 @@ static void show_man_page(const char *git_cmd)
 static void show_info_page(const char *git_cmd)
 {
        const char *page = cmd_to_page(git_cmd);
-       setenv("INFOPATH", GIT_INFO_PATH, 1);
+       setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
        execlp("info", "info", "gitman", page, NULL);
 }
 
diff --git a/cache.h b/cache.h
index c17fbf58a764e0c96cc4f23d2804ab16e6df7aea..45e713e9283dcf7b241291ac121e4d4a771f5796 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -667,6 +667,7 @@ extern int read_ref(const char *filename, unsigned char *sha1);
 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[];
index 4f275cb8e6a67515292a9dfc60bd1343065067a9..a25589880130f2232aaf626cddcd739ac80dd378 100644 (file)
@@ -21,12 +21,12 @@ typedef int pid_t;
 #define WEXITSTATUS(x) ((x) & 0xff)
 #define WIFSIGNALED(x) ((unsigned)(x) > 259)
 
-#define SIGKILL 0
-#define SIGCHLD 0
-#define SIGPIPE 0
-#define SIGHUP 0
-#define SIGQUIT 0
-#define SIGALRM 100
+#define SIGHUP 1
+#define SIGQUIT 3
+#define SIGKILL 9
+#define SIGPIPE 13
+#define SIGALRM 14
+#define SIGCHLD 17
 
 #define F_GETFD 1
 #define F_SETFD 2
index 0c48506eebdaab3b04e5c018bcc5233582404432..db3af6a833f030cae94dbcd5926aac1b380969a7 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 # git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
-# It supports kdiff3, tkdiff, xxdiff, meld, opendiff, emerge, ecmerge,
-# vimdiff, gvimdiff, and custom user-configurable tools.
+# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff,
+# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools.
 # This script is typically launched by using the 'git difftool'
 # convenience command.
 #
@@ -73,6 +73,10 @@ launch_merge_tool () {
                        > /dev/null 2>&1
                ;;
 
+       kompare)
+               "$merge_tool_path" "$LOCAL" "$REMOTE"
+               ;;
+
        tkdiff)
                "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
                ;;
@@ -134,7 +138,7 @@ valid_custom_tool() {
 # Built-in merge tools are always valid.
 valid_tool() {
        case "$1" in
-       kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
+       kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
                ;; # happy
        *)
                if ! valid_custom_tool "$1"
@@ -177,31 +181,24 @@ fi
 
 # Try to guess an appropriate merge tool if no tool has been set.
 if test -z "$merge_tool"; then
-
        # We have a $DISPLAY so try some common UNIX merge tools
        if test -n "$DISPLAY"; then
-               merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
-               # If gnome then prefer meld
-               if test -n "$GNOME_DESKTOP_SESSION_ID"; then
-                       merge_tool_candidates="meld $merge_tool_candidates"
-               fi
-               # If KDE then prefer kdiff3
-               if test "$KDE_FULL_SESSION" = "true"; then
-                       merge_tool_candidates="kdiff3 $merge_tool_candidates"
+               # If gnome then prefer meld, otherwise, prefer kdiff3 or kompare
+               if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+                       merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff"
+               else
+                       merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff"
                fi
        fi
-
-       # $EDITOR is emacs so add emerge as a candidate
        if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
-               merge_tool_candidates="$merge_tool_candidates emerge"
+               # $EDITOR is emacs so add emerge as a candidate
+               merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
+       elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+               # $EDITOR is vim so add vimdiff as a candidate
+               merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
+       else
+               merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
        fi
-
-       # $EDITOR is vim so add vimdiff as a candidate
-       if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
-               merge_tool_candidates="$merge_tool_candidates vimdiff"
-       fi
-
-       merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
        echo "merge tool candidates: $merge_tool_candidates"
 
        # Loop over each candidate and stop when a valid merge tool is found.
index ca3dbd2465f9c33f450f9d7bfb9d60206ef509ae..6e2610cda6d2721eb4fb9ac063bb47ef80bfbae3 100644 (file)
@@ -28,7 +28,8 @@ OPTIONS
 --tool=<tool>::
        Use the merge resolution program specified by <tool>.
        Valid merge tools are:
-       kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
+       kdiff3, kompare, tkdiff, meld, xxdiff, emerge,
+       vimdiff, gvimdiff, ecmerge, and opendiff
 +
 If a merge resolution program is not specified, 'git-difftool'
 will use the configuration variable `merge.tool`.  If the
index 540700ee844eb47c417c10a04a85af9af5b23569..d93cf960f9eaf05eec11b67746142e6e94d719cb 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -937,6 +937,8 @@ int main(int argc, char **argv)
        gid_t gid = 0;
        int i;
 
+       git_extract_argv0_path(argv[0]);
+
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
index 60ed17470a6a2bf9bea202a04004b06d207a77d7..0dbd9dad8b100cdd4918571636ad3e9a1a0a2abf 100644 (file)
@@ -40,7 +40,7 @@ static int get_mode(const char *path, int *mode)
                *mode = 0;
        else if (!strcmp(path, "-"))
                *mode = create_ce_mode(0666);
-       else if (stat(path, &st))
+       else if (lstat(path, &st))
                return error("Could not access '%s'", path);
        else
                *mode = st.st_mode;
diff --git a/diff.c b/diff.c
index 972b3daa6578776ca2f262d8e4d3290bae64e234..a5a540fd381495d2a75527b0f4fbe23ad668708f 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -12,6 +12,7 @@
 #include "run-command.h"
 #include "utf8.h"
 #include "userdiff.h"
+#include "sigchain.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -170,6 +171,33 @@ static struct diff_tempfile {
        char tmp_path[PATH_MAX];
 } diff_temp[2];
 
+static struct diff_tempfile *claim_diff_tempfile(void) {
+       int i;
+       for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
+               if (!diff_temp[i].name)
+                       return diff_temp + i;
+       die("BUG: diff is failing to clean up its tempfiles");
+}
+
+static int remove_tempfile_installed;
+
+static void remove_tempfile(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
+               if (diff_temp[i].name == diff_temp[i].tmp_path) {
+                       unlink(diff_temp[i].name);
+                       diff_temp[i].name = NULL;
+               }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+       remove_tempfile();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
 static int count_lines(const char *data, int size)
 {
        int count, ch, completely_empty = 1, nl_just_seen = 0;
@@ -1940,10 +1968,11 @@ static void prep_temp_blob(struct diff_tempfile *temp,
        sprintf(temp->mode, "%06o", mode);
 }
 
-static void prepare_temp_file(const char *name,
-                             struct diff_tempfile *temp,
-                             struct diff_filespec *one)
+static struct diff_tempfile *prepare_temp_file(const char *name,
+               struct diff_filespec *one)
 {
+       struct diff_tempfile *temp = claim_diff_tempfile();
+
        if (!DIFF_FILE_VALID(one)) {
        not_a_valid_file:
                /* A '-' entry produces this for file-2, and
@@ -1952,7 +1981,13 @@ static void prepare_temp_file(const char *name,
                temp->name = "/dev/null";
                strcpy(temp->hex, ".");
                strcpy(temp->mode, ".");
-               return;
+               return temp;
+       }
+
+       if (!remove_tempfile_installed) {
+               atexit(remove_tempfile);
+               sigchain_push_common(remove_tempfile_on_signal);
+               remove_tempfile_installed = 1;
        }
 
        if (!one->sha1_valid ||
@@ -1992,7 +2027,7 @@ static void prepare_temp_file(const char *name,
                         */
                        sprintf(temp->mode, "%06o", one->mode);
                }
-               return;
+               return temp;
        }
        else {
                if (diff_populate_filespec(one, 0))
@@ -2000,24 +2035,7 @@ static void prepare_temp_file(const char *name,
                prep_temp_blob(temp, one->data, one->size,
                               one->sha1, one->mode);
        }
-}
-
-static void remove_tempfile(void)
-{
-       int i;
-
-       for (i = 0; i < 2; i++)
-               if (diff_temp[i].name == diff_temp[i].tmp_path) {
-                       unlink(diff_temp[i].name);
-                       diff_temp[i].name = NULL;
-               }
-}
-
-static void remove_tempfile_on_signal(int signo)
-{
-       remove_tempfile();
-       signal(SIGINT, SIG_DFL);
-       raise(signo);
+       return temp;
 }
 
 /* An external diff command takes:
@@ -2035,34 +2053,22 @@ static void run_external_diff(const char *pgm,
                              int complete_rewrite)
 {
        const char *spawn_arg[10];
-       struct diff_tempfile *temp = diff_temp;
        int retval;
-       static int atexit_asked = 0;
-       const char *othername;
        const char **arg = &spawn_arg[0];
 
-       othername = (other? other : name);
-       if (one && two) {
-               prepare_temp_file(name, &temp[0], one);
-               prepare_temp_file(othername, &temp[1], two);
-               if (! atexit_asked &&
-                   (temp[0].name == temp[0].tmp_path ||
-                    temp[1].name == temp[1].tmp_path)) {
-                       atexit_asked = 1;
-                       atexit(remove_tempfile);
-               }
-               signal(SIGINT, remove_tempfile_on_signal);
-       }
-
        if (one && two) {
+               struct diff_tempfile *temp_one, *temp_two;
+               const char *othername = (other ? other : name);
+               temp_one = prepare_temp_file(name, one);
+               temp_two = prepare_temp_file(othername, two);
                *arg++ = pgm;
                *arg++ = name;
-               *arg++ = temp[0].name;
-               *arg++ = temp[0].hex;
-               *arg++ = temp[0].mode;
-               *arg++ = temp[1].name;
-               *arg++ = temp[1].hex;
-               *arg++ = temp[1].mode;
+               *arg++ = temp_one->name;
+               *arg++ = temp_one->hex;
+               *arg++ = temp_one->mode;
+               *arg++ = temp_two->name;
+               *arg++ = temp_two->hex;
+               *arg++ = temp_two->mode;
                if (other) {
                        *arg++ = other;
                        *arg++ = xfrm_msg;
@@ -2081,16 +2087,86 @@ static void run_external_diff(const char *pgm,
        }
 }
 
+static int similarity_index(struct diff_filepair *p)
+{
+       return p->score * 100 / MAX_SCORE;
+}
+
+static void fill_metainfo(struct strbuf *msg,
+                         const char *name,
+                         const char *other,
+                         struct diff_filespec *one,
+                         struct diff_filespec *two,
+                         struct diff_options *o,
+                         struct diff_filepair *p)
+{
+       strbuf_init(msg, PATH_MAX * 2 + 300);
+       switch (p->status) {
+       case DIFF_STATUS_COPIED:
+               strbuf_addf(msg, "similarity index %d%%", similarity_index(p));
+               strbuf_addstr(msg, "\ncopy from ");
+               quote_c_style(name, msg, NULL, 0);
+               strbuf_addstr(msg, "\ncopy to ");
+               quote_c_style(other, msg, NULL, 0);
+               strbuf_addch(msg, '\n');
+               break;
+       case DIFF_STATUS_RENAMED:
+               strbuf_addf(msg, "similarity index %d%%", similarity_index(p));
+               strbuf_addstr(msg, "\nrename from ");
+               quote_c_style(name, msg, NULL, 0);
+               strbuf_addstr(msg, "\nrename to ");
+               quote_c_style(other, msg, NULL, 0);
+               strbuf_addch(msg, '\n');
+               break;
+       case DIFF_STATUS_MODIFIED:
+               if (p->score) {
+                       strbuf_addf(msg, "dissimilarity index %d%%\n",
+                                   similarity_index(p));
+                       break;
+               }
+               /* fallthru */
+       default:
+               /* nothing */
+               ;
+       }
+       if (one && two && hashcmp(one->sha1, two->sha1)) {
+               int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
+
+               if (DIFF_OPT_TST(o, BINARY)) {
+                       mmfile_t mf;
+                       if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
+                           (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
+                               abbrev = 40;
+               }
+               strbuf_addf(msg, "index %.*s..%.*s",
+                           abbrev, sha1_to_hex(one->sha1),
+                           abbrev, sha1_to_hex(two->sha1));
+               if (one->mode == two->mode)
+                       strbuf_addf(msg, " %06o", one->mode);
+               strbuf_addch(msg, '\n');
+       }
+       if (msg->len)
+               strbuf_setlen(msg, msg->len - 1);
+}
+
 static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
                         const char *attr_path,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
-                        const char *xfrm_msg,
+                        struct strbuf *msg,
                         struct diff_options *o,
-                        int complete_rewrite)
+                        struct diff_filepair *p)
 {
+       const char *xfrm_msg = NULL;
+       int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score;
+
+       if (msg) {
+               fill_metainfo(msg, name, other, one, two, o, p);
+               xfrm_msg = msg->len ? msg->buf : NULL;
+       }
+
        if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
                pgm = NULL;
        else {
@@ -2130,11 +2206,6 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
                hashclr(one->sha1);
 }
 
-static int similarity_index(struct diff_filepair *p)
-{
-       return p->score * 100 / MAX_SCORE;
-}
-
 static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
 {
        /* Strip the prefix but do not molest /dev/null and absolute paths */
@@ -2148,13 +2219,11 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 {
        const char *pgm = external_diff();
        struct strbuf msg;
-       char *xfrm_msg;
        struct diff_filespec *one = p->one;
        struct diff_filespec *two = p->two;
        const char *name;
        const char *other;
        const char *attr_path;
-       int complete_rewrite = 0;
 
        name  = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
@@ -2164,83 +2233,34 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 
        if (DIFF_PAIR_UNMERGED(p)) {
                run_diff_cmd(pgm, name, NULL, attr_path,
-                            NULL, NULL, NULL, o, 0);
+                            NULL, NULL, NULL, o, p);
                return;
        }
 
        diff_fill_sha1_info(one);
        diff_fill_sha1_info(two);
 
-       strbuf_init(&msg, PATH_MAX * 2 + 300);
-       switch (p->status) {
-       case DIFF_STATUS_COPIED:
-               strbuf_addf(&msg, "similarity index %d%%", similarity_index(p));
-               strbuf_addstr(&msg, "\ncopy from ");
-               quote_c_style(name, &msg, NULL, 0);
-               strbuf_addstr(&msg, "\ncopy to ");
-               quote_c_style(other, &msg, NULL, 0);
-               strbuf_addch(&msg, '\n');
-               break;
-       case DIFF_STATUS_RENAMED:
-               strbuf_addf(&msg, "similarity index %d%%", similarity_index(p));
-               strbuf_addstr(&msg, "\nrename from ");
-               quote_c_style(name, &msg, NULL, 0);
-               strbuf_addstr(&msg, "\nrename to ");
-               quote_c_style(other, &msg, NULL, 0);
-               strbuf_addch(&msg, '\n');
-               break;
-       case DIFF_STATUS_MODIFIED:
-               if (p->score) {
-                       strbuf_addf(&msg, "dissimilarity index %d%%\n",
-                                       similarity_index(p));
-                       complete_rewrite = 1;
-                       break;
-               }
-               /* fallthru */
-       default:
-               /* nothing */
-               ;
-       }
-
-       if (hashcmp(one->sha1, two->sha1)) {
-               int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
-
-               if (DIFF_OPT_TST(o, BINARY)) {
-                       mmfile_t mf;
-                       if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
-                           (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
-                               abbrev = 40;
-               }
-               strbuf_addf(&msg, "index %.*s..%.*s",
-                               abbrev, sha1_to_hex(one->sha1),
-                               abbrev, sha1_to_hex(two->sha1));
-               if (one->mode == two->mode)
-                       strbuf_addf(&msg, " %06o", one->mode);
-               strbuf_addch(&msg, '\n');
-       }
-
-       if (msg.len)
-               strbuf_setlen(&msg, msg.len - 1);
-       xfrm_msg = msg.len ? msg.buf : NULL;
-
        if (!pgm &&
            DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
            (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
-               /* a filepair that changes between file and symlink
+               /*
+                * a filepair that changes between file and symlink
                 * needs to be split into deletion and creation.
                 */
                struct diff_filespec *null = alloc_filespec(two->path);
                run_diff_cmd(NULL, name, other, attr_path,
-                            one, null, xfrm_msg, o, 0);
+                            one, null, &msg, o, p);
                free(null);
+               strbuf_release(&msg);
+
                null = alloc_filespec(one->path);
                run_diff_cmd(NULL, name, other, attr_path,
-                            null, two, xfrm_msg, o, 0);
+                            null, two, &msg, o, p);
                free(null);
        }
        else
                run_diff_cmd(pgm, name, other, attr_path,
-                            one, two, xfrm_msg, o, complete_rewrite);
+                            one, two, &msg, o, p);
 
        strbuf_release(&msg);
 }
@@ -3537,15 +3557,15 @@ void diff_unmerge(struct diff_options *options,
 static char *run_textconv(const char *pgm, struct diff_filespec *spec,
                size_t *outsize)
 {
-       struct diff_tempfile temp;
+       struct diff_tempfile *temp;
        const char *argv[3];
        const char **arg = argv;
        struct child_process child;
        struct strbuf buf = STRBUF_INIT;
 
-       prepare_temp_file(spec->path, &temp, spec);
+       temp = prepare_temp_file(spec->path, spec);
        *arg++ = pgm;
-       *arg++ = temp.name;
+       *arg++ = temp->name;
        *arg = NULL;
 
        memset(&child, 0, sizeof(child));
@@ -3554,13 +3574,11 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        if (start_command(&child) != 0 ||
            strbuf_read(&buf, child.out, 0) < 0 ||
            finish_command(&child) != 0) {
-               if (temp.name == temp.tmp_path)
-                       unlink(temp.name);
+               remove_tempfile();
                error("error running textconv command '%s'", pgm);
                return NULL;
        }
-       if (temp.name == temp.tmp_path)
-               unlink(temp.name);
+       remove_tempfile();
 
        return strbuf_detach(&buf, outsize);
 }
index cdd35f91954bdc751455e1083a0612a21eeadc67..f234066defd637dc3c048ba3d9eb43f1ecbad446 100644 (file)
@@ -9,17 +9,78 @@ static const char *argv0_path;
 
 const char *system_path(const char *path)
 {
-       if (!is_absolute_path(path) && argv0_path) {
-               struct strbuf d = STRBUF_INIT;
-               strbuf_addf(&d, "%s/%s", argv0_path, path);
-               path = strbuf_detach(&d, NULL);
+#ifdef RUNTIME_PREFIX
+       static const char *prefix;
+#else
+       static const char *prefix = PREFIX;
+#endif
+       struct strbuf d = STRBUF_INIT;
+
+       if (is_absolute_path(path))
+               return path;
+
+#ifdef RUNTIME_PREFIX
+       assert(argv0_path);
+       assert(is_absolute_path(argv0_path));
+
+       if (!prefix) {
+               const char *strip[] = {
+                       GIT_EXEC_PATH,
+                       BINDIR,
+                       0
+               };
+               const char **s;
+
+               for (s = strip; *s; s++) {
+                       const char *sargv = argv0_path + strlen(argv0_path);
+                       const char *ss = *s + strlen(*s);
+                       while (argv0_path < sargv && *s < ss
+                               && (*sargv == *ss ||
+                                   (is_dir_sep(*sargv) && is_dir_sep(*ss)))) {
+                               sargv--;
+                               ss--;
+                       }
+                       if (*s == ss) {
+                               struct strbuf d = STRBUF_INIT;
+                               /* We also skip the trailing directory separator. */
+                               assert(sargv - argv0_path - 1 >= 0);
+                               strbuf_add(&d, argv0_path, sargv - argv0_path - 1);
+                               prefix = strbuf_detach(&d, NULL);
+                               break;
+                       }
+               }
        }
+
+       if (!prefix) {
+               prefix = PREFIX;
+               fprintf(stderr, "RUNTIME_PREFIX requested, "
+                               "but prefix computation failed.  "
+                               "Using static fallback '%s'.\n", prefix);
+       }
+#endif
+
+       strbuf_addf(&d, "%s/%s", prefix, path);
+       path = strbuf_detach(&d, NULL);
        return path;
 }
 
-void git_set_argv0_path(const char *path)
+const char *git_extract_argv0_path(const char *argv0)
 {
-       argv0_path = path;
+       const char *slash;
+
+       if (!argv0 || !*argv0)
+               return NULL;
+       slash = argv0 + strlen(argv0);
+
+       while (argv0 <= slash && !is_dir_sep(*slash))
+               slash--;
+
+       if (slash >= argv0) {
+               argv0_path = xstrndup(argv0, slash - argv0);
+               return slash + 1;
+       }
+
+       return argv0;
 }
 
 void git_set_argv_exec_path(const char *exec_path)
@@ -61,9 +122,7 @@ void setup_path(void)
        const char *old_path = getenv("PATH");
        struct strbuf new_path = STRBUF_INIT;
 
-       add_path(&new_path, argv_exec_path);
-       add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT));
-       add_path(&new_path, system_path(GIT_EXEC_PATH));
+       add_path(&new_path, git_exec_path());
        add_path(&new_path, argv0_path);
 
        if (old_path)
index 594f961387240c221020c9ea0bccd8a39ff69595..e2b546b615e2806bf7d733099ca0ac7bcfaef823 100644 (file)
@@ -2,8 +2,8 @@
 #define GIT_EXEC_CMD_H
 
 extern void git_set_argv_exec_path(const char *exec_path);
-extern void git_set_argv0_path(const char *path);
-extern const chargit_exec_path(void);
+extern const char *git_extract_argv0_path(const char *path);
+extern const char *git_exec_path(void);
 extern void setup_path(void);
 extern const char **prepare_git_cmd(const char **argv);
 extern int execv_git_cmd(const char **argv); /* NULL terminated */
index f0e08aca70c16e9309dde87954593a76ad37b9ef..1935206be04f3ecc52512958879734b0895d1953 100644 (file)
@@ -150,6 +150,7 @@ Format of STDIN stream:
 #include "refs.h"
 #include "csum-file.h"
 #include "quote.h"
+#include "exec_cmd.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -2406,6 +2407,8 @@ int main(int argc, const char **argv)
 {
        unsigned int i, show_stats = 1;
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
index fef7faf33947a87ded0e8a2d9fd6f1af6047d951..ab6cea3e538047c30a5d728c7a16ee3c935c070b 100755 (executable)
@@ -76,6 +76,7 @@
     'history'         => \&req_CATCHALL,
     'watchers'        => \&req_EMPTY,
     'editors'         => \&req_EMPTY,
+    'noop'            => \&req_EMPTY,
     'annotate'        => \&req_annotate,
     'Global_option'   => \&req_Globaloption,
     #'annotate'        => \&req_CATCHALL,
@@ -1413,14 +1414,14 @@ sub req_ci
                close $pipe || die "bad pipe: $! $?";
        }
 
+    $updater->update();
+
        ### Then hooks/post-update
        $hook = $ENV{GIT_DIR}.'hooks/post-update';
        if (-x $hook) {
                system($hook, "refs/heads/$state->{module}");
        }
 
-    $updater->update();
-
     # foreach file specified on the command line ...
     foreach my $filename ( @committedfiles )
     {
index 00e13373061cc7808d509f8232a259b858b6b642..09f3a1068f69718fa544e3e614b1040ed33ab292 100755 (executable)
@@ -390,21 +390,19 @@ fi
 
 if test -z "$merge_tool" ; then
     if test -n "$DISPLAY"; then
-        merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
         if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
-            merge_tool_candidates="meld $merge_tool_candidates"
-        fi
-        if test "$KDE_FULL_SESSION" = "true"; then
-            merge_tool_candidates="kdiff3 $merge_tool_candidates"
+            merge_tool_candidates="meld kdiff3 tkdiff xxdiff gvimdiff"
+        else
+            merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
         fi
     fi
     if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
-        merge_tool_candidates="$merge_tool_candidates emerge"
-    fi
-    if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
-        merge_tool_candidates="$merge_tool_candidates vimdiff"
+        merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
+    elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+        merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
+    else
+        merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
     fi
-    merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
     echo "merge tool candidates: $merge_tool_candidates"
     for i in $merge_tool_candidates; do
         init_merge_tool_path $i
index 1438650ae8a2ec71615821168970936b9d170f35..3dc659dd5896ce6c887eb786718919aef82f4ac6 100755 (executable)
@@ -373,17 +373,15 @@ do_next () {
                pick_one -n $sha1 || failed=t
                case "$(peek_next_command)" in
                squash|s)
-                       EDIT_COMMIT=
                        USE_OUTPUT=output
                        MSG_OPT=-F
-                       MSG_FILE="$MSG"
+                       EDIT_OR_FILE="$MSG"
                        cp "$MSG" "$SQUASH_MSG"
                        ;;
                *)
-                       EDIT_COMMIT=-e
                        USE_OUTPUT=
                        MSG_OPT=
-                       MSG_FILE=
+                       EDIT_OR_FILE=-e
                        rm -f "$SQUASH_MSG" || exit
                        cp "$MSG" "$GIT_DIR"/SQUASH_MSG
                        rm -f "$GIT_DIR"/MERGE_MSG || exit
@@ -397,7 +395,8 @@ do_next () {
                        GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
                        GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
                        GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
-                       $USE_OUTPUT git commit --no-verify $MSG_OPT "$MSG_FILE" $EDIT_COMMIT || failed=t
+                       $USE_OUTPUT git commit --no-verify \
+                               $MSG_OPT "$EDIT_OR_FILE" || failed=t
                fi
                if test $failed = t
                then
diff --git a/git.c b/git.c
index ecc8fad09aebd16410f2736458d60e04f9dfb296..320cb435646361b9d542d22d1592f985008fb750 100644 (file)
--- a/git.c
+++ b/git.c
@@ -442,21 +442,11 @@ static int run_argv(int *argcp, const char ***argv)
 
 int main(int argc, const char **argv)
 {
-       const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help";
-       char *slash = (char *)cmd + strlen(cmd);
+       const char *cmd;
 
-       /*
-        * Take the basename of argv[0] as the command
-        * name, and the dirname as the default exec_path
-        * if we don't have anything better.
-        */
-       while (cmd <= slash && !is_dir_sep(*slash))
-               slash--;
-       if (cmd <= slash) {
-               *slash++ = 0;
-               git_set_argv0_path(cmd);
-               cmd = slash;
-       }
+       cmd = git_extract_argv0_path(argv[0]);
+       if (!cmd)
+               cmd = "git-help";
 
        /*
         * "git-xxxx" is the same as "git xxxx", but we obviously:
index 825162a0b6dce8c354de67a30abfbad94d29fdde..52ad88b34e15223ccac27945139022babeb33f4c 100644 (file)
@@ -322,6 +322,82 @@ something like the following in your gitweb.conf (or gitweb_config.perl) file:
   $home_link = "/";
 
 
+PATH_INFO usage
+-----------------------
+If you enable PATH_INFO usage in gitweb by putting
+
+   $feature{'pathinfo'}{'default'} = [1];
+
+in your gitweb.conf, it is possible to set up your server so that it
+consumes and produces URLs in the form
+
+http://git.example.com/project.git/shortlog/sometag
+
+by using a configuration such as the following, that assumes that
+/var/www/gitweb is the DocumentRoot of your webserver, and that it
+contains the gitweb.cgi script and complementary static files
+(stylesheet, favicon):
+
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+
+The rewrite rule guarantees that existing static files will be properly
+served, whereas any other URL will be passed to gitweb as PATH_INFO
+parameter.
+
+Notice that in this case you don't need special settings for
+@stylesheets, $my_uri and $home_link, but you lose "dumb client" access
+to your project .git dirs. A possible workaround for the latter is the
+following: in your project root dir (e.g. /pub/git) have the projects
+named without a .git extension (e.g. /pub/git/project instead of
+/pub/git/project.git) and configure Apache as follows:
+
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+
+The additional AliasMatch makes it so that
+
+http://git.example.com/project.git
+
+will give raw access to the project's git dir (so that the project can
+be cloned), while
+
+http://git.example.com/project
+
+will provide human-friendly gitweb access.
+
+
 Originally written by:
   Kay Sievers <kay.sievers@vrfy.org>
 
index 931db4f7ebb7e789278ac3e20a8570d40873163e..f27dbb6bf4acfe6f5381d7c86760b1170c8a10ff 100755 (executable)
@@ -2901,9 +2901,14 @@ sub git_header_html {
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
 EOF
-# print out each stylesheet that exist
+       # the stylesheet, favicon etc urls won't work correctly with path_info
+       # unless we set the appropriate base URL
+       if ($ENV{'PATH_INFO'}) {
+               print '<base href="'.esc_url($my_url).'" />\n';
+       }
+       # print out each stylesheet that exist, providing backwards capability
+       # for those people who defined $stylesheet in a config file
        if (defined $stylesheet) {
-#provides backwards capability for those people who define style sheet in a config file
                print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
        } else {
                foreach my $stylesheet (@stylesheets) {
@@ -6015,7 +6020,25 @@ sub git_feed {
        }
        if (defined($commitlist[0])) {
                %latest_commit = %{$commitlist[0]};
-               %latest_date   = parse_date($latest_commit{'author_epoch'});
+               my $latest_epoch = $latest_commit{'committer_epoch'};
+               %latest_date   = parse_date($latest_epoch);
+               my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+               if (defined $if_modified) {
+                       my $since;
+                       if (eval { require HTTP::Date; 1; }) {
+                               $since = HTTP::Date::str2time($if_modified);
+                       } elsif (eval { require Time::ParseDate; 1; }) {
+                               $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+                       }
+                       if (defined $since && $latest_epoch <= $since) {
+                               print $cgi->header(
+                                       -type => $content_type,
+                                       -charset => 'utf-8',
+                                       -last_modified => $latest_date{'rfc2822'},
+                                       -status => '304 Not Modified');
+                               return;
+                       }
+               }
                print $cgi->header(
                        -type => $content_type,
                        -charset => 'utf-8',
@@ -6074,7 +6097,24 @@ sub git_feed {
                print "<title>$title</title>\n" .
                      "<link>$alt_url</link>\n" .
                      "<description>$descr</description>\n" .
-                     "<language>en</language>\n";
+                     "<language>en</language>\n" .
+                     # project owner is responsible for 'editorial' content
+                     "<managingEditor>$owner</managingEditor>\n";
+               if (defined $logo || defined $favicon) {
+                       # prefer the logo to the favicon, since RSS
+                       # doesn't allow both
+                       my $img = esc_url($logo || $favicon);
+                       print "<image>\n" .
+                             "<url>$img</url>\n" .
+                             "<title>$title</title>\n" .
+                             "<link>$alt_url</link>\n" .
+                             "</image>\n";
+               }
+               if (%latest_date) {
+                       print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
+                       print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
+               }
+               print "<generator>gitweb v.$version/$git_version</generator>\n";
        } elsif ($format eq 'atom') {
                print <<XML;
 <feed xmlns="http://www.w3.org/2005/Atom">
@@ -6101,6 +6141,7 @@ sub git_feed {
                } else {
                        print "<updated>$latest_date{'iso-8601'}</updated>\n";
                }
+               print "<generator version='$version/$git_version'>gitweb</generator>\n";
        }
 
        # contents
index 846e91a23126b747fbea8d9a8511f708c3d70e43..37e66779ab9e14dbe5b5416d0e19b04d7f122d52 100644 (file)
@@ -8,6 +8,7 @@
 #include "blob.h"
 #include "quote.h"
 #include "parse-options.h"
+#include "exec_cmd.h"
 
 static void hash_fd(int fd, const char *type, int write_object, const char *path)
 {
@@ -81,6 +82,8 @@ int main(int argc, const char **argv)
 
        type = blob_type;
 
+       git_extract_argv0_path(argv[0]);
+
        git_config(git_default_config, NULL);
 
        argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
index 59037df5025f0e71b882d53489fe5274aa9065f0..18d81acd1b3743761b23ded035dc58e32b6aad80 100644 (file)
@@ -10,6 +10,7 @@
 #include "exec_cmd.h"
 #include "remote.h"
 #include "list-objects.h"
+#include "sigchain.h"
 
 #include <expat.h>
 
@@ -1384,7 +1385,7 @@ static void remove_locks(void)
 static void remove_locks_on_signal(int signo)
 {
        remove_locks();
-       signal(signo, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -2195,6 +2196,8 @@ int main(int argc, char **argv)
        struct ref *ref;
        char *rewritten_url = NULL;
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        remote = xcalloc(sizeof(*remote), 1);
@@ -2277,10 +2280,7 @@ int main(int argc, char **argv)
                goto cleanup;
        }
 
-       signal(SIGINT, remove_locks_on_signal);
-       signal(SIGHUP, remove_locks_on_signal);
-       signal(SIGQUIT, remove_locks_on_signal);
-       signal(SIGTERM, remove_locks_on_signal);
+       sigchain_push_common(remove_locks_on_signal);
 
        /* Check whether the remote has server info files */
        remote->can_update_info_refs = 0;
index c3fa0df855395f08a53c8619af50d28a478bbb3d..f91293c23f2bcb6d673e0316cd3736fdddd0fbe4 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 #include "cache.h"
+#include "exec_cmd.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #endif
@@ -1389,6 +1390,8 @@ int main(int argc, char **argv)
        int total, n = 0;
        int nongit_ok;
 
+       git_extract_argv0_path(argv[0]);
+
        /* init the random number generator */
        arc4_init();
 
index b46a6d65973ac4b6ce4600a67c11e7bb37e49b89..f7a38079e1ec1a68606ba728964efbd5b2a04c4c 100644 (file)
@@ -8,6 +8,7 @@
 #include "tree.h"
 #include "progress.h"
 #include "fsck.h"
+#include "exec_cmd.h"
 
 static const char index_pack_usage[] =
 "git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
@@ -880,6 +881,8 @@ int main(int argc, char **argv)
        struct pack_idx_entry **idx_objects;
        unsigned char pack_sha1[20];
 
+       git_extract_argv0_path(argv[0]);
+
        /*
         * We wish to read the repository's config file if any, and
         * for that it is necessary to call setup_git_directory_gently().
index 8589155532da9eb7f42a1e9c3132fcf42b1b9275..021c3375c10711027269ee58bb9a201bc69c519a 100644 (file)
@@ -2,6 +2,7 @@
  * Copyright (c) 2005, Junio C Hamano
  */
 #include "cache.h"
+#include "sigchain.h"
 
 static struct lock_file *lock_file_list;
 static const char *alternate_index_output;
@@ -24,7 +25,7 @@ static void remove_lock_file(void)
 static void remove_lock_file_on_signal(int signo)
 {
        remove_lock_file();
-       signal(signo, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -136,11 +137,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
        lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
        if (0 <= lk->fd) {
                if (!lock_file_list) {
-                       signal(SIGINT, remove_lock_file_on_signal);
-                       signal(SIGHUP, remove_lock_file_on_signal);
-                       signal(SIGTERM, remove_lock_file_on_signal);
-                       signal(SIGQUIT, remove_lock_file_on_signal);
-                       signal(SIGPIPE, remove_lock_file_on_signal);
+                       sigchain_push_common(remove_lock_file_on_signal);
                        atexit(remove_lock_file);
                }
                lk->owner = getpid();
index 7827e87a928586226570132fc8922991b1cb6c8a..c00a2b385a3373f88b9fd3f087d9681cdc46cbf6 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "run-command.h"
+#include "exec_cmd.h"
 
 static const char *pgm;
 static const char *arguments[9];
@@ -93,6 +94,8 @@ int main(int argc, char **argv)
        if (argc < 3)
                usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
        read_cache();
 
index 2d1413efbbc33c51fd4820933dcb54164e12d706..f18201acdb21f7eb3e09a546382417851b572fd4 100644 (file)
@@ -2,6 +2,7 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "blob.h"
+#include "exec_cmd.h"
 
 static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
 static int resolve_directories = 1;
@@ -344,6 +345,8 @@ int main(int argc, char **argv)
        if (argc != 4)
                usage(merge_tree_usage);
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        buf1 = get_tree_descriptor(t+0, argv[1]);
diff --git a/mktag.c b/mktag.c
index ba3d495e0715d83ffab3103e4d340a3b9ac4f4e7..6d5083eaf04b9ad1b54dad5eaca33d9a73373bbd 100644 (file)
--- a/mktag.c
+++ b/mktag.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "tag.h"
+#include "exec_cmd.h"
 
 /*
  * A signature file has a very simple fixed format: four lines
@@ -159,6 +160,8 @@ int main(int argc, char **argv)
        if (argc != 1)
                usage("git-mktag < signaturefile");
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        if (strbuf_read(&buf, 0, 4096) < 0) {
index 514fd9b15a6680389bf2c1274d29c55d2c1034f7..6283bc3d431eca77e79970d1de59c61ac36af028 100644 (file)
--- a/mktree.c
+++ b/mktree.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "tree.h"
+#include "exec_cmd.h"
 
 static struct treeent {
        unsigned mode;
@@ -70,6 +71,8 @@ int main(int ac, char **av)
        unsigned char sha1[20];
        int line_termination = '\n';
 
+       git_extract_argv0_path(av[0]);
+
        setup_git_directory();
 
        while ((1 < ac) && av[1][0] == '-') {
index e93eb966e2ccd4bfe31a89668d55c2276c45b87a..48a12bc1352ad53fbc19ec8c5982a91673a098e1 100644 (file)
@@ -7,6 +7,7 @@
 */
 
 #include "cache.h"
+#include "exec_cmd.h"
 
 #define BLKSIZE 512
 
@@ -601,6 +602,8 @@ int main(int argc, char **argv)
        unsigned char *sha1;
        char buf[42]; /* 40 byte sha1 + \n + \0 */
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
        for (i = 1; i < argc; i++) {
diff --git a/pager.c b/pager.c
index f19ddbc87df04f117cd5e39189c8322fd5f29d68..4921843577e42b774457a61277b9bc3441d3ab6b 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "run-command.h"
+#include "sigchain.h"
 
 /*
  * This is split up from the rest of git so that we can do
@@ -38,6 +39,13 @@ static void wait_for_pager(void)
        finish_command(&pager_process);
 }
 
+static void wait_for_pager_signal(int signo)
+{
+       wait_for_pager();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
 void setup_pager(void)
 {
        const char *pager = getenv("GIT_PAGER");
@@ -75,6 +83,7 @@ void setup_pager(void)
        close(pager_process.in);
 
        /* this makes sure that the parent terminates after the pager */
+       sigchain_push_common(wait_for_pager_signal);
        atexit(wait_for_pager);
 }
 
index 871f1d20c0e364220d23035b34685ced8737cda8..3660ad461d359a7fb968692f40cea75c9764f61a 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 
 static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
 {
@@ -79,6 +80,8 @@ int main(int argc, char **argv)
        if (argc != 1)
                usage(patch_id_usage);
 
+       git_extract_argv0_path(argv[0]);
+
        generate_id_list();
        return 0;
 }
diff --git a/refs.c b/refs.c
index 33ced65a7801f8653d608a9580e237ce8df39ae2..024211d72b5e21e1c87e98b0f7e3a6982525d82d 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        return 1;
 }
 
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
 {
        const char *logfile;
        FILE *logfp;
@@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
        logfp = fopen(logfile, "r");
        if (!logfp)
                return -1;
+
+       if (ofs) {
+               struct stat statbuf;
+               if (fstat(fileno(logfp), &statbuf) ||
+                   statbuf.st_size < ofs ||
+                   fseek(logfp, -ofs, SEEK_END) ||
+                   fgets(buf, sizeof(buf), logfp))
+                       return -1;
+       }
+
        while (fgets(buf, sizeof(buf), logfp)) {
                unsigned char osha1[20], nsha1[20];
                char *email_end, *message;
@@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
        return ret;
 }
 
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+       return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+}
+
 static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
 {
        DIR *dir = opendir(git_path("logs/%s", base));
diff --git a/refs.h b/refs.h
index 06ad26055661a9b9e475d0f8a7bd6d1cfb42e792..3bb529d3870012feda9a0398cba15c09aee10cf9 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned
 /* iterate over reflog entries */
 typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
 
 /*
  * Calls the specified function for each reflog file until it returns nonzero,
index 360f7e5a028b4842b152f1fcbd9f39f3a8623b5c..8868b800cbb9259f97b8acac3c8701153118b657 100644 (file)
@@ -2340,7 +2340,8 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
 static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
                              void *buf, unsigned long len, time_t mtime)
 {
-       int fd, size, ret;
+       int fd, ret;
+       size_t size;
        unsigned char *compressed;
        z_stream stream;
        char *filename;
index 722fc35a6d98e1c260c6e14738ad3b2d41d924aa..5d0ac0263d04d7ec72a3b7dec4aaf47aec80da5e 100644 (file)
@@ -238,8 +238,28 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is of the form @{-<n>}.
+ */
+static char *substitute_nth_last_branch(const char **string, int *len)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = interpret_nth_last_branch(*string, &buf);
+
+       if (ret == *len) {
+               size_t size;
+               *string = strbuf_detach(&buf, &size);
+               *len = size;
+               return (char *)*string;
+       }
+
+       return NULL;
+}
+
 int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 {
+       char *last_branch = substitute_nth_last_branch(&str, &len);
        const char **p, *r;
        int refs_found = 0;
 
@@ -259,11 +279,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
                                break;
                }
        }
+       free(last_branch);
        return refs_found;
 }
 
 int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
 {
+       char *last_branch = substitute_nth_last_branch(&str, &len);
        const char **p;
        int logs_found = 0;
 
@@ -294,9 +316,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
                if (!warn_ambiguous_refs)
                        break;
        }
+       free(last_branch);
        return logs_found;
 }
 
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
        static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
@@ -307,10 +332,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
-       /* basic@{time or number} format to query ref-log */
+       /* basic@{time or number or -number} format to query ref-log */
        reflog_len = at = 0;
        if (len && str[len-1] == '}') {
-               for (at = 0; at < len - 1; at++) {
+               for (at = len-2; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
                                reflog_len = (len-1) - (at+2);
                                len = at;
@@ -324,6 +349,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                return -1;
 
        if (!len && reflog_len) {
+               struct strbuf buf = STRBUF_INIT;
+               int ret;
+               /* try the @{-N} syntax for n-th checkout */
+               ret = interpret_nth_last_branch(str+at, &buf);
+               if (ret > 0) {
+                       /* substitute this branch name and restart */
+                       return get_sha1_1(buf.buf, buf.len, sha1);
+               } else if (ret == 0) {
+                       return -1;
+               }
                /* allow "@{...}" to mean the current branch reflog */
                refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
        } else if (reflog_len)
@@ -379,8 +414,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
 static int get_parent(const char *name, int len,
                      unsigned char *result, int idx)
 {
@@ -674,6 +707,92 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
        return retval;
 }
 
+struct grab_nth_branch_switch_cbdata {
+       long cnt, alloc;
+       struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+                                 const char *email, unsigned long timestamp, int tz,
+                                 const char *message, void *cb_data)
+{
+       struct grab_nth_branch_switch_cbdata *cb = cb_data;
+       const char *match = NULL, *target = NULL;
+       size_t len;
+       int nth;
+
+       if (!prefixcmp(message, "checkout: moving from ")) {
+               match = message + strlen("checkout: moving from ");
+               target = strstr(match, " to ");
+       }
+
+       if (!match || !target)
+               return 0;
+
+       len = target - match;
+       nth = cb->cnt++ % cb->alloc;
+       strbuf_reset(&cb->buf[nth]);
+       strbuf_add(&cb->buf[nth], match, len);
+       return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns the number of characters parsed if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+       long nth;
+       int i, retval;
+       struct grab_nth_branch_switch_cbdata cb;
+       const char *brace;
+       char *num_end;
+
+       if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+               return -1;
+       brace = strchr(name, '}');
+       if (!brace)
+               return -1;
+       nth = strtol(name+3, &num_end, 10);
+       if (num_end != brace)
+               return -1;
+       if (nth <= 0)
+               return -1;
+       cb.alloc = nth;
+       cb.buf = xmalloc(nth * sizeof(struct strbuf));
+       for (i = 0; i < nth; i++)
+               strbuf_init(&cb.buf[i], 20);
+       cb.cnt = 0;
+       retval = 0;
+       for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+       if (cb.cnt < nth) {
+               cb.cnt = 0;
+               for (i = 0; i < nth; i++)
+                       strbuf_release(&cb.buf[i]);
+               for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+       }
+       if (cb.cnt < nth)
+               goto release_return;
+       i = cb.cnt % nth;
+       strbuf_reset(buf);
+       strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+       retval = brace-name+1;
+
+release_return:
+       for (i = 0; i < nth; i++)
+               strbuf_release(&cb.buf[i]);
+       free(cb.buf);
+
+       return retval;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
diff --git a/sigchain.c b/sigchain.c
new file mode 100644 (file)
index 0000000..1118b99
--- /dev/null
@@ -0,0 +1,52 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define SIGCHAIN_MAX_SIGNALS 32
+
+struct sigchain_signal {
+       sigchain_fun *old;
+       int n;
+       int alloc;
+};
+static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];
+
+static void check_signum(int sig)
+{
+       if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
+               die("BUG: signal out of range: %d", sig);
+}
+
+int sigchain_push(int sig, sigchain_fun f)
+{
+       struct sigchain_signal *s = signals + sig;
+       check_signum(sig);
+
+       ALLOC_GROW(s->old, s->n + 1, s->alloc);
+       s->old[s->n] = signal(sig, f);
+       if (s->old[s->n] == SIG_ERR)
+               return -1;
+       s->n++;
+       return 0;
+}
+
+int sigchain_pop(int sig)
+{
+       struct sigchain_signal *s = signals + sig;
+       check_signum(sig);
+       if (s->n < 1)
+               return 0;
+
+       if (signal(sig, s->old[s->n - 1]) == SIG_ERR)
+               return -1;
+       s->n--;
+       return 0;
+}
+
+void sigchain_push_common(sigchain_fun f)
+{
+       sigchain_push(SIGINT, f);
+       sigchain_push(SIGHUP, f);
+       sigchain_push(SIGTERM, f);
+       sigchain_push(SIGQUIT, f);
+       sigchain_push(SIGPIPE, f);
+}
diff --git a/sigchain.h b/sigchain.h
new file mode 100644 (file)
index 0000000..618083b
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef SIGCHAIN_H
+#define SIGCHAIN_H
+
+typedef void (*sigchain_fun)(int);
+
+int sigchain_push(int sig, sigchain_fun f);
+int sigchain_pop(int sig);
+
+void sigchain_push_common(sigchain_fun f);
+
+#endif /* SIGCHAIN_H */
index 8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f..f208cf1db972d580d977c356bb589cf6147247a7 100644 (file)
--- a/t/README
+++ b/t/README
@@ -212,6 +212,24 @@ library for your script to use.
    is to summarize successes and failures in the test script and
    exit with an appropriate error code.
 
+ - test_tick
+
+   Make commit and tag names consistent by setting the author and
+   committer times to defined stated.  Subsequent calls will
+   advance the times by a fixed amount.
+
+ - test_commit <message> [<filename> [<contents>]]
+
+   Creates a commit with the given message, committing the given
+   file with the given contents (default for both is to reuse the
+   message string), and adds a tag (again reusing the message
+   string as name).  Calls test_tick to make the SHA-1s
+   reproducible.
+
+ - test_merge <message> <commit-or-tag>
+
+   Merges the given rev using the given message.  Like test_commit,
+   creates a tag and calls test_tick before committing.
 
 Tips for Writing Tests
 ----------------------
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
new file mode 100644 (file)
index 0000000..260a231
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# After setting the fake editor with this function, you can
+#
+# - override the commit message with $FAKE_COMMIT_MESSAGE,
+# - amend the commit message with $FAKE_COMMIT_AMEND
+# - check that non-commit messages have a certain line count with $EXPECT_COUNT
+# - rewrite a rebase -i script with $FAKE_LINES in the form
+#
+#      "[<lineno1>] [<lineno2>]..."
+#
+#   If a line number is prefixed with "squash" or "edit", the respective line's
+#   command will be replaced with the specified one.
+
+set_fake_editor () {
+       echo "#!$SHELL_PATH" >fake-editor.sh
+       cat >> fake-editor.sh <<\EOF
+case "$1" in
+*/COMMIT_EDITMSG)
+       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
+       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
+       exit
+       ;;
+esac
+test -z "$EXPECT_COUNT" ||
+       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
+       exit
+test -z "$FAKE_LINES" && exit
+grep -v '^#' < "$1" > "$1".tmp
+rm -f "$1"
+cat "$1".tmp
+action=pick
+for line in $FAKE_LINES; do
+       case $line in
+       squash|edit)
+               action="$line";;
+       *)
+               echo sed -n "${line}s/^pick/$action/p"
+               sed -n "${line}p" < "$1".tmp
+               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
+               action=pick;;
+       esac
+done
+EOF
+
+       test_set_editor "$(pwd)/fake-editor.sh"
+       chmod a+x fake-editor.sh
+}
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
new file mode 100755 (executable)
index 0000000..09f855a
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='signals work as we expect'
+. ./test-lib.sh
+
+cat >expect <<EOF
+three
+two
+one
+EOF
+
+test_expect_success 'sigchain works' '
+       test-sigchain >actual
+       case "$?" in
+       143) true ;; # POSIX w/ SIGTERM=15
+         3) true ;; # Windows
+         *) false ;;
+       esac &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
new file mode 100755 (executable)
index 0000000..d709ecf
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+       echo "$1" > "$1" &&
+       git add "$1" &&
+       git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+       make_commit 1 &&
+       git branch side &&
+       make_commit 2 &&
+       make_commit 3 &&
+       git checkout side &&
+       make_commit 4 &&
+       git merge master &&
+       git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+#  \         \
+#   \         \
+#    --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+test_rev_equivalent () {
+
+       git rev-parse "$1" > expect &&
+       git rev-parse "$2" > output &&
+       test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+       test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+       test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+       test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_success '@{-1}@{1} works' '
+       test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+       test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+       test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
new file mode 100755 (executable)
index 0000000..87b30a2
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo hello >world &&
+       git add world &&
+       git commit -m initial &&
+       git branch other &&
+       echo "hello again" >>world &&
+       git add world &&
+       git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+       test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+       git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+       git checkout - &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+       git checkout - &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+       git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+       git checkout - &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+       git checkout - &&
+       test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+       test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'more switches' '
+       for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+       do
+               git checkout -b branch$i
+       done
+'
+
+more_switches () {
+       for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+       do
+               git checkout branch$i
+       done
+}
+
+test_expect_success 'switch to the last' '
+       more_switches &&
+       git checkout @{-1} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2"
+'
+
+test_expect_success 'switch to second from the last' '
+       more_switches &&
+       git checkout @{-2} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3"
+'
+
+test_expect_success 'switch to third from the last' '
+       more_switches &&
+       git checkout @{-3} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4"
+'
+
+test_expect_success 'switch to fourth from the last' '
+       more_switches &&
+       git checkout @{-4} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5"
+'
+
+test_expect_success 'switch to twelfth from the last' '
+       more_switches &&
+       git checkout @{-12} &&
+       test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
+'
+
+test_done
index 2cc8e7abe1b9244b2d6520cdbb0e0769e2a6d986..603b003edff6d32fe8725f119778658c76c806fb 100755 (executable)
@@ -10,6 +10,10 @@ that the result still makes sense.
 '
 . ./test-lib.sh
 
+. ../lib-rebase.sh
+
+set_fake_editor
+
 # set up two branches like this:
 #
 # A - B - C - D - E
@@ -61,39 +65,6 @@ test_expect_success 'setup' '
        git tag I
 '
 
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
-       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
-       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-       exit
-       ;;
-esac
-test -z "$EXPECT_COUNT" ||
-       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
-       exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
-       case $line in
-       squash|edit)
-               action="$line";;
-       *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
-               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-               action=pick;;
-       esac
-done
-EOF
-
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
-
 test_expect_success 'no changes are a nop' '
        git rebase -i F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
@@ -462,4 +433,30 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
 
 '
 
+test_expect_success 'submodule rebase setup' '
+       git checkout A &&
+       mkdir sub &&
+       (
+               cd sub && git init && >elif &&
+               git add elif && git commit -m "submodule initial"
+       ) &&
+       echo 1 >file1 &&
+       git add file1 sub
+       test_tick &&
+       git commit -m "One" &&
+       echo 2 >file1 &&
+       test_tick &&
+       git commit -a -m "Two" &&
+       (
+               cd sub && echo 3 >elif &&
+               git commit -a -m "submodule second"
+       ) &&
+       test_tick &&
+       git commit -a -m "Three changes submodule"
+'
+
+test_expect_success 'submodule rebase -i' '
+       FAKE_LINES="1 squash 2 3" git rebase -i A
+'
+
 test_done
index 5816415aafe02f03ede5e1afb6f2e92dedc4b3da..c49143a1a45d6949253e2daf0e57f60029041e60 100755 (executable)
@@ -22,47 +22,17 @@ rewritten.
 # where B, D and G touch the same file.
 
 test_expect_success 'setup' '
-       : > file1 &&
-       git add file1 &&
-       test_tick &&
-       git commit -m A &&
-       git tag A &&
-       echo 1 > file1 &&
-       test_tick &&
-       git commit -m B file1 &&
-       : > file2 &&
-       git add file2 &&
-       test_tick &&
-       git commit -m C &&
-       echo 2 > file1 &&
-       test_tick &&
-       git commit -m D file1 &&
-       : > file3 &&
-       git add file3 &&
-       test_tick &&
-       git commit -m E &&
-       git tag E &&
-       git checkout -b branch1 A &&
-       : > file4 &&
-       git add file4 &&
-       test_tick &&
-       git commit -m F &&
-       git tag F &&
-       echo 3 > file1 &&
-       test_tick &&
-       git commit -m G file1 &&
-       git tag G &&
-       : > file5 &&
-       git add file5 &&
-       test_tick &&
-       git commit -m H &&
-       git tag H &&
-       git checkout -b branch2 F &&
-       : > file6 &&
-       git add file6 &&
-       test_tick &&
-       git commit -m I &&
-       git tag I
+       test_commit A file1 &&
+       test_commit B file1 1 &&
+       test_commit C file2 &&
+       test_commit D file1 2 &&
+       test_commit E file3 &&
+       git checkout A &&
+       test_commit F file4 &&
+       test_commit G file1 3 &&
+       test_commit H file5 &&
+       git checkout F &&
+       test_commit I file6
 '
 
 # A - B - C - D - E
@@ -72,68 +42,44 @@ test_expect_success 'setup' '
 #         I -- G2 -- J -- K           I -- K
 # G2 = same changes as G
 test_expect_success 'skip same-resolution merges with -p' '
-       git checkout branch1 &&
+       git checkout H &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m L &&
-       git checkout branch2 &&
-       echo 3 > file1 &&
-       git commit -a -m G2 &&
+       test_commit L file1 23 &&
+       git checkout I &&
+       test_commit G2 file1 3 &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m J &&
-       echo file7 > file7 &&
-       git add file7 &&
-       git commit -m K &&
-       GIT_EDITOR=: git rebase -i -p branch1 &&
-       test $(git rev-parse branch2^^) = $(git rev-parse branch1) &&
+       test_commit J file1 23 &&
+       test_commit K file7 file7 &&
+       git rebase -i -p L &&
+       test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
        test "23" = "$(cat file1)" &&
-       test "" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)" &&
-
-       git checkout branch1 &&
-       git reset --hard H &&
-       git checkout branch2 &&
-       git reset --hard I
+       test "I" = "$(cat file6)" &&
+       test "file7" = "$(cat file7)"
 '
 
 # A - B - C - D - E
 #   \             \ \
-#     F - G - H -- L \        -->   L
-#       \            |               \
-#         I -- G2 -- J -- K           I -- G2 -- K
+#     F - G - H -- L2 \        -->   L2
+#       \             |                \
+#         I -- G3 --- J2 -- K2           I -- G3 -- K2
 # G2 = different changes as G
 test_expect_success 'keep different-resolution merges with -p' '
-       git checkout branch1 &&
+       git checkout H &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m L &&
-       git checkout branch2 &&
-       echo 4 > file1 &&
-       git commit -a -m G2 &&
+       test_commit L2 file1 23 &&
+       git checkout I &&
+       test_commit G3 file1 4 &&
        ! git merge E &&
-       echo 24 > file1 &&
-       git add file1 &&
-       git commit -m J &&
-       echo file7 > file7 &&
-       git add file7 &&
-       git commit -m K &&
-       ! GIT_EDITOR=: git rebase -i -p branch1 &&
+       test_commit J2 file1 24 &&
+       test_commit K2 file7 file7 &&
+       test_must_fail git rebase -i -p L2 &&
        echo 234 > file1 &&
        git add file1 &&
-       GIT_EDITOR=: git rebase --continue &&
-       test $(git rev-parse branch2^^^) = $(git rev-parse branch1) &&
+       git rebase --continue &&
+       test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
        test "234" = "$(cat file1)" &&
-       test "" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)" &&
-
-       git checkout branch1 &&
-       git reset --hard H &&
-       git checkout branch2 &&
-       git reset --hard I
+       test "I" = "$(cat file6)" &&
+       test "file7" = "$(cat file7)"
 '
 
 test_done
index aacfaae843395e383d999e570a6bc9942e4c1c0b..6533505218a51db369d03357603c2ad1926ce448 100755 (executable)
@@ -5,44 +5,14 @@
 
 test_description='git rebase preserve merges
 
-This test runs git rebase with and tries to squash a commit from after a merge
-to before the merge.
+This test runs git rebase with -p and tries to squash a commit from after
+a merge to before the merge.
 '
 . ./test-lib.sh
 
-# Copy/paste from t3404-rebase-interactive.sh
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
-       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
-       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-       exit
-       ;;
-esac
-test -z "$EXPECT_COUNT" ||
-       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
-       exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
-       case $line in
-       squash|edit)
-               action="$line";;
-       *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
-               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-               action=pick;;
-       esac
-done
-EOF
+. ../lib-rebase.sh
 
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
+set_fake_editor
 
 # set up two branches like this:
 #
@@ -51,27 +21,13 @@ chmod a+x fake-editor.sh
 #        -- C1 --
 
 test_expect_success 'setup' '
-       touch a &&
-       touch b &&
-       git add a &&
-       git commit -m A1 &&
-       git tag A1
-       git add b &&
-       git commit -m B1 &&
-       git tag B1 &&
-       git checkout -b branch &&
-       touch c &&
-       git add c &&
-       git commit -m C1 &&
-       git checkout master &&
-       touch d &&
-       git add d &&
-       git commit -m D1 &&
-       git merge branch &&
-       touch f &&
-       git add f &&
-       git commit -m F1 &&
-       git tag F1
+       test_commit A1 &&
+       test_commit B1 &&
+       test_commit C1 &&
+       git reset --hard B1 &&
+       test_commit D1 &&
+       test_merge E1 C1 &&
+       test_commit F1
 '
 
 # Should result in:
@@ -82,7 +38,7 @@ test_expect_success 'setup' '
 #
 test_expect_success 'squash F1 into D1' '
        FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
-       test "$(git rev-parse HEAD^2)" = "$(git rev-parse branch)" &&
+       test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
        git tag E2
 '
@@ -100,32 +56,15 @@ test_expect_success 'squash F1 into D1' '
 # And rebase G1..M1 onto E2
 
 test_expect_success 'rebase two levels of merge' '
-       git checkout -b branch2 A1 &&
-       touch g &&
-       git add g &&
-       git commit -m G1 &&
-       git checkout -b branch3 &&
-       touch h
-       git add h &&
-       git commit -m H1 &&
-       git checkout -b branch4 &&
-       touch i &&
-       git add i &&
-       git commit -m I1 &&
-       git tag I1 &&
-       git checkout branch3 &&
-       touch j &&
-       git add j &&
-       git commit -m J1 &&
-       git merge I1 --no-commit &&
-       git commit -m K1 &&
-       git tag K1 &&
-       git checkout branch2 &&
-       touch l &&
-       git add l &&
-       git commit -m L1 &&
-       git merge K1 --no-commit &&
-       git commit -m M1 &&
+       test_commit G1 &&
+       test_commit H1 &&
+       test_commit I1 &&
+       git checkout -b branch3 H1 &&
+       test_commit J1 &&
+       test_merge K1 I1 &&
+       git checkout -b branch2 G1 &&
+       test_commit L1 &&
+       test_merge M1 K1 &&
        GIT_EDITOR=: git rebase -i -p E2 &&
        test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
index 3d8ff674c0c153473d5b3cc35ca60e90d7d0be2b..8a9154a422980833d8e59fa018b3d9a18423514f 100755 (executable)
@@ -6,24 +6,18 @@ Tests if git rebase --root --onto <newparent> can rebase the root commit.
 '
 . ./test-lib.sh
 
+# we always run the interactive rebases unchanged, so just disable the editor
+GIT_EDITOR=:
+export GIT_EDITOR
+
 test_expect_success 'prepare repository' '
-       echo 1 > A &&
-       git add A &&
-       git commit -m 1 &&
-       echo 2 > A &&
-       git add A &&
-       git commit -m 2 &&
+       test_commit 1 A &&
+       test_commit 2 A &&
        git symbolic-ref HEAD refs/heads/other &&
        rm .git/index &&
-       echo 3 > B &&
-       git add B &&
-       git commit -m 3 &&
-       echo 1 > A &&
-       git add A &&
-       git commit -m 1b &&
-       echo 4 > B &&
-       git add B &&
-       git commit -m 4
+       test_commit 3 B &&
+       test_commit 1b A 1 &&
+       test_commit 4 B
 '
 
 test_expect_success 'rebase --root expects --onto' '
@@ -69,7 +63,7 @@ test_expect_success 'pre-rebase got correct input (2)' '
 
 test_expect_success 'rebase -i --root --onto <newbase>' '
        git checkout -b work3 other &&
-       GIT_EDITOR=: git rebase -i --root --onto master &&
+       git rebase -i --root --onto master &&
        git log --pretty=tformat:"%s" > rebased3 &&
        test_cmp expect rebased3
 '
@@ -80,7 +74,7 @@ test_expect_success 'pre-rebase got correct input (3)' '
 
 test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
        git branch work4 other &&
-       GIT_EDITOR=: git rebase -i --root --onto master work4 &&
+       git rebase -i --root --onto master work4 &&
        git log --pretty=tformat:"%s" > rebased4 &&
        test_cmp expect rebased4
 '
@@ -91,7 +85,7 @@ test_expect_success 'pre-rebase got correct input (4)' '
 
 test_expect_success 'rebase -i -p with linear history' '
        git checkout -b work5 other &&
-       GIT_EDITOR=: git rebase -i -p --root --onto master &&
+       git rebase -i -p --root --onto master &&
        git log --pretty=tformat:"%s" > rebased5 &&
        test_cmp expect rebased5
 '
@@ -103,9 +97,7 @@ test_expect_success 'pre-rebase got correct input (5)' '
 test_expect_success 'set up merge history' '
        git checkout other^ &&
        git checkout -b side &&
-       echo 5 > C &&
-       git add C &&
-       git commit -m 5 &&
+       test_commit 5 C &&
        git checkout other &&
        git merge side
 '
@@ -123,7 +115,7 @@ EOF
 
 test_expect_success 'rebase -i -p with merge' '
        git checkout -b work6 other &&
-       GIT_EDITOR=: git rebase -i -p --root --onto master &&
+       git rebase -i -p --root --onto master &&
        git log --graph --topo-order --pretty=tformat:"%s" > rebased6 &&
        test_cmp expect-side rebased6
 '
@@ -132,9 +124,7 @@ test_expect_success 'set up second root and merge' '
        git symbolic-ref HEAD refs/heads/third &&
        rm .git/index &&
        rm A B C &&
-       echo 6 > D &&
-       git add D &&
-       git commit -m 6 &&
+       test_commit 6 D &&
        git checkout other &&
        git merge third
 '
@@ -156,7 +146,7 @@ EOF
 
 test_expect_success 'rebase -i -p with two roots' '
        git checkout -b work7 other &&
-       GIT_EDITOR=: git rebase -i -p --root --onto master &&
+       git rebase -i -p --root --onto master &&
        git log --graph --topo-order --pretty=tformat:"%s" > rebased7 &&
        test_cmp expect-third rebased7
 '
@@ -172,22 +162,14 @@ EOF
 
 test_expect_success 'pre-rebase hook stops rebase' '
        git checkout -b stops1 other &&
-       (
-               GIT_EDITOR=:
-               export GIT_EDITOR
-               test_must_fail git rebase --root --onto master
-       ) &&
+       test_must_fail git rebase --root --onto master &&
        test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
        test 0 = $(git rev-list other...stops1 | wc -l)
 '
 
 test_expect_success 'pre-rebase hook stops rebase -i' '
        git checkout -b stops2 other &&
-       (
-               GIT_EDITOR=:
-               export GIT_EDITOR
-               test_must_fail git rebase --root --onto master
-       ) &&
+       test_must_fail git rebase --root --onto master &&
        test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
        test 0 = $(git rev-list other...stops2 | wc -l)
 '
@@ -232,11 +214,7 @@ test_expect_success 'rebase --root with conflict (second part)' '
 
 test_expect_success 'rebase -i --root with conflict (first part)' '
        git checkout -b conflict2 other &&
-       (
-               GIT_EDITOR=:
-               export GIT_EDITOR
-               test_must_fail git rebase -i --root --onto master
-       ) &&
+       test_must_fail git rebase -i --root --onto master &&
        git ls-files -u | grep "B$"
 '
 
@@ -274,11 +252,7 @@ EOF
 
 test_expect_success 'rebase -i -p --root with conflict (first part)' '
        git checkout -b conflict3 other &&
-       (
-               GIT_EDITOR=:
-               export GIT_EDITOR
-               test_must_fail git rebase -i -p --root --onto master
-       ) &&
+       test_must_fail git rebase -i -p --root --onto master &&
        git ls-files -u | grep "B$"
 '
 
index 02efecae3ad06e5a62553e990fc0934dd0c65eab..9055c8b318aa8cfa8b89fd19b20e94a7435ee155 100755 (executable)
@@ -82,4 +82,11 @@ test_expect_success \
     git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
+test_expect_success \
+    'diff symlinks with non-existing targets' \
+    'ln -s narf pinky &&
+    ln -s take\ over brain &&
+    test_must_fail git diff --no-index pinky brain > output 2> output.err &&
+    grep narf output &&
+    ! grep error output.err'
 test_done
index 2f27a0ba9ec002b1a6e4d3bd0ed1dc7484d4d14e..a3f0897a52ce2147388baeac6fc64d3b8501b516 100755 (executable)
@@ -104,7 +104,7 @@ cat >expect.typechange <<'EOF'
 -1
 diff --git a/file b/file
 new file mode 120000
-index ad8b3d2..67be421
+index 0000000..67be421
 --- /dev/null
 +++ b/file
 @@ -0,0 +1 @@
index 55334927abb33864a55f8ff49fd0c0c94a3c1769..0f185caa44f3a9d048a2c058d963a1e86e9984fd 100755 (executable)
@@ -25,6 +25,10 @@ test_expect_success 'setup repository and commits' '
        git update-index foo &&
        git commit -m "foo back to file" &&
        git branch foo-back-to-file &&
+       printf "\0" > foo &&
+       git update-index foo &&
+       git commit -m "foo becomes binary" &&
+       git branch foo-becomes-binary &&
        rm -f foo &&
        git update-index --remove foo &&
        mkdir foo &&
@@ -85,6 +89,20 @@ test_expect_success 'symlink becomes file' '
        '
 test_debug 'cat patch'
 
+test_expect_success 'binary file becomes symlink' '
+       git checkout -f foo-becomes-binary &&
+       git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+test_expect_success 'symlink becomes binary file' '
+       git checkout -f foo-symlinked-to-bar &&
+       git diff-tree -p --binary HEAD foo-becomes-binary > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
 
 test_expect_success 'symlink becomes directory' '
        git checkout -f foo-symlinked-to-bar &&
index 41d5a5996ebcf243be067991e65eca6060f54f46..c1839f70b9dce20142dd0a4b50c999a6d4db5301 100644 (file)
@@ -193,6 +193,31 @@ test_tick () {
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
 }
 
+# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+#
+# This will commit a file with the given contents and the given commit
+# message.  It will also add a tag with <message> as name.
+#
+# Both <file> and <contents> default to <message>.
+
+test_commit () {
+       file=${2:-$(echo "$1" | tr 'A-Z' 'a-z')}
+       echo "${3-$1}" > "$file" &&
+       git add "$file" &&
+       test_tick &&
+       git commit -m "$1" &&
+       git tag "$1"
+}
+
+# Call test_merge with the arguments "<message> <commit>", where <commit>
+# can be a tag pointing to the commit-to-merge.
+
+test_merge () {
+       test_tick &&
+       git merge -m "$1" "$2" &&
+       git tag "$1"
+}
+
 # You are not expected to call test_ok_ and test_failure_ directly, use
 # the text_expect_* functions instead.
 
diff --git a/test-sigchain.c b/test-sigchain.c
new file mode 100644 (file)
index 0000000..42db234
--- /dev/null
@@ -0,0 +1,22 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define X(f) \
+static void f(int sig) { \
+       puts(#f); \
+       fflush(stdout); \
+       sigchain_pop(sig); \
+       raise(sig); \
+}
+X(one)
+X(two)
+X(three)
+#undef X
+
+int main(int argc, char **argv) {
+       sigchain_push(SIGTERM, one);
+       sigchain_push(SIGTERM, two);
+       sigchain_push(SIGTERM, three);
+       raise(SIGTERM);
+       return 0;
+}
index bcdc8bbb3b44a43aa43db6035a31478158e070af..6dd8ad02fbb05ca9124473d29132d31f0e14ede0 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "blob.h"
+#include "exec_cmd.h"
 
 static char *create_temp_file(unsigned char *sha1)
 {
@@ -25,6 +26,8 @@ int main(int argc, char **argv)
 {
        unsigned char sha1[20];
 
+       git_extract_argv0_path(argv[0]);
+
        if (argc != 2)
                usage("git-unpack-file <sha1>");
        if (get_sha1(argv[1], sha1))
index 16bc2ca9eb0fe46b90969ba4855d9b4d89369f42..e547282ed5c0027b35cbbd8e4f07bf968c6f2171 100644 (file)
@@ -240,8 +240,11 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con
        return ce;
 }
 
-static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmask, struct cache_entry *src[5],
-       const struct name_entry *names, const struct traverse_info *info)
+static int unpack_nondirectories(int n, unsigned long mask,
+                                unsigned long dirmask,
+                                struct cache_entry **src,
+                                const struct name_entry *names,
+                                const struct traverse_info *info)
 {
        int i;
        struct unpack_trees_options *o = info->data;
@@ -291,7 +294,7 @@ static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmas
 
 static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
 {
-       struct cache_entry *src[5] = { NULL, };
+       struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
        struct unpack_trees_options *o = info->data;
        const struct name_entry *p = names;
 
index 7e8209ea4b43995737b36bc58db47e7dd6eadb19..7b38fd867bba6f8b8dcf9f2094769314b7f23e02 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 
 static const char update_server_info_usage[] =
 "git update-server-info [--force]";
@@ -19,6 +20,8 @@ int main(int ac, char **av)
        if (i != ac)
                usage(update_server_info_usage);
 
+       git_extract_argv0_path(av[0]);
+
        setup_git_directory();
 
        return !!update_server_info(force);
index e5adbc011e0ab71eeb06a42c4bd40cbea0bf3fa2..5db6f939551dff618b9e4c7a8bbf5a51bfa3032f 100644 (file)
@@ -616,6 +616,8 @@ int main(int argc, char **argv)
        int i;
        int strict = 0;
 
+       git_extract_argv0_path(argv[0]);
+
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
diff --git a/var.c b/var.c
index f1eb314e899c518b8dea54b4ff8f3923da7b12cf..7362ed87354a4bea98c28f9a8c315b7209c67fa2 100644 (file)
--- a/var.c
+++ b/var.c
@@ -4,6 +4,7 @@
  * Copyright (C) Eric Biederman, 2005
  */
 #include "cache.h"
+#include "exec_cmd.h"
 
 static const char var_usage[] = "git var [-l | <variable>]";
 
@@ -56,6 +57,8 @@ int main(int argc, char **argv)
                usage(var_usage);
        }
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory_gently(&nongit);
        val = NULL;