diff options
40 files changed, 1362 insertions, 183 deletions
diff --git a/.gitignore b/.gitignore index 284db5dff..25eb4637a 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive +git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag diff --git a/Documentation/config.txt b/Documentation/config.txt index 98c1f3e2e..84e38911e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -202,6 +202,12 @@ http.lowSpeedLimit, http.lowSpeedTime:: Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and 'GIT_HTTP_LOW_SPEED_TIME' environment variables. +http.noEPSV:: + A boolean which disables using of EPSV ftp command by curl. + This can helpful with some "poor" ftp servers which doesn't + support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV' + environment variable. Default is false (curl will use EPSV). + i18n.commitEncoding:: Character encoding the commit messages are stored in; git itself does not care per se, but this information is necessary e.g. when diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 51d7c94d7..d562232e5 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -8,14 +8,15 @@ git-daemon - A really simple server for git repositories SYNOPSIS -------- [verse] -'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] +'git-daemon' [--verbose] [--syslog] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] [--interpolated-path=pathtemplate] + [--reuseaddr] [--detach] [--pid-file=file] [--enable=service] [--disable=service] [--allow-override=service] [--forbid-override=service] - [--reuseaddr] [--detach] [--pid-file=file] - [--user=user [--group=group]] [directory...] + [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]] + [directory...] DESCRIPTION ----------- @@ -54,8 +55,12 @@ OPTIONS --interpolated-path=pathtemplate:: To support virtual hosting, an interpolated path template can be used to dynamically construct alternate paths. The template - supports %H for the target hostname as supplied by the client, + supports %H for the target hostname as supplied by the client but + converted to all lowercase, %CH for the canonical hostname, + %IP for the server's IP address, %P for the port number, and %D for the absolute path of the named repository. + After interpolation, the path is validated against the directory + whitelist. --export-all:: Allow pulling from all directories that look like GIT repositories @@ -64,9 +69,17 @@ OPTIONS --inetd:: Have the server run as an inetd service. Implies --syslog. + Incompatible with --port, --listen, --user and --group options. + +--listen=host_or_ipaddr:: + Listen on an a specific IP address or hostname. IP addresses can + be either an IPv4 address or an IPV6 address if supported. If IPv6 + is not supported, then --listen=hostname is also not supported and + --listen must be given an IPv4 address. + Incompatible with '--inetd' option. ---port:: - Listen on an alternative port. +--port=n:: + Listen on an alternative port. Incompatible with '--inetd' option. --init-timeout:: Timeout between the moment the connection is established and the @@ -182,6 +195,24 @@ clients, a symlink from `/software` into the appropriate default repository could be made as well. +git-daemon as regular daemon for virtual hosts:: + To set up `git-daemon` as a regular, non-inetd service that + handles repositories for multiple virtual hosts based on + their IP addresses, start the daemon like this: ++ +------------------------------------------------ + git-daemon --verbose --export-all + --interpolated-path=/pub/%IP/%D + /pub/192.168.1.200/software + /pub/10.10.220.23/software +------------------------------------------------ ++ +In this example, the root-level directory `/pub` will contain +a subdirectory for each virtual host IP address supported. +Repositories can still be accessed by hostname though, assuming +they correspond to these IP addresses. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki @@ -81,8 +81,6 @@ all: # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. -# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. @@ -174,7 +172,7 @@ SCRIPT_PERL = \ git-send-email.perl git-svn.perl SCRIPT_PYTHON = \ - git-merge-recursive.py + git-merge-recursive-old.py SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ @@ -199,7 +197,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X \ git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ - git-merge-recur$X \ + git-merge-recursive$X \ $(EXTRA_PROGRAMS) # Empty... @@ -524,6 +522,9 @@ endif ifdef NO_INET_NTOP LIB_OBJS += compat/inet_ntop.o endif +ifdef NO_INET_PTON + LIB_OBJS += compat/inet_pton.o +endif ifdef NO_ICONV ALL_CFLAGS += -DNO_ICONV @@ -570,7 +571,8 @@ LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \ + git-merge-recur$X all: $(MAKE) -C templates @@ -585,6 +587,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS help.o: common-cmds.h +git-merge-recur$X: git-merge-recursive$X + rm -f $@ && ln git-merge-recursive$X $@ + $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -722,11 +727,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -merge-recursive.o path-list.o: path-list.h -git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(DIFF_OBJS): diffcore.h @@ -887,6 +887,7 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ + git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/builtin-apply.c b/builtin-apply.c index 25e90d8d2..de5f85526 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -854,6 +854,49 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } +static void check_whitespace(const char *line, int len) +{ + const char *err = "Adds trailing whitespace"; + int seen_space = 0; + int i; + + /* + * We know len is at least two, since we have a '+' and we + * checked that the last character was a '\n' before calling + * this function. That is, an addition of an empty line would + * check the '+' here. Sneaky... + */ + if (isspace(line[len-2])) + goto error; + + /* + * Make sure that there is no space followed by a tab in + * indentation. + */ + err = "Space in indent is followed by a tab"; + for (i = 1; i < len; i++) { + if (line[i] == '\t') { + if (seen_space) + goto error; + } + else if (line[i] == ' ') + seen_space = 1; + else + break; + } + return; + + error: + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) + ; + else + fprintf(stderr, "%s.\n%s:%d:%.*s\n", + err, patch_input_file, linenr, len-2, line+1); +} + + /* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference @@ -904,25 +947,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s trailing = 0; break; case '+': - /* - * We know len is at least two, since we have a '+' and - * we checked that the last character was a '\n' above. - * That is, an addition of an empty line would check - * the '+' here. Sneaky... - */ - if ((new_whitespace != nowarn_whitespace) && - isspace(line[len-2])) { - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < - whitespace_error) - ; - else { - fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", - patch_input_file, - linenr, len-2, line+1); - } - } + if (new_whitespace != nowarn_whitespace) + check_whitespace(line, len); added++; newlines--; trailing = 0; @@ -1494,22 +1520,68 @@ static int apply_line(char *output, const char *patch, int plen) { /* plen is number of bytes to be copied from patch, * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n'. + * patch[plen] is '\n', unless this is the incomplete + * last line. */ + int i; int add_nl_to_tail = 0; - if ((new_whitespace == strip_whitespace) && - 1 < plen && isspace(patch[plen-1])) { + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + if ((new_whitespace != strip_whitespace) || !whitespace_error) { + memcpy(output, patch + 1, plen); + return plen; + } + + if (1 < plen && isspace(patch[plen-1])) { if (patch[plen] == '\n') add_nl_to_tail = 1; plen--; while (0 < plen && isspace(patch[plen])) plen--; - applied_after_stripping++; + fixed = 1; } - memcpy(output, patch + 1, plen); + + for (i = 1; i < plen; i++) { + char ch = patch[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_fix_leading_space = 1; + } + else if (ch == ' ') + last_space_in_indent = i; + else + break; + } + + buf = output; + if (need_fix_leading_space) { + /* between patch[1..last_tab_in_indent] strip the + * funny spaces, updating them to tab as needed. + */ + for (i = 1; i < last_tab_in_indent; i++, plen--) { + char ch = patch[i]; + if (ch != ' ') + *output++ = ch; + else if ((i % 8) == 0) + *output++ = '\t'; + } + fixed = 1; + i = last_tab_in_indent; + } + else + i = 1; + + memcpy(output, patch + i, plen); if (add_nl_to_tail) output[plen++] = '\n'; - return plen; + if (fixed) + applied_after_stripping++; + return output + plen - buf; } static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) diff --git a/builtin-grep.c b/builtin-grep.c index 671878817..4205e5d38 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -325,6 +325,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) else hit |= grep_file(opt, ce->name); } + free_grep_patterns(opt); return hit; } @@ -694,5 +695,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (grep_object(&opt, paths, real_obj, list.objects[i].name)) hit = 1; } + free_grep_patterns(&opt); return !hit; } diff --git a/builtin-log.c b/builtin-log.c index fbc58bbca..9d1ceae44 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -348,6 +348,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH; + if (!output_directory) + output_directory = prefix; + if (output_directory) { if (use_stdout) die("standard output, or directory, which one?"); diff --git a/builtin-update-index.c b/builtin-update-index.c index 0620e779b..a3c0a455a 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -112,11 +112,13 @@ static int add_file_to_cache(const char *path) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (0 <= pos) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, !info_only)) diff --git a/compat/inet_pton.c b/compat/inet_pton.c new file mode 100644 index 000000000..5704e0d2b --- /dev/null +++ b/compat/inet_pton.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 1996-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <string.h> + +#ifndef NS_INT16SZ +#define NS_INT16SZ 2 +#endif + +#ifndef NS_INADDRSZ +#define NS_INADDRSZ 4 +#endif + +#ifndef NS_IN6ADDRSZ +#define NS_IN6ADDRSZ 16 +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +static int inet_pton6(const char *src, unsigned char *dst); + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[NS_INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (! saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + memcpy(dst, tmp, NS_INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ + +#ifndef NO_IPV6 +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, NS_IN6ADDRSZ); + return (1); +} +#endif + +/* int + * isc_net_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int +inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); +#ifndef NO_IPV6 + case AF_INET6: + return (inet_pton6(src, dst)); +#endif + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} diff --git a/configure.ac b/configure.ac index 511cac93d..b1a5833b4 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) AS_HELP_STRING([--without-python], [don't use python scripts])], @@ -100,7 +99,6 @@ AC_PROG_CC AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. if test -z "$NO_PYTHON"; then if test -z "$PYTHON_PATH"; then diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash new file mode 100755 index 000000000..d9cb17d0b --- /dev/null +++ b/contrib/completion/git-completion.bash @@ -0,0 +1,324 @@ +# +# bash completion support for core Git. +# +# Copyright (C) 2006 Shawn Pearce +# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/). +# +# The contained completion routines provide support for completing: +# +# *) local and remote branch names +# *) local and remote tag names +# *) .git/remotes file names +# *) git 'subcommands' +# *) tree paths within 'ref:path/to/file' expressions +# +# To use these routines: +# +# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh). +# 2) Added the following line to your .bashrc: +# source ~/.git-completion.sh +# + +__git_refs () +{ + local cmd i is_hash=y + if [ -d "$1" ]; then + cmd=git-peek-remote + else + cmd=git-ls-remote + fi + for i in $($cmd "$1" 2>/dev/null); do + case "$is_hash,$i" in + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; + n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; + n,*) is_hash=y; echo "$i" ;; + esac + done +} + +__git_refs2 () +{ + local cmd i is_hash=y + if [ -d "$1" ]; then + cmd=git-peek-remote + else + cmd=git-ls-remote + fi + for i in $($cmd "$1" 2>/dev/null); do + case "$is_hash,$i" in + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;; + n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;; + n,*) is_hash=y; echo "$i:$i" ;; + esac + done +} + +__git_remotes () +{ + local i REVERTGLOB=$(shopt -p nullglob) + shopt -s nullglob + for i in .git/remotes/*; do + echo ${i#.git/remotes/} + done + $REVERTGLOB +} + +__git_complete_file () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + ?*:*) + local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')" + cur="$(echo "$cur" | sed 's,^.*:,,')" + case "$cur" in + ?*/*) + pfx="$(echo "$cur" | sed 's,/[^/]*$,,')" + cur="$(echo "$cur" | sed 's,^.*/,,')" + ls="$ref:$pfx" + pfx="$pfx/" + ;; + *) + ls="$ref" + ;; + esac + COMPREPLY=($(compgen -P "$pfx" \ + -W "$(git-ls-tree "$ls" \ + | sed '/^100... blob /s,^.* ,, + /^040000 tree /{ + s,^.* ,, + s,$,/, + } + s/^.* //')" \ + -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + ;; + esac +} + +_git_branch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur")) +} + +_git_cat_file () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-cat-file*,1) + COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur")) + ;; + *) + __git_complete_file + ;; + esac +} + +_git_checkout () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur")) +} + +_git_diff () +{ + __git_complete_file +} + +_git_diff_tree () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur")) +} + +_git_fetch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-fetch*,1) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + *) + case "$cur" in + *:*) + cur=$(echo "$cur" | sed 's/^.*://') + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + ;; + *) + local remote + case "${COMP_WORDS[0]}" in + git-fetch) remote="${COMP_WORDS[1]}" ;; + git) remote="${COMP_WORDS[2]}" ;; + esac + COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur")) + ;; + esac + ;; + esac +} + +_git_ls_remote () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) +} + +_git_ls_tree () +{ + __git_complete_file +} + +_git_log () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + *..*) + local pfx=$(echo "$cur" | sed 's/\.\..*$/../') + cur=$(echo "$cur" | sed 's/^.*\.\.//') + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + ;; + esac +} + +_git_merge_base () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) +} + +_git_pull () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-pull*,1) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + *) + local remote + case "${COMP_WORDS[0]}" in + git-pull) remote="${COMP_WORDS[1]}" ;; + git) remote="${COMP_WORDS[2]}" ;; + esac + COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) + ;; + esac +} + +_git_push () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + + case "${COMP_WORDS[0]},$COMP_CWORD" in + git-push*,1) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + git,2) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + ;; + *) + case "$cur" in + *:*) + local remote + case "${COMP_WORDS[0]}" in + git-push) remote="${COMP_WORDS[1]}" ;; + git) remote="${COMP_WORDS[2]}" ;; + esac + cur=$(echo "$cur" | sed 's/^.*://') + COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur")) + ;; + esac + ;; + esac +} + +_git_show () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) +} + +_git () +{ + if [ $COMP_CWORD = 1 ]; then + COMPREPLY=($(compgen \ + -W "--version $(git help -a|egrep '^ ')" \ + -- "${COMP_WORDS[COMP_CWORD]}")) + else + case "${COMP_WORDS[1]}" in + branch) _git_branch ;; + cat-file) _git_cat_file ;; + checkout) _git_checkout ;; + diff) _git_diff ;; + diff-tree) _git_diff_tree ;; + fetch) _git_fetch ;; + log) _git_log ;; + ls-remote) _git_ls_remote ;; + ls-tree) _git_ls_tree ;; + pull) _git_pull ;; + push) _git_push ;; + show) _git_show ;; + show-branch) _git_log ;; + whatchanged) _git_log ;; + *) COMPREPLY=() ;; + esac + fi +} + +_gitk () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur")) +} + +complete -o default -o nospace -F _git git +complete -o default -F _gitk gitk +complete -o default -F _git_branch git-branch +complete -o default -o nospace -F _git_cat_file git-cat-file +complete -o default -F _git_checkout git-checkout +complete -o default -o nospace -F _git_diff git-diff +complete -o default -F _git_diff_tree git-diff-tree +complete -o default -o nospace -F _git_fetch git-fetch +complete -o default -o nospace -F _git_log git-log +complete -o default -F _git_ls_remote git-ls-remote +complete -o default -o nospace -F _git_ls_tree git-ls-tree +complete -o default -F _git_merge_base git-merge-base +complete -o default -o nospace -F _git_pull git-pull +complete -o default -o nospace -F _git_push git-push +complete -o default -F _git_show git-show +complete -o default -o nospace -F _git_log git-whatchanged + +# The following are necessary only for Cygwin, and only are needed +# when the user has tab-completed the executable name and consequently +# included the '.exe' suffix. +# +complete -o default -o nospace -F _git_cat_file git-cat-file.exe +complete -o default -o nospace -F _git_diff git-diff.exe +complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe +complete -o default -o nospace -F _git_log git-log.exe +complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe +complete -o default -F _git_merge_base git-merge-base.exe +complete -o default -o nospace -F _git_push git-push.exe +complete -o default -o nospace -F _git_log git-whatchanged.exe @@ -9,23 +9,30 @@ #include <syslog.h> #include <pwd.h> #include <grp.h> +#include <limits.h> #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" #include "interpolate.h" +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 256 +#endif + static int log_syslog; static int verbose; static int reuseaddr; static const char daemon_usage[] = -"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" +"git-daemon [--verbose] [--syslog] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" " [--interpolated-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" " [--[enable|disable|allow-override|forbid-override]=service]\n" -" [--user=user [[--group=group]] [directory...]"; +" [--inetd | [--listen=host_or_ipaddr] [--port=n]\n" +" [--user=user [--group=group]]\n" +" [directory...]"; /* List of acceptable pathname prefixes */ static char **ok_paths; @@ -56,13 +63,19 @@ static unsigned int init_timeout; * Feel free to make dynamic as needed. */ #define INTERP_SLOT_HOST (0) -#define INTERP_SLOT_DIR (1) -#define INTERP_SLOT_PERCENT (2) +#define INTERP_SLOT_CANON_HOST (1) +#define INTERP_SLOT_IP (2) +#define INTERP_SLOT_PORT (3) +#define INTERP_SLOT_DIR (4) +#define INTERP_SLOT_PERCENT (5) static struct interp interp_table[] = { { "%H", 0}, + { "%CH", 0}, + { "%IP", 0}, + { "%P", 0}, { "%D", 0}, - { "%%", "%"}, + { "%%", 0}, }; @@ -396,7 +409,11 @@ static void make_service_overridable(const char *name, int ena) { die("No such service %s", name); } -static void parse_extra_args(char *extra_args, int buflen) +/* + * Separate the "extra args" information as supplied by the client connection. + * Any resulting data is squirrelled away in the given interpolation table. + */ +static void parse_extra_args(struct interp *table, char *extra_args, int buflen) { char *val; int vallen; @@ -408,16 +425,88 @@ static void parse_extra_args(char *extra_args, int buflen) val = extra_args + 5; vallen = strlen(val) + 1; if (*val) { - char *save = xmalloc(vallen); - interp_table[INTERP_SLOT_HOST].value = save; - strlcpy(save, val, vallen); + /* Split <host>:<port> at colon. */ + char *host = val; + char *port = strrchr(host, ':'); + if (port) { + *port = 0; + port++; + interp_set_entry(table, INTERP_SLOT_PORT, port); + } + interp_set_entry(table, INTERP_SLOT_HOST, host); } + /* On to the next one */ extra_args = val + vallen; } } } +void fill_in_extra_table_entries(struct interp *itable) +{ + char *hp; + + /* + * Replace literal host with lowercase-ized hostname. + */ + hp = interp_table[INTERP_SLOT_HOST].value; + for ( ; *hp; hp++) + *hp = tolower(*hp); + + /* + * Locate canonical hostname and its IP address. + */ +#ifndef NO_IPV6 + { + struct addrinfo hints; + struct addrinfo *ai, *ai0; + int gai; + static char addrbuf[HOST_NAME_MAX + 1]; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + + gai = getaddrinfo(interp_table[INTERP_SLOT_HOST].value, 0, &hints, &ai0); + if (!gai) { + for (ai = ai0; ai; ai = ai->ai_next) { + struct sockaddr_in *sin_addr = (void *)ai->ai_addr; + + inet_ntop(AF_INET, &sin_addr->sin_addr, + addrbuf, sizeof(addrbuf)); + interp_set_entry(interp_table, + INTERP_SLOT_CANON_HOST, ai->ai_canonname); + interp_set_entry(interp_table, + INTERP_SLOT_IP, addrbuf); + break; + } + freeaddrinfo(ai0); + } + } +#else + { + struct hostent *hent; + struct sockaddr_in sa; + char **ap; + static char addrbuf[HOST_NAME_MAX + 1]; + + hent = gethostbyname(interp_table[INTERP_SLOT_HOST].value); + + ap = hent->h_addr_list; + memset(&sa, 0, sizeof sa); + sa.sin_family = hent->h_addrtype; + sa.sin_port = htons(0); + memcpy(&sa.sin_addr, *ap, hent->h_length); + + inet_ntop(hent->h_addrtype, &sa.sin_addr, + addrbuf, sizeof(addrbuf)); + + interp_set_entry(interp_table, INTERP_SLOT_CANON_HOST, hent->h_name); + interp_set_entry(interp_table, INTERP_SLOT_IP, addrbuf); + } +#endif +} + + static int execute(struct sockaddr *addr) { static char line[1000]; @@ -458,8 +547,16 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; - if (len != pktlen) - parse_extra_args(line + len + 1, pktlen - len - 1); + /* + * Initialize the path interpolation table for this connection. + */ + interp_clear_table(interp_table, ARRAY_SIZE(interp_table)); + interp_set_entry(interp_table, INTERP_SLOT_PERCENT, "%"); + + if (len != pktlen) { + parse_extra_args(interp_table, line + len + 1, pktlen - len - 1); + fill_in_extra_table_entries(interp_table); + } for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); @@ -467,7 +564,12 @@ static int execute(struct sockaddr *addr) if (!strncmp("git-", line, 4) && !strncmp(s->name, line + 4, namelen) && line[namelen + 4] == ' ') { - interp_table[INTERP_SLOT_DIR].value = line+namelen+5; + /* + * Note: The directory here is probably context sensitive, + * and might depend on the actual service being performed. + */ + interp_set_entry(interp_table, + INTERP_SLOT_DIR, line + namelen + 5); return run_service(interp_table, s); } } @@ -663,23 +765,22 @@ static int set_reuse_addr(int sockfd) #ifndef NO_IPV6 -static int socksetup(int port, int **socklist_p) +static int socksetup(char *listen_addr, int listen_port, int **socklist_p) { int socknum = 0, *socklist = NULL; int maxfd = -1; char pbuf[NI_MAXSERV]; - struct addrinfo hints, *ai0, *ai; int gai; - sprintf(pbuf, "%d", port); + sprintf(pbuf, "%d", listen_port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; - gai = getaddrinfo(NULL, pbuf, &hints, &ai0); + gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); if (gai) die("getaddrinfo() failed: %s\n", gai_strerror(gai)); @@ -733,20 +834,27 @@ static int socksetup(int port, int **socklist_p) #else /* NO_IPV6 */ -static int socksetup(int port, int **socklist_p) +static int socksetup(char *listen_addr, int listen_port, int **socklist_p) { struct sockaddr_in sin; int sockfd; + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(listen_port); + + if (listen_addr) { + /* Well, host better be an IP address here. */ + if (inet_pton(AF_INET, listen_addr, &sin.sin_addr.s_addr) <= 0) + return 0; + } else { + sin.sin_addr.s_addr = htonl(INADDR_ANY); + } + sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) return 0; - memset(&sin, 0, sizeof sin); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_ANY); - sin.sin_port = htons(port); - if (set_reuse_addr(sockfd)) { close(sockfd); return 0; @@ -855,13 +963,14 @@ static void store_pid(const char *path) fclose(f); } -static int serve(int port, struct passwd *pass, gid_t gid) +static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid) { int socknum, *socklist; - socknum = socksetup(port, &socklist); + socknum = socksetup(listen_addr, listen_port, &socklist); if (socknum == 0) - die("unable to allocate any listen sockets on port %u", port); + die("unable to allocate any listen sockets on host %s port %u", + listen_addr, listen_port); if (pass && gid && (initgroups(pass->pw_name, gid) || setgid (gid) || @@ -873,7 +982,8 @@ static int serve(int port, struct passwd *pass, gid_t gid) int main(int argc, char **argv) { - int port = DEFAULT_GIT_PORT; + int listen_port = 0; + char *listen_addr = NULL; int inetd_mode = 0; const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; @@ -890,12 +1000,20 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { char *arg = argv[i]; + if (!strncmp(arg, "--listen=", 9)) { + char *p = arg + 9; + char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1); + while (*p) + *ph++ = tolower(*p++); + *ph = 0; + continue; + } if (!strncmp(arg, "--port=", 7)) { char *end; unsigned long n; n = strtoul(arg+7, &end, 0); if (arg[7] && !*end) { - port = n; + listen_port = n; continue; } } @@ -995,6 +1113,11 @@ int main(int argc, char **argv) if (inetd_mode && (group_name || user_name)) die("--user and --group are incompatible with --inetd"); + if (inetd_mode && (listen_port || listen_addr)) + die("--listen= and --port= are incompatible with --inetd"); + else if (listen_port == 0) + listen_port = DEFAULT_GIT_PORT; + if (group_name && !user_name) die("--group supplied without --user"); @@ -1043,5 +1166,5 @@ int main(int argc, char **argv) if (pid_file) store_pid(pid_file); - return serve(port, pass, gid); + return serve(listen_addr, listen_port, pass, gid); } @@ -256,8 +256,12 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) } if (match_string(date, "PM") == 2) { - if (tm->tm_hour > 0 && tm->tm_hour < 12) - tm->tm_hour += 12; + tm->tm_hour = (tm->tm_hour % 12) + 12; + return 2; + } + + if (match_string(date, "AM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 0; return 2; } @@ -598,6 +602,34 @@ static void date_tea(struct tm *tm, int *num) date_time(tm, 17); } +static void date_pm(struct tm *tm, int *num) +{ + int hour, n = *num; + *num = 0; + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12) + 12; +} + +static void date_am(struct tm *tm, int *num) +{ + int hour, n = *num; + *num = 0; + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12); +} + static const struct special { const char *name; void (*fn)(struct tm *, int *); @@ -606,6 +638,8 @@ static const struct special { { "noon", date_noon }, { "midnight", date_midnight }, { "tea", date_tea }, + { "PM", date_pm }, + { "AM", date_am }, { NULL } }; @@ -712,6 +746,27 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) return end; } +static const char *approxidate_digit(const char *date, struct tm *tm, int *num) +{ + char *end; + unsigned long number = strtoul(date, &end, 10); + + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + int match = match_multi_number(number, *end, date, end, tm); + if (match) + return date + match; + } + } + + *num = number; + return end; +} + unsigned long approxidate(const char *date) { int number = 0; @@ -731,9 +786,7 @@ unsigned long approxidate(const char *date) break; date++; if (isdigit(c)) { - char *end; - number = strtoul(date-1, &end, 10); - date = end; + date = approxidate_digit(date-1, &tm, &number); continue; } if (isalpha(c)) @@ -20,12 +20,13 @@ static int diff_use_color_default; static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ - "", /* normal */ - "\033[1m", /* bold */ - "\033[36m", /* cyan */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[33m" /* yellow */ + "", /* PLAIN (normal) */ + "\033[1m", /* METAINFO (bold) */ + "\033[36m", /* FRAGINFO (cyan) */ + "\033[31m", /* OLD (red) */ + "\033[32m", /* NEW (green) */ + "\033[33m", /* COMMIT (yellow) */ + "\033[41m", /* WHITESPACE (red background) */ }; static int parse_diff_color_slot(const char *var, int ofs) @@ -42,6 +43,8 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_FILE_NEW; if (!strcasecmp(var+ofs, "commit")) return DIFF_COMMIT; + if (!strcasecmp(var+ofs, "whitespace")) + return DIFF_WHITESPACE; die("bad config variable '%s'", var); } @@ -205,7 +208,7 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); - printf("--- %s\n+++ %s\n@@ -", name_a, name_b); + printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b); print_line_count(lc_a); printf(" +"); print_line_count(lc_b); @@ -383,9 +386,89 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } +static void emit_line(const char *set, const char *reset, const char *line, int len) +{ + if (len > 0 && line[len-1] == '\n') + len--; + fputs(set, stdout); + fwrite(line, len, 1, stdout); + puts(reset); +} + +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + int col0 = ecbdata->nparents; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int i; + int tail = len; + int need_highlight_leading_space = 0; + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) { + emit_line(set, reset, line, len); + return; + } + + /* The line is a newly added line. Does it have funny leading + * whitespaces? In indent, SP should never precede a TAB. + */ + for (i = col0; i < len; i++) { + if (line[i] == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_highlight_leading_space = 1; + } + else if (line[i] == ' ') + last_space_in_indent = i; + else + break; + } + fputs(set, stdout); + fwrite(line, col0, 1, stdout); + fputs(reset, stdout); + if (((i == len) || line[i] == '\n') && i != col0) { + /* The whole line was indent */ + emit_line(ws, reset, line + col0, len - col0); + return; + } + i = col0; + if (need_highlight_leading_space) { + while (i < last_tab_in_indent) { + if (line[i] == ' ') { + fputs(ws, stdout); + putchar(' '); + fputs(reset, stdout); + } + else + putchar(line[i]); + i++; + } + } + tail = len - 1; + if (line[tail] == '\n' && i < tail) + tail--; + while (i < tail) { + if (!isspace(line[tail])) + break; + tail--; + } + if ((i < tail && line[tail + 1] != '\n')) { + /* This has whitespace between tail+1..len */ + fputs(set, stdout); + fwrite(line + i, tail - i + 1, 1, stdout); + fputs(reset, stdout); + emit_line(ws, reset, line + tail + 1, len - tail - 1); + } + else + emit_line(set, reset, line + i, len - i); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { int i; + int color; struct emit_callback *ecbdata = priv; const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); @@ -403,45 +486,52 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ; if (2 <= i && i < len && line[i] == ' ') { ecbdata->nparents = i - 1; - set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); + emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), + reset, line, len); + return; } - else if (len < ecbdata->nparents) + + if (len < ecbdata->nparents) { set = reset; - else { - int nparents = ecbdata->nparents; - int color = DIFF_PLAIN; - if (ecbdata->diff_words && nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); - if (ecbdata->diff_words) { - if (line[0] == '-') { - diff_words_append(line, len, - &ecbdata->diff_words->minus); - return; - } else if (line[0] == '+') { - diff_words_append(line, len, - &ecbdata->diff_words->plus); - return; - } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - line++; - len--; - } else - for (i = 0; i < nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - set = diff_get_color(ecbdata->color_diff, color); + emit_line(reset, reset, line, len); + return; } - if (len > 0 && line[len-1] == '\n') + + color = DIFF_PLAIN; + if (ecbdata->diff_words && ecbdata->nparents != 1) + /* fall back to normal diff */ + free_diff_words_data(ecbdata); + if (ecbdata->diff_words) { + if (line[0] == '-') { + diff_words_append(line, len, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, len, + &ecbdata->diff_words->plus); + return; + } + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + line++; len--; - fputs (set, stdout); - fwrite (line, len, 1, stdout); - puts (reset); + emit_line(set, reset, line, len); + return; + } + for (i = 0; i < ecbdata->nparents && len; i++) { + if (line[i] == '-') + color = DIFF_FILE_OLD; + else if (line[i] == '+') + color = DIFF_FILE_NEW; + } + + if (color != DIFF_FILE_NEW) { + emit_line(diff_get_color(ecbdata->color_diff, color), + reset, line, len); + return; + } + emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) @@ -86,6 +86,7 @@ enum color_diff { DIFF_FILE_OLD = 4, DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, + DIFF_WHITESPACE = 7, }; const char *diff_get_color(int diff_use_color, enum color_diff ix); @@ -283,7 +283,7 @@ static int dir_exists(const char *dirname, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen) +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only) { DIR *fdir = opendir(path); int contents = 0; @@ -314,7 +314,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co switch (DTYPE(de)) { struct stat st; - int subdir, rewind_base; default: continue; case DT_UNKNOWN: @@ -328,26 +327,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co case DT_DIR: memcpy(fullname + baselen + len, "/", 2); len++; - rewind_base = dir->nr; - subdir = read_directory_recursive(dir, fullname, fullname, - baselen + len); if (dir->show_other_directories && - (subdir || !dir->hide_empty_directories) && !dir_exists(fullname, baselen + len)) { - /* Rewind the read subdirectory */ - while (dir->nr > rewind_base) - free(dir->entries[--dir->nr]); + if (dir->hide_empty_directories && + !read_directory_recursive(dir, + fullname, fullname, + baselen + len, 1)) + continue; break; } - contents += subdir; + + contents += read_directory_recursive(dir, + fullname, fullname, baselen + len, 0); continue; case DT_REG: case DT_LNK: break; } - add_name(dir, fullname, baselen + len); contents++; + if (check_only) + goto exit_early; + else + add_name(dir, fullname, baselen + len); } +exit_early: closedir(fdir); pop_exclude_per_directory(dir, exclude_stk); @@ -393,7 +396,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i } } - read_directory_recursive(dir, path, base, baselen); + read_directory_recursive(dir, path, base, baselen, 0); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } diff --git a/git-branch.sh b/git-branch.sh index e0501ec23..4f31903d6 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -112,6 +112,16 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." +if [ -d "$GIT_DIR/refs/heads/$branchname" ] +then + for refdir in `cd "$GIT_DIR" && \ + find "refs/heads/$branchname" -type d | sort -r` + do + rmdir "$GIT_DIR/$refdir" || \ + die "Could not delete '$refdir', there may still be a ref there." + done +fi + if [ -e "$GIT_DIR/refs/heads/$branchname" ] then if test '' = "$force" diff --git a/git-checkout.sh b/git-checkout.sh index 580a9e8a2..dd477245f 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -4,8 +4,8 @@ USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]' SUBDIRECTORY_OK=Sometimes . git-sh-setup -old=$(git-rev-parse HEAD) old_name=HEAD +old=$(git-rev-parse --verify $old_name 2>/dev/null) new= new_name= force= @@ -139,6 +139,13 @@ fi die "git checkout: to checkout the requested commit you need to specify a name for a new branch which is created and switched to" +if [ "X$old" = X ] +then + echo "warning: You do not appear to currently be on a branch." >&2 + echo "warning: Forcing checkout of $new_name." >&2 + force=1 +fi + if [ "$force" ] then git-read-tree --reset -u $new diff --git a/git-clone.sh b/git-clone.sh index e1b3bf382..3998c55ce 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -31,6 +31,10 @@ clone_dumb_http () { cd "$2" && clone_tmp="$GIT_DIR/clone-tmp" && mkdir -p "$clone_tmp" || exit 1 + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + curl_extra_args="${curl_extra_args} --disable-epsv" + fi http_fetch "$1/info/refs" "$clone_tmp/refs" || { echo >&2 "Cannot get remote repository information. Perhaps git-update-server-info needs to be run there?" diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 99b3dc392..5e23851f8 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -135,7 +135,7 @@ foreach my $f (@files) { if ($fields[4] eq 'M') { push @mfiles, $fields[5]; } - if ($fields[4] eq 'R') { + if ($fields[4] eq 'D') { push @dfiles, $fields[5]; } } diff --git a/git-fetch.sh b/git-fetch.sh index 50ad101e8..f1522bd49 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -257,6 +257,7 @@ fi fetch_main () { reflist="$1" refs= + rref= for ref in $reflist do @@ -289,6 +290,10 @@ fetch_main () { if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + noepsv_opt="--disable-epsv" + fi max_depth=5 depth=0 head="ref: $remote_name" @@ -300,7 +305,7 @@ fetch_main () { $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; print "$u"; ' "$head") - head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") + head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted") depth=$( expr \( $depth + 1 \) ) done expr "z$head" : "z$_x40\$" >/dev/null || diff --git a/git-ls-remote.sh b/git-ls-remote.sh index 2c0b52122..0f88953f2 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -53,6 +53,10 @@ http://* | https://* | ftp://* ) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + curl_extra_args="${curl_extra_args} --disable-epsv" + fi curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" || echo "failed slurping" ;; diff --git a/git-merge-recursive.py b/git-merge-recursive-old.py index 4039435ce..4039435ce 100755 --- a/git-merge-recursive.py +++ b/git-merge-recursive-old.py diff --git a/git-merge.sh b/git-merge.sh index d049e1643..5b34b4de9 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,21 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> < LF=' ' -all_strategies='recursive recur octopus resolve stupid ours' -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - default_twohead_strategies=recursive ;; -?*) - default_twohead_strategies=recur ;; -esac +all_strategies='recur recursive recursive-old octopus resolve stupid ours' +default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='recur resolve octopus stupid ours' - default_twohead_strategies='resolve' + all_strategies='recur recursive resolve octopus stupid ours' fi dropsave() { @@ -122,10 +116,6 @@ do strategy="$2" shift ;; esac - case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in - recursive,?*) - strategy=recur ;; - esac case " $all_strategies " in *" $strategy "*) use_strategies="$use_strategies$strategy " ;; diff --git a/git-rebase.sh b/git-rebase.sh index 20f74d416..a7373c053 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,13 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - strategy=recursive ;; -?*) - strategy=recur ;; -esac - +strategy=recursive do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -206,11 +200,6 @@ do shift done -case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in -recursive,?*) - strategy=recur ;; -esac - # Make sure we do not have .dotest if test -z "$do_merge" then @@ -303,11 +292,11 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" +if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" then - die 'The recursive merge strategy currently relies on Python, + die 'The recursive-old merge strategy is written in Python, which this installation of git was not configured with. Please consider -a different merge strategy (e.g. octopus, resolve, stupid, ours) +a different merge strategy (e.g. recursive, resolve, or stupid) or install Python and git with Python support.' fi diff --git a/git-repack.sh b/git-repack.sh index b525fc5df..f2c9071d1 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -4,6 +4,7 @@ # USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' +SUBDIRECTORY_OK='Yes' . git-sh-setup no_update_info= all_into_one= remove_redundant= @@ -32,12 +33,10 @@ trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15 # There will be more repacking strategies to come... case ",$all_into_one," in ,,) - rev_list='--unpacked' - pack_objects='--incremental' + args='--unpacked --incremental' ;; ,t,) - rev_list= - pack_objects= + args= # Redundancy check in all-into-one case is trivial. existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \ @@ -45,11 +44,8 @@ case ",$all_into_one," in ;; esac -pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" -name=$( { git-rev-list --objects --all $rev_list || - echo "git-rev-list died with exit code $?" - } | - git-pack-objects --non-empty $pack_objects "$PACKTMP") || +args="$args $local $quiet $no_reuse_delta$extra" +name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") || exit 1 if [ -z "$name" ]; then echo Nothing new to pack. diff --git a/git-svn.perl b/git-svn.perl index 017f45ac9..f5c7d4634 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1247,6 +1247,7 @@ sub assert_svn_wc_clean { } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); + @status = grep(!/^X/,@status) if $_no_ignore_ext; if (scalar @status) { print STDERR "Tree ($SVN_WC) is not clean:\n"; print STDERR $_ foreach @status; diff --git a/git-svnimport.perl b/git-svnimport.perl index ed628974d..988514e29 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F); sub usage() { print STDERR <<END; @@ -39,12 +39,12 @@ Usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] - [-m] [-M regex] [-A author_file] [-S] [SVN_URL] + [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL] END exit(1); } -getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:Suv") or usage(); +getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; @@ -548,8 +548,12 @@ sub commit { $committer_name = $committer_email = $author; } - if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) { ($author_name, $author_email) = ($1, $2); + print "Author from From: $1 <$2>\n" if ($opt_v);; + } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);; } else { $author_name = $committer_name; $author_email = $committer_email; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a3c3e7471..44991b153 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -664,7 +664,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => $long}, + -title => decode("utf8", $long, Encode::FB_DEFAULT)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, @@ -138,16 +138,13 @@ void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; - if (opt->fixed) - return; - - /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - compile_regexp(p, opt); + if (!opt->fixed) + compile_regexp(p, opt); break; default: opt->extended = 1; @@ -167,6 +164,46 @@ void compile_grep_patterns(struct grep_opt *opt) die("incomplete pattern expression: %s", p->pattern); } +static void free_pattern_expr(struct grep_expr *x) +{ + switch (x->node) { + case GREP_NODE_ATOM: + break; + case GREP_NODE_NOT: + free_pattern_expr(x->u.unary); + break; + case GREP_NODE_AND: + case GREP_NODE_OR: + free_pattern_expr(x->u.binary.left); + free_pattern_expr(x->u.binary.right); + break; + } + free(x); +} + +void free_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p, *n; + + for (p = opt->pattern_list; p; p = n) { + n = p->next; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + regfree(&p->regexp); + break; + default: + break; + } + free(p); + } + + if (!opt->extended) + return; + free_pattern_expr(opt->pattern_expression); +} + static char *end_of_line(char *cp, unsigned long *left) { unsigned long l = *left; @@ -439,6 +476,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long lno++; } + free(prev); + if (opt->status_only) return 0; if (opt->unmatch_name_only) { @@ -73,6 +73,7 @@ struct grep_opt { extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); extern void compile_grep_patterns(struct grep_opt *opt); +extern void free_grep_patterns(struct grep_opt *opt); extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); #endif @@ -23,6 +23,7 @@ char *ssl_capath = NULL; char *ssl_cainfo = NULL; long curl_low_speed_limit = -1; long curl_low_speed_time = -1; +int curl_ftp_no_epsv = 0; struct curl_slist *pragma_header; @@ -155,6 +156,11 @@ static int http_options(const char *var, const char *value) return 0; } + if (!strcmp("http.noepsv", var)) { + curl_ftp_no_epsv = git_config_bool(var, value); + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value); } @@ -196,6 +202,9 @@ static CURL* get_curl_handle(void) curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT); + if (curl_ftp_no_epsv) + curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); + return result; } @@ -251,6 +260,9 @@ void http_init(void) max_requests = DEFAULT_MAX_REQUESTS; #endif + if (getenv("GIT_CURL_FTP_NO_EPSV")) + curl_ftp_no_epsv = 1; + #ifndef NO_CURL_EASY_DUPHANDLE curl_default = get_curl_handle(); #endif diff --git a/interpolate.c b/interpolate.c index 4570c123d..5d9d1889f 100644 --- a/interpolate.c +++ b/interpolate.c @@ -4,9 +4,35 @@ #include <string.h> +#include "git-compat-util.h" #include "interpolate.h" +void interp_set_entry(struct interp *table, int slot, const char *value) +{ + char *oldval = table[slot].value; + char *newval = NULL; + + if (oldval) + free(oldval); + + if (value) + newval = xstrdup(value); + + table[slot].value = newval; +} + + +void interp_clear_table(struct interp *table, int ninterps) +{ + int i; + + for (i = 0; i < ninterps; i++) { + interp_set_entry(table, i, NULL); + } +} + + /* * Convert a NUL-terminated string in buffer orig * into the supplied buffer, result, whose length is reslen, diff --git a/interpolate.h b/interpolate.h index d16f9244f..190a180b5 100644 --- a/interpolate.h +++ b/interpolate.h @@ -16,6 +16,9 @@ struct interp { char *value; }; +extern void interp_set_entry(struct interp *table, int slot, const char *value); +extern void interp_clear_table(struct interp *table, int ninterps); + extern int interpolate(char *result, int reslen, const char *orig, const struct interp *interps, int ninterps); diff --git a/read-cache.c b/read-cache.c index 20c9d494a..97c38670b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -347,11 +347,13 @@ int add_file_to_index(const char *path, int verbose) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (pos >= 0) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, 1)) diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5b04efc89..6907cbcd2 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -61,4 +61,16 @@ test_expect_success \ test -f .git/logs/refs/heads/g/h/i && diff expect .git/logs/refs/heads/g/h/i' +test_expect_success \ + 'git branch j/k should work after branch j has been deleted' \ + 'git-branch j && + git-branch -d j && + git-branch j/k' + +test_expect_success \ + 'git branch l should work after branch l/m has been deleted' \ + 'git-branch l/m && + git-branch -d l/m && + git-branch l' + test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 6cd05c3d9..c20e4c29f 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -19,4 +19,26 @@ test_expect_success \ 'Test that "git-add -- -q" works' \ 'touch -- -q && git-add -- -q' +test_expect_success \ + 'git-add: Test that executable bit is not used if core.filemode=0' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo1 && + chmod 755 xfoo1 && + git-add xfoo1 && + case "`git-ls-files --stage xfoo1`" in + 100644" "*xfoo1) echo ok;; + *) echo fail; git-ls-files --stage xfoo1; exit 1;; + esac' + +test_expect_success \ + 'git-update-index --add: Test that executable bit is not used...' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo2 && + chmod 755 xfoo2 && + git-update-index --add xfoo2 && + case "`git-ls-files --stage xfoo2`" in + 100644" "*xfoo2) echo ok;; + *) echo fail; git-ls-files --stage xfoo2; exit 1;; + esac' + test_done diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh new file mode 100755 index 000000000..b2131cdac --- /dev/null +++ b/t/t6001-rev-list-graft.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='Revision traversal vs grafts and path limiter' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir subdir && + echo >fileA fileA && + echo >subdir/fileB fileB && + git add fileA subdir/fileB && + git commit -a -m "Initial in one history." && + A0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified && + git commit -a -m "Second in one history." && + A1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified && + git commit -a -m "Third in one history." && + A2=`git rev-parse --verify HEAD` && + + rm -f .git/refs/heads/master .git/index && + + echo >fileA fileA again && + echo >subdir/fileB fileB again && + git add fileA subdir/fileB && + git commit -a -m "Initial in alternate history." && + B0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified in alternate history && + git commit -a -m "Second in alternate history." && + B1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified in alternate history && + git commit -a -m "Third in alternate history." && + B2=`git rev-parse --verify HEAD` && + : done +' + +check () { + type=$1 + shift + + arg= + which=arg + rm -f test.expect + for a + do + if test "z$a" = z-- + then + which=expect + child= + continue + fi + if test "$which" = arg + then + arg="$arg$a " + continue + fi + if test "$type" = basic + then + echo "$a" + else + if test "z$child" != z + then + echo "$child $a" + fi + child="$a" + fi + done >test.expect + if test "$type" != basic && test "z$child" != z + then + echo >>test.expect $child + fi + if test $type = basic + then + git rev-list $arg >test.actual + elif test $type = parents + then + git rev-list --parents $arg >test.actual + elif test $type = parents-raw + then + git rev-list --parents --pretty=raw $arg | + sed -n -e 's/^commit //p' >test.actual + fi + diff test.expect test.actual +} + +for type in basic parents parents-raw +do + test_expect_success 'without grafts' " + rm -f .git/info/grafts + check $type $B2 -- $B2 $B1 $B0 + " + + test_expect_success 'with grafts' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0 + " + + test_expect_success 'without grafts, with pathlimit' " + rm -f .git/info/grafts + check $type $B2 subdir -- $B2 $B0 + " + + test_expect_success 'with grafts, with pathlimit' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 subdir -- $B2 $B0 $A2 $A0 + " + +done +test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index b64e8b7d7..085d4a096 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -31,6 +31,15 @@ test_expect_success setup ' git checkout master ' +test_expect_success "checkout from non-existing branch" ' + + git checkout -b delete-me master && + rm .git/refs/heads/delete-me && + test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && + git checkout master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + test_expect_success "checkout with dirty tree without -m" ' fill 0 1 2 3 4 5 >one && diff --git a/t/test-lib.sh b/t/test-lib.sh index e75ad5faa..0fe271884 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -211,7 +211,7 @@ export PATH GIT_EXEC_PATH PYTHON=`sed -e '1{ s/^#!// q -}' ../git-merge-recursive` || { +}' ../git-merge-recursive-old` || { error "You haven't built things yet, have you?" } "$PYTHON" -c 'import subprocess' 2>/dev/null || { |