Commits

Anonymous committed 93dcab2 Merge

GIT 0.99.9k

This is not 1.0rc4 yet, but to push the recent fixes out.

Signed-off-by: Junio C Hamano <junkio@cox.net>

Comments (0)

Files changed (77)

 git-receive-pack
 git-relink
 git-repack
+git-repo-config
 git-request-pull
 git-reset
 git-resolve

Documentation/git-daemon.txt

 SYNOPSIS
 --------
 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
-             [--timeout=n] [--init-timeout=n] [directory...]
+             [--timeout=n] [--init-timeout=n] [--strict-paths] [directory...]
 
 DESCRIPTION
 -----------
 
 OPTIONS
 -------
+--strict-paths::
+	Match paths exactly (i.e. don't allow "/foo/repo" when the real path is
+	"/foo/repo.git" or "/foo/repo/.git") and don't do user-relative paths.
+	git-daemon will refuse to start when this option is enabled and no
+	whitelist is specified.
+
 --export-all::
 	Allow pulling from all directories that look like GIT repositories
-	(have the 'objects' subdirectory and a 'HEAD' file), even if they
+	(have the 'objects' and 'refs' subdirectories), even if they
 	do not have the 'git-daemon-export-ok' file.
 
 --inetd::
 --verbose::
 	Log details about the incoming connections and requested files.
 
+<directory>::
+	A directory to add to the whitelist of allowed directories. Unless
+	--strict-paths is specified this will also include subdirectories
+	of each named directory.
+
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org> and YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
+<yoshfuji@linux-ipv6.org> and the git-list <git@vger.kernel.org>
 
 Documentation
 --------------

Documentation/git-repo-config.txt

+git-repo-config(1)
+==================
+
+NAME
+----
+git-repo-config - Get and set options in .git/config.
+
+
+SYNOPSIS
+--------
+'git-repo-config' name [value [value_regex]]
+'git-repo-config' --replace-all name [value [value_regex]]
+'git-repo-config' --get name [value_regex]
+'git-repo-config' --get-all name [value_regex]
+'git-repo-config' --unset name [value_regex]
+'git-repo-config' --unset-all name [value_regex]
+
+DESCRIPTION
+-----------
+You can query/set/replace/unset options with this command. The name is
+actually the section and the key separated by a dot, and the value will be
+escaped.
+
+If you want to set/unset an option which can occor on multiple lines, you
+should provide a POSIX regex for the value. If you want to handle the lines
+*not* matching the regex, just prepend a single exlamation mark in front
+(see EXAMPLES).
+
+This command will fail if
+
+. .git/config is invalid,
+. .git/config can not be written to,
+. no section was provided,
+. the section or key is invalid,
+. you try to unset an option which does not exist, or
+. you try to unset/set an option for which multiple lines match.
+
+
+OPTIONS
+-------
+
+--replace-all::
+	Default behaviour is to replace at most one line. This replaces
+	all lines matching the key (and optionally the value_regex)
+
+--get::
+	Get the value for a given key (optionally filtered by a regex
+	matching the value).
+
+--get-all::
+	Like get, but does not fail if the number of values for the key
+	is not exactly one.
+
+--unset::
+	Remove the line matching the key from .git/config.
+
+--unset-all::
+	Remove all matching lines from .git/config.
+
+
+EXAMPLE
+-------
+
+Given a .git/config like this:
+
+	#
+	# This is the config file, and
+	# a '#' or ';' character indicates
+	# a comment
+	#
+
+	; core variables
+	[core]
+		; Don't trust file modes
+		filemode = false
+
+	; Our diff algorithm
+	[diff]
+		external = "/usr/local/bin/gnu-diff -u"
+		renames = true
+
+	; Proxy settings
+	[proxy]
+		command="ssh" for "ssh://kernel.org/"
+		command="proxy-command" for kernel.org
+		command="myprotocol-command" for "my://"
+		command=default-proxy ; for all the rest
+
+you can set the filemode to true with
+
+------------
+% git repo-config core.filemode true
+------------
+
+The hypothetic proxy command entries actually have a postfix to discern
+to what URL they apply. Here is how to change the entry for kernel.org
+to "ssh".
+
+------------
+% git repo-config proxy.command '"ssh" for kernel.org' 'for kernel.org$'
+------------
+
+This makes sure that only the key/value pair for kernel.org is replaced.
+
+To delete the entry for renames, do
+
+------------
+% git repo-config --unset diff.renames
+------------
+
+If you want to delete an entry for a multivar (like proxy.command above),
+you have to provide a regex matching the value of exactly one line.
+
+To query the value for a given key, do
+
+------------
+% git repo-config --get core.filemode
+------------
+
+or
+
+------------
+% git repo-config core.filemode
+------------
+
+or, to query a multivar:
+
+------------
+% git repo-config --get proxy.command "for kernel.org$"
+------------
+
+If you want to know all the values for a multivar, do:
+
+------------
+% git repo-config --get-all proxy.command
+------------
+
+If you like to live dangerous, you can replace *all* proxy.commands by a
+new one with
+
+------------
+% git repo-config --replace-all proxy.command ssh
+------------
+
+However, if you really only want to replace the line for the default proxy,
+i.e. the one without a "for ..." postfix, do something like this:
+
+------------
+% git repo-config proxy.command ssh '! for '
+------------
+
+To actually match only values with an exclamation mark, you have to
+
+------------
+% git repo-config section.key value '[!]'
+------------
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+

Documentation/git-reset.txt

 Sets the current head to the specified commit and optionally resets the
 index and working tree to match.
 
+This command is useful if you notice some small error in a recent
+commit (or set of commits) and want to redo that part without showing
+the undo in the history.
+
+If you want to undo a commit other than the latest on a branch,
+gitlink:git-revert[1] is your friend.
+
 OPTIONS
 -------
 --mixed::
-	Like --soft but reports what has not been updated. This is the
-	default action.
+	Resets the index but not the working tree (ie, the changed files
+	are preserved but not marked for commit) and reports what has not
+	been updated. This is the default action.
 
 --soft::
 	Does not touch the index file nor the working tree at all, but
-	requires them in a good order.
+	requires them to be in a good order. This leaves all your changed
+	files "Updated but not checked in", as gitlink:git-status[1] would
+	put it.
 
 --hard::
 	Matches the working tree and index to that of the tree being
-	switched to.
+	switched to. Any changes to tracked files in the working tree
+	since <commit-ish> are lost.
 
 <commit-ish>::
 	Commit to make the current HEAD.

Documentation/git.txt

 gitlink:git-read-tree[1]::
 	Reads tree information into the directory index
 
+gitlink:git-repo-config[1]::
+	Get and set options in .git/config.
+
 gitlink:git-unpack-objects[1]::
 	Unpacks objects out of a packed archive.
 

Documentation/howto/rebase-from-internal-branch.txt

     $ git checkout master
     $ cd Documentation; ed git.txt ...
     $ cd ..; git add Documentation/*.txt
-    $ git commit -s -v
-
-NOTE.  The -v flag to commit is a handy way to make sure that
-your additions are not introducing bogusly formatted lines.
+    $ git commit -s
 
 After the commit, the ancestry graph would look like this:
 
 Let's go back to the earlier picture, with different labels.
 
 You, as an individual developer, cloned upstream repository and
-amde a couple of commits on top of it.
+made a couple of commits on top of it.
 
                               *your "master" head
    upstream --> #1 --> #2 --> #3

Documentation/howto/update-hook-example.txt

+From: Junio C Hamano <junkio@cox.net>
+Subject: control access to branches.
+Date: Thu, 17 Nov 2005 23:55:32 -0800
+Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
+Abstract: An example hooks/update script is presented to
+ implement repository maintenance policies, such as who can push
+ into which branch and who can make a tag.
+
+When your developer runs git-push into the repository,
+git-receive-pack is run (either locally or over ssh) as that
+developer, so is hooks/update script.  Quoting from the relevant
+section of the documentation:
+
+    Before each ref is updated, if $GIT_DIR/hooks/update file exists
+    and executable, it is called with three parameters:
+
+           $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+    The refname parameter is relative to $GIT_DIR; e.g. for the
+    master head this is "refs/heads/master".  Two sha1 are the
+    object names for the refname before and after the update.  Note
+    that the hook is called before the refname is updated, so either
+    sha1-old is 0{40} (meaning there is no such ref yet), or it
+    should match what is recorded in refname.
+
+So if your policy is (1) always require fast-forward push
+(i.e. never allow "git-push repo +branch:branch"), (2) you
+have a list of users allowed to update each branch, and (3) you
+do not let tags to be overwritten, then:
+
+	#!/bin/sh
+	# This is a sample hooks/update script, written by JC
+        # in his e-mail buffer, so naturally it is not tested
+        # but hopefully would convey the idea.
+
+	umask 002
+        case "$1" in
+        refs/tags/*)
+		# No overwriting an existing tag
+        	if test -f "$GIT_DIR/$1"
+                then
+                	exit 1
+		fi
+		;;
+	refs/heads/*)
+        	# No rebasing or rewinding
+                if expr "$2" : '0*$' >/dev/null
+                then
+                	# creating a new branch
+			;
+		else
+                	# updating -- make sure it is a fast forward
+        		mb=`git-merge-base "$2" "$3"`
+			case "$mb,$2" in
+                        "$2,$mb")
+                        	;; # fast forward -- happy
+			*)
+                        	exit 1 ;; # unhappy
+			esac
+		fi
+		;;
+	*)
+		# No funny refs allowed
+		exit 1
+		;;
+	esac
+
+	# Is the user allowed to update it?
+	me=`id -u -n` ;# e.g. "junio"
+	while read head_pattern users
+        do
+		if expr "$1" : "$head_pattern" >/dev/null
+		then
+			case " $users " in
+			*" $me "*)
+                        	exit 0 ;; # happy
+			' * ')
+                        	exit 0 ;; # anybody
+			esac
+		fi
+	done
+	exit 1
+
+For the sake of simplicity, I assumed that you keep something
+like this in $GIT_DIR/info/allowed-pushers file:
+
+	refs/heads/master	junio
+        refs/heads/cogito$	pasky
+	refs/heads/bw/		linus
+        refs/heads/tmp/		*
+        refs/tags/v[0-9]*	junio
+
+With this, Linus can push or create "bw/penguin" or "bw/zebra"
+or "bw/panda" branches, Pasky can do only "cogito", and I can do
+master branch and make versioned tags.  And anybody can do
+tmp/blah branches.  This assumes all the users are in a single
+group that can write into $GIT_DIR/ and underneath.
+
+
+
+
+
+
+
+

Documentation/pull-fetch-param.txt

 	to name the remote repository:
 +
 ===============================================================
-- Rsync URL:		rsync://remote.machine/path/to/repo.git/
-- HTTP(s) URL:		http://remote.machine/path/to/repo.git/
-- git URL:		git://remote.machine/path/to/repo.git/
-- ssh URL:		remote.machine:/path/to/repo.git/
-- Local directory:	/path/to/repo.git/
+- rsync://host.xz/path/to/repo.git/
+- http://host.xz/path/to/repo.git/
+- https://host.xz/path/to/repo.git/
+- git://host.xz/path/to/repo.git/
+- git://host.xz/~user/path/to/repo.git/
+- ssh://host.xz/path/to/repo.git/
+- ssh://host.xz/~user/path/to/repo.git/
+- ssh://host.xz/~/path/to/repo.git
+===============================================================
++
+	SSH Is the default transport protocol and also supports an
+	scp-like syntax.  Both syntaxes support username expansion,
+	as does the native git protocol. The following three are
+	identical to the last three above, respectively:
++
+===============================================================
+- host.xz:/path/to/repo.git/
+- host.xz:~user/path/to/repo.git/
+- host.xz:path/to/repo.git
+===============================================================
++
+       To sync with a local directory, use:
+
+===============================================================
+- /path/to/repo.git/
 ===============================================================
 +
 In addition to the above, as a short-hand, the name of a

Documentation/tutorial.txt

    the "project lead" person does.
 
 3. Copy over the packed files from "project lead" public
-   repository to your public repository.
+   repository to your public repository, unless the "project
+   lead" repository lives on the same machine as yours.  In the
+   latter case, you can use `objects/info/alternates` file to
+   point at the repository you are borrowing from.
 
 4. Push into the public repository from your primary
    repository. Run `git repack`, and possibly `git prune` if the
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-cache perspective.
 
-GIT_VERSION = 0.99.9j
+GIT_VERSION = 0.99.9k
 
 # CFLAGS and LDFLAGS are for the users to override from the command line.
 
 SCRIPT_PYTHON = \
 	git-merge-recursive.py
 
+SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
+	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+	  $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
+	  gitk git-cherry-pick
+
 # The ones that do not have to link with lcrypto nor lz.
 SIMPLE_PROGRAMS = \
 	git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
 	git-unpack-objects$X git-update-index$X git-update-server-info$X \
 	git-upload-pack$X git-verify-pack$X git-write-tree$X \
 	git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
-	git-name-rev$X git-pack-redundant$X git-var$X $(SIMPLE_PROGRAMS)
+	git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X
+
+# what 'all' will build and 'install' will install.
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) git$X
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
 
 GIT_LIST_TWEAK =
 
+# Set paths to tools early so that they can be used for version tests.
+ifndef SHELL_PATH
+	SHELL_PATH = /bin/sh
+endif
+ifndef PERL_PATH
+	PERL_PATH = /usr/bin/perl
+endif
+ifndef PYTHON_PATH
+	PYTHON_PATH = /usr/bin/python
+endif
+
 PYMODULES = \
 	gitMergeCommon.py
 
 ifdef WITH_OWN_SUBPROCESS_PY
 	PYMODULES += compat/subprocess.py
+else
+	ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
+		PYMODULES += compat/subprocess.py
+	endif
 endif
 
 ifdef WITH_SEND_EMAIL
 		CURL_LIBCURL = -lcurl
 	endif
 	PROGRAMS += git-http-fetch$X
-	ifndef NO_EXPAT
-		EXPAT_LIBEXPAT = -lexpat
-		PROGRAMS += git-http-push$X
+	curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
+	ifeq "$(curl_check)" "070908"
+		ifndef NO_EXPAT
+			EXPAT_LIBEXPAT = -lexpat
+			PROGRAMS += git-http-push$X
+		endif
 	endif
 endif
 
-ifndef SHELL_PATH
-	SHELL_PATH = /bin/sh
-endif
-ifndef PERL_PATH
-	PERL_PATH = /usr/bin/perl
-endif
-ifndef PYTHON_PATH
-	PYTHON_PATH = /usr/bin/python
-endif
-
 ifndef NO_OPENSSL
 	LIB_OBJS += epoch.o
 	OPENSSL_LIBSSL = -lssl
 
 ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER))
 
-SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
-	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-	  $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-	  gitk git-cherry-pick
-
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
-all: $(PROGRAMS) $(SCRIPTS) git
+all: $(ALL_PROGRAMS)
 
 all:
 	$(MAKE) -C templates
 
 # Only use $(CFLAGS). We don't need anything else.
-git: git.c Makefile
+git$(X): git.c Makefile
 	$(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
-		$(CFLAGS) $@.c -o $@
+		$(CFLAGS) $< -o $@
 
-$(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh
+$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 	rm -f $@
 	sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
 	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIB_FILE) $(SIMPLE_LIB)
 
-git-http-fetch$X: fetch.o
+git-http-fetch$X: fetch.o http.o
+git-http-push$X: http.o
 git-local-fetch$X: fetch.o
 git-ssh-fetch$X: rsh.o fetch.o
 git-ssh-upload$X: rsh.o
 
 ### Installation rules
 
-install: $(PROGRAMS) $(SCRIPTS) git
+install: all
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
-	$(INSTALL) git $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(bindir))
 	$(MAKE) -C templates install
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 	$(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 ### Cleaning rules
 
 clean:
-	rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o git $(PROGRAMS) $(LIB_FILE)
+	rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o $(LIB_FILE)
+	rm -f $(PROGRAMS) $(SIMPLE_PROGRAMS) git$X
 	rm -f $(filter-out gitk,$(SCRIPTS))
 	rm -f *.spec *.pyc *.pyo
 	rm -rf $(GIT_TARNAME)
 
 int safe_create_leading_directories(char *path);
 char *safe_strncpy(char *, const char *, size_t);
+char *enter_repo(char *path, int strict);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size);
 unsigned long approxidate(const char *);
 
 extern int setup_ident(void);
-extern char *get_ident(const char *name, const char *email, const char *date_str);
-extern char *git_author_info(void);
-extern char *git_committer_info(void);
+extern const char *git_author_info(void);
+extern const char *git_committer_info(void);
 
 static inline void *xmalloc(size_t size)
 {
 extern int git_config(config_fn_t fn);
 extern int git_config_int(const char *, const char *);
 extern int git_config_bool(const char *, const char *);
+extern int git_config_set(const char *, const char *);
+extern int git_config_set_multivar(const char *, const char *, const char *, int);
 
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
-
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Johannes Schindelin, 2005
+ *
+ */
 #include "cache.h"
+#include <regex.h>
 
 #define MAXNAME (256)
 
 			return -1;
 		if (c == ']')
 			return baselen;
-		if (!isalnum(c))
+		if (!isalnum(c) && c != '.')
 			return -1;
 		if (baselen > MAXNAME / 2)
 			return -1;
 		return 0;
 	}
 
-	if (!strcmp(var, "diff.renamelimit")) {
-		diff_rename_limit_default = git_config_int(var, value);
-		return 0;
-	}
-
 	/* Add other config variables here.. */
 	return 0;
 }
 	}
 	return ret;
 }
+
+/*
+ * Find all the stuff for git_config_set() below.
+ */
+
+#define MAX_MATCHES 512
+
+static struct {
+	int baselen;
+	char* key;
+	int do_not_match;
+	regex_t* value_regex;
+	int multi_replace;
+	off_t offset[MAX_MATCHES];
+	enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
+	int seen;
+} store;
+
+static int matches(const char* key, const char* value)
+{
+	return !strcmp(key, store.key) &&
+		(store.value_regex == NULL ||
+		 (store.do_not_match ^
+		  !regexec(store.value_regex, value, 0, NULL, 0)));
+}
+
+static int store_aux(const char* key, const char* value)
+{
+	switch (store.state) {
+	case KEY_SEEN:
+		if (matches(key, value)) {
+			if (store.seen == 1 && store.multi_replace == 0) {
+				fprintf(stderr,
+					"Warning: %s has multiple values\n",
+					key);
+			} else if (store.seen >= MAX_MATCHES) {
+				fprintf(stderr, "Too many matches\n");
+				return 1;
+			}
+
+			store.offset[store.seen] = ftell(config_file);
+			store.seen++;
+		}
+		break;
+	case SECTION_SEEN:
+		if (strncmp(key, store.key, store.baselen+1)) {
+			store.state = SECTION_END_SEEN;
+			break;
+		} else
+			/* do not increment matches: this is no match */
+			store.offset[store.seen] = ftell(config_file);
+		/* fallthru */
+	case SECTION_END_SEEN:
+	case START:
+		if (matches(key, value)) {
+			store.offset[store.seen] = ftell(config_file);
+			store.state = KEY_SEEN;
+			store.seen++;
+		} else if(!strncmp(key, store.key, store.baselen))
+			store.state = SECTION_SEEN;
+	}
+	return 0;
+}
+
+static void store_write_section(int fd, const char* key)
+{
+	write(fd, "[", 1);
+	write(fd, key, store.baselen);
+	write(fd, "]\n", 2);
+}
+
+static void store_write_pair(int fd, const char* key, const char* value)
+{
+	int i;
+
+	write(fd, "\t", 1);
+	write(fd, key+store.baselen+1,
+		strlen(key+store.baselen+1));
+	write(fd, " = ", 3);
+	for (i = 0; value[i]; i++)
+		switch (value[i]) {
+		case '\n': write(fd, "\\n", 2); break;
+		case '\t': write(fd, "\\t", 2); break;
+		case '"': case '\\': write(fd, "\\", 1);
+		default: write(fd, value+i, 1);
+	}
+	write(fd, "\n", 1);
+}
+
+static int find_beginning_of_line(const char* contents, int size,
+	int offset_, int* found_bracket)
+{
+	int equal_offset = size, bracket_offset = size;
+	int offset;
+
+	for (offset = offset_-2; offset > 0 
+			&& contents[offset] != '\n'; offset--)
+		switch (contents[offset]) {
+			case '=': equal_offset = offset; break;
+			case ']': bracket_offset = offset; break;
+		}
+	if (bracket_offset < equal_offset) {
+		*found_bracket = 1;
+		offset = bracket_offset+1;
+	} else
+		offset++;
+
+	return offset;
+}
+
+int git_config_set(const char* key, const char* value)
+{
+	return git_config_set_multivar(key, value, NULL, 0);
+}
+
+/*
+ * If value==NULL, unset in (remove from) config,
+ * if value_regex!=NULL, disregard key/value pairs where value does not match.
+ * if multi_replace==0, nothing, or only one matching key/value is replaced,
+ *     else all matching key/values (regardless how many) are removed,
+ *     before the new pair is written.
+ *
+ * Returns 0 on success.
+ *
+ * This function does this:
+ *
+ * - it locks the config file by creating ".git/config.lock"
+ *
+ * - it then parses the config using store_aux() as validator to find
+ *   the position on the key/value pair to replace. If it is to be unset,
+ *   it must be found exactly once.
+ *
+ * - the config file is mmap()ed and the part before the match (if any) is
+ *   written to the lock file, then the changed part and the rest.
+ *
+ * - the config file is removed and the lock file rename()d to it.
+ *
+ */
+int git_config_set_multivar(const char* key, const char* value,
+	const char* value_regex, int multi_replace)
+{
+	int i;
+	struct stat st;
+	int fd;
+	char* config_filename = strdup(git_path("config"));
+	char* lock_file = strdup(git_path("config.lock"));
+	const char* last_dot = strrchr(key, '.');
+
+	/*
+	 * Since "key" actually contains the section name and the real
+	 * key name separated by a dot, we have to know where the dot is.
+	 */
+
+	if (last_dot == NULL) {	
+		fprintf(stderr, "key does not contain a section: %s\n", key);
+		return 2;
+	}
+	store.baselen = last_dot - key;
+
+	store.multi_replace = multi_replace;
+
+	/*
+	 * Validate the key and while at it, lower case it for matching.
+	 */
+	store.key = (char*)malloc(strlen(key)+1);
+	for (i = 0; key[i]; i++)
+		if (i != store.baselen &&
+				((!isalnum(key[i]) && key[i] != '.') ||
+				 (i == store.baselen+1 && !isalpha(key[i])))) {
+			fprintf(stderr, "invalid key: %s\n", key);
+			free(store.key);
+			return 1;
+		} else
+			store.key[i] = tolower(key[i]);
+	store.key[i] = 0;
+
+	/*
+	 * The lock_file serves a purpose in addition to locking: the new
+	 * contents of .git/config will be written into it.
+	 */
+	fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
+	if (fd < 0) {
+		fprintf(stderr, "could not lock config file\n");
+		free(store.key);
+		return -1;
+	}
+
+	/*
+	 * If .git/config does not exist yet, write a minimal version.
+	 */
+	if (stat(config_filename, &st)) {
+		static const char contents[] =
+			"#\n"
+			"# This is the config file\n"
+			"#\n"
+			"\n";
+
+		free(store.key);
+
+		/* if nothing to unset, error out */
+		if (value == NULL) {
+			close(fd);
+			unlink(lock_file);
+			return 5;
+		}
+
+		store.key = (char*)key;
+
+		write(fd, contents, sizeof(contents)-1);
+		store_write_section(fd, key);
+		store_write_pair(fd, key, value);
+	} else{
+		int in_fd;
+		char* contents;
+		int i, copy_begin, copy_end, new_line = 0;
+
+		if (value_regex == NULL)
+			store.value_regex = NULL;
+		else {
+			if (value_regex[0] == '!') {
+				store.do_not_match = 1;
+				value_regex++;
+			} else
+				store.do_not_match = 0;
+
+			store.value_regex = (regex_t*)malloc(sizeof(regex_t));
+			if (regcomp(store.value_regex, value_regex,
+					REG_EXTENDED)) {
+				fprintf(stderr, "Invalid pattern: %s",
+					value_regex);
+				free(store.value_regex);
+				return 6;
+			}
+		}
+
+		store.offset[0] = 0;
+		store.state = START;
+		store.seen = 0;
+
+		/*
+		 * After this, store.offset will contain the *end* offset
+		 * of the last match, or remain at 0 if no match was found.
+		 * As a side effect, we make sure to transform only a valid
+		 * existing config file.
+		 */
+		if (git_config(store_aux)) {
+			fprintf(stderr, "invalid config file\n");
+			free(store.key);
+			if (store.value_regex != NULL) {
+				regfree(store.value_regex);
+				free(store.value_regex);
+			}
+			return 3;
+		}
+
+		free(store.key);
+		if (store.value_regex != NULL) {
+			regfree(store.value_regex);
+			free(store.value_regex);
+		}
+
+		/* if nothing to unset, or too many matches, error out */
+		if ((store.seen == 0 && value == NULL) ||
+				(store.seen > 1 && multi_replace == 0)) {
+			close(fd);
+			unlink(lock_file);
+			return 5;
+		}
+
+		in_fd = open(config_filename, O_RDONLY, 0666);
+		contents = mmap(NULL, st.st_size, PROT_READ,
+			MAP_PRIVATE, in_fd, 0);
+		close(in_fd);
+
+		if (store.seen == 0)
+			store.seen = 1;
+
+		for (i = 0, copy_begin = 0; i < store.seen; i++) {
+			if (store.offset[i] == 0) {
+				store.offset[i] = copy_end = st.st_size;
+			} else if (store.state != KEY_SEEN) {
+				copy_end = store.offset[i];
+			} else
+				copy_end = find_beginning_of_line(
+					contents, st.st_size,
+					store.offset[i]-2, &new_line);
+
+			/* write the first part of the config */
+			if (copy_end > copy_begin) {
+				write(fd, contents + copy_begin,
+				copy_end - copy_begin);
+				if (new_line)
+					write(fd, "\n", 1);
+			}
+			copy_begin = store.offset[i];
+		}
+
+		/* write the pair (value == NULL means unset) */
+		if (value != NULL) {
+			if (store.state == START)
+				store_write_section(fd, key);
+			store_write_pair(fd, key, value);
+		}
+
+		/* write the rest of the config */
+		if (copy_begin < st.st_size)
+			write(fd, contents + copy_begin,
+				st.st_size - copy_begin);
+
+		munmap(contents, st.st_size);
+		unlink(config_filename);
+	}
+
+	close(fd);
+
+	if (rename(lock_file, config_filename) < 0) {
+		fprintf(stderr, "Could not rename the lock file?\n");
+		return 4;
+	}
+
+	return 0;
+}
+
+
 		memset(&sa, 0, sizeof sa);
 		sa.sin_family = he->h_addrtype;
 		sa.sin_port = htons(nport);
-		memcpy(&sa.sin_addr, ap, he->h_length);
+		memcpy(&sa.sin_addr, *ap, he->h_length);
 
 		if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
 			close(sockfd);
 
 #endif /* NO_IPV6 */
 
+static char *git_proxy_command = NULL;
+static const char *rhost_name = NULL;
+static int rhost_len;
+
+static int git_proxy_command_options(const char *var, const char *value)
+{
+	if (!strcmp(var, "core.gitproxy")) {
+		const char *for_pos;
+		int matchlen = -1;
+		int hostlen;
+
+		if (git_proxy_command)
+			return 0;
+		/* [core]
+		 * ;# matches www.kernel.org as well
+		 * gitproxy = netcatter-1 for kernel.org
+		 * gitproxy = netcatter-2 for sample.xz
+		 * gitproxy = netcatter-default
+		 */
+		for_pos = strstr(value, " for ");
+		if (!for_pos)
+			/* matches everybody */
+			matchlen = strlen(value);
+		else {
+			hostlen = strlen(for_pos + 5);
+			if (rhost_len < hostlen)
+				matchlen = -1;
+			else if (!strncmp(for_pos + 5,
+					  rhost_name + rhost_len - hostlen,
+					  hostlen) &&
+				 ((rhost_len == hostlen) ||
+				  rhost_name[rhost_len - hostlen -1] == '.'))
+				matchlen = for_pos - value;
+			else
+				matchlen = -1;
+		}
+		if (0 <= matchlen) {
+			/* core.gitproxy = none for kernel.org */
+			if (matchlen == 4 && 
+			    !memcmp(value, "none", 4))
+				matchlen = 0;
+			git_proxy_command = xmalloc(matchlen + 1);
+			memcpy(git_proxy_command, value, matchlen);
+			git_proxy_command[matchlen] = 0;
+		}
+		return 0;
+	}
+
+	return git_default_config(var, value);
+}
+
+static int git_use_proxy(const char *host)
+{
+	rhost_name = host;
+	rhost_len = strlen(host);
+	git_proxy_command = getenv("GIT_PROXY_COMMAND");
+	git_config(git_proxy_command_options);
+	rhost_name = NULL;
+	return (git_proxy_command && *git_proxy_command);
+}
+
+static int git_proxy_connect(int fd[2], const char *prog, char *host, char *path)
+{
+	char *port = STR(DEFAULT_GIT_PORT);
+	char *colon, *end;
+	int pipefd[2][2];
+	pid_t pid;
+
+	if (host[0] == '[') {
+		end = strchr(host + 1, ']');
+		if (end) {
+			*end = 0;
+			end++;
+			host++;
+		} else
+			end = host;
+	} else
+		end = host;
+	colon = strchr(end, ':');
+
+	if (colon) {
+		*colon = 0;
+		port = colon + 1;
+	}
+
+	if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+		die("unable to create pipe pair for communication");
+	pid = fork();
+	if (!pid) {
+		dup2(pipefd[1][0], 0);
+		dup2(pipefd[0][1], 1);
+		close(pipefd[0][0]);
+		close(pipefd[0][1]);
+		close(pipefd[1][0]);
+		close(pipefd[1][1]);
+		execlp(git_proxy_command, git_proxy_command, host, port, NULL);
+		die("exec failed");
+	}
+	fd[0] = pipefd[0][0];
+	fd[1] = pipefd[1][1];
+	close(pipefd[0][1]);
+	close(pipefd[1][0]);
+	packet_write(fd[1], "%s %s\n", prog, path);
+	return pid;
+}
+
 /*
  * Yeah, yeah, fixme. Need to pass in the heads etc.
  */
 int git_connect(int fd[2], char *url, const char *prog)
 {
 	char command[1024];
-	char *host, *path;
-	char *colon;
+	char *host, *path = url;
+	char *colon = NULL;
 	int pipefd[2][2];
 	pid_t pid;
-	enum protocol protocol;
-
-	host = NULL;
-	path = url;
-	colon = strchr(url, ':');
-	protocol = PROTO_LOCAL;
-	if (colon) {
-		*colon = 0;
+	enum protocol protocol = PROTO_LOCAL;
+
+	host = strstr(url, "://");
+	if(host) {
+		*host = '\0';
+		protocol = get_protocol(url);
+		host += 3;
+		path = strchr(host, '/');
+	}
+	else {
 		host = url;
-		path = colon+1;
-		protocol = PROTO_SSH;
-		if (!memcmp(path, "//", 2)) {
-			char *slash = strchr(path + 2, '/');
-			if (slash) {
-				int nr = slash - path - 2;
-				memmove(path, path+2, nr);
-				path[nr] = 0;
-				protocol = get_protocol(url);
-				host = path;
-				path = slash;
-			}
+		if ((colon = strchr(host, ':'))) {
+			protocol = PROTO_SSH;
+			*colon = '\0';
+			path = colon + 1;
 		}
 	}
 
-	if (protocol == PROTO_GIT)
+	if (!path || !*path)
+		die("No path specified. See 'man git-pull' for valid url syntax");
+
+	/*
+	 * null-terminate hostname and point path to ~ for URL's like this:
+	 *    ssh://host.xz/~user/repo
+	 */
+	if (protocol != PROTO_LOCAL && host != url) {
+		char *ptr = path;
+		if (path[1] == '~')
+			path++;
+		else
+			path = strdup(ptr);
+
+		*ptr = '\0';
+	}
+
+	if (protocol == PROTO_GIT) {
+		if (git_use_proxy(host))
+			return git_proxy_connect(fd, prog, host, path);
 		return git_tcp_connect(fd, prog, host, path);
+	}
 
 	if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
 		die("unable to create pipe pair for communication");
 
 static const char daemon_usage[] =
 "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
-"           [--timeout=n] [--init-timeout=n] [directory...]";
+"           [--timeout=n] [--init-timeout=n] [--strict-paths] [directory...]";
 
 /* List of acceptable pathname prefixes */
 static char **ok_paths = NULL;
+static int strict_paths = 0;
 
 /* If this is set, git-daemon-export-ok is not required */
 static int export_all_trees = 0;
 	va_end(params);
 }
 
-static int path_ok(const char *dir)
+static char *path_ok(char *dir)
 {
-	const char *p = dir;
-	char **pp;
-	int sl, ndot;
+	char *path = enter_repo(dir, strict_paths);
 
-	/* The pathname here should be an absolute path. */
-	if ( *p++ != '/' )
-		return 0;
-
-	sl = 1;  ndot = 0;
-
-	for (;;) {
-		if ( *p == '.' ) {
-			ndot++;
-		} else if ( *p == '\0' ) {
-			/* Reject "." and ".." at the end of the path */
-			if ( sl && ndot > 0 && ndot < 3 )
-				return 0;
-
-			/* Otherwise OK */
-			break;
-		} else if ( *p == '/' ) {
-			/* Refuse "", "." or ".." */
-			if ( sl && ndot < 3 )
-				return 0;
-			sl = 1;
-			ndot = 0;
-		} else {
-			sl = ndot = 0;
-		}
-		p++;
+	if (!path) {
+		logerror("'%s': unable to chdir or not a git archive", dir);
+		return NULL;
 	}
 
 	if ( ok_paths && *ok_paths ) {
-		int ok = 0;
-		int dirlen = strlen(dir);
-
+		char **pp;
+		int pathlen = strlen(path);
+
+		/* The validation is done on the paths after enter_repo
+		 * canonicalization, so whitelist should be written in
+		 * terms of real pathnames (i.e. after ~user is expanded
+		 * and symlinks resolved).
+		 */
 		for ( pp = ok_paths ; *pp ; pp++ ) {
 			int len = strlen(*pp);
-			if ( len <= dirlen &&
-			     !strncmp(*pp, dir, len) &&
-			     (dir[len] == '/' || dir[len] == '\0') ) {
-				ok = 1;
-				break;
-			}
+			if (len <= pathlen &&
+			    !memcmp(*pp, path, len) &&
+			    (path[len] == '\0' ||
+			     (!strict_paths && path[len] == '/')))
+				return path;
 		}
-
-		if ( !ok )
-			return 0; /* Path not in whitelist */
+	}
+	else {
+		/* be backwards compatible */
+		if (!strict_paths)
+			return path;
 	}
 
-	return 1;		/* Path acceptable */
+	logerror("'%s': not in whitelist", path);
+	return NULL;		/* Fallthrough. Deny by default */
 }
 
-static int set_dir(const char *dir)
+static int upload(char *dir)
 {
-	if (!path_ok(dir)) {
-		errno = EACCES;
-		return -1;
-	}
+	/* Timeout as string */
+	char timeout_buf[64];
+	const char *path;
+
+	loginfo("Request for '%s'", dir);
 
-	if ( chdir(dir) )
+	if (!(path = path_ok(dir)))
 		return -1;
 
 	/*
 	 * We want a readable HEAD, usable "objects" directory, and
 	 * a "git-daemon-export-ok" flag that says that the other side
 	 * is ok with us doing this.
+	 *
+	 * path_ok() uses enter_repo() and does whitelist checking.
+	 * We only need to make sure the repository is exported.
 	 */
+
 	if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
+		logerror("'%s': repository not exported.", path);
 		errno = EACCES;
 		return -1;
 	}
 
-	if (access("objects/", X_OK) || access("HEAD", R_OK)) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	/* If all this passed, we're OK */
-	return 0;
-}
-
-static int upload(char *dir)
-{
-	/* Try paths in this order */
-	static const char *paths[] = { "%s", "%s/.git", "%s.git", "%s.git/.git", NULL };
-	const char **pp;
-	/* Enough for the longest path above including final null */
-	int buflen = strlen(dir)+10;
-	char *dirbuf = xmalloc(buflen);
-	/* Timeout as string */
-	char timeout_buf[64];
-
-	loginfo("Request for '%s'", dir);
-
-	for ( pp = paths ; *pp ; pp++ ) {
-		snprintf(dirbuf, buflen, *pp, dir);
-		if ( !set_dir(dirbuf) )
-			break;
-	}
-
-	if ( !*pp ) {
-		logerror("Cannot set directory '%s': %s", dir, strerror(errno));
-		return -1;
-	}
-
 	/*
 	 * We'll ignore SIGTERM from now on, we have a
 	 * good client.
 	if (len && line[len-1] == '\n')
 		line[--len] = 0;
 
-	if (!strncmp("git-upload-pack /", line, 17))
+	if (!strncmp("git-upload-pack ", line, 16))
 		return upload(line+16);
 
 	logerror("Protocol error: '%s'", line);
 		return 0;
 	}
 
+	if (listen(sockfd, 5) < 0) {
+		close(sockfd);
+		return 0;
+	}
+
 	*socklist_p = xmalloc(sizeof(int));
 	**socklist_p = sockfd;
+	return 1;
 }
 
 #endif
 			init_timeout = atoi(arg+15);
 			continue;
 		}
+		if (!strcmp(arg, "--strict-paths")) {
+			strict_paths = 1;
+			continue;
+		}
 		if (!strcmp(arg, "--")) {
 			ok_paths = &argv[i+1];
 			break;
 	if (log_syslog)
 		openlog("git-daemon", 0, LOG_DAEMON);
 
+	if (strict_paths && (!ok_paths || !*ok_paths)) {
+		if (!inetd_mode)
+			die("git-daemon: option --strict-paths requires a whitelist");
+
+		logerror("option --strict-paths requires a whitelist");
+		exit (1);
+	}
+
 	if (inetd_mode) {
 		fclose(stderr); //FIXME: workaround
 		return execute();
+git-core (0.99.9k-0) unstable; urgency=low
+
+  * GIT 0.99.9k but not 1.0rc yet.
+
+ -- Junio C Hamano <junkio@cox.net>  Fri, 25 Nov 2005 16:33:11 -0800
+
 git-core (0.99.9j-0) unstable; urgency=low
 
   * GIT 0.99.9j aka 1.0rc3
 	const char *prefix = setup_git_directory();
 	int entries, i;
 
-	git_config(git_default_config);
+	git_config(git_diff_config);
 	diff_setup(&diff_options);
 	while (1 < argc && argv[1][0] == '-') {
 		if (!strcmp(argv[1], "--")) {
 	int allow_options = 1;
 	int i;
 
-	git_config(git_default_config);
+	git_config(git_diff_config);
 	diff_setup(&diff_options);
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 {
 	int stage1, stage2;
 
+	setup_git_directory();
+
+	git_config(git_diff_config);
 	read_cache();
 	diff_setup(&diff_options);
 	while (1 < ac && av[1][0] == '-') {
 	return retval;
 }
 
-static const char *generate_header(const char *commit, const char *parent, const char *msg, unsigned long len)
+static const char *generate_header(const char *commit, const char *parent, const char *msg)
 {
 	static char this_header[16384];
 	int offset;
+	unsigned long len;
 
 	if (!verbose_header)
 		return commit;
 
+	len = strlen(msg);
 	offset = sprintf(this_header, "%s%s (from %s)\n", header_prefix, commit, parent);
 	offset += pretty_print_commit(commit_format, msg, len, this_header + offset, sizeof(this_header) - offset);
 	return this_header;
 }
 
-static int diff_tree_commit(const unsigned char *commit, const char *name)
+static int diff_tree_commit(const unsigned char *commit_sha1)
 {
-	unsigned long size, offset;
-	char *buf = read_object_with_reference(commit, "commit", &size, NULL);
+	struct commit *commit;
+	struct commit_list *parents;
+	char name[50];
+	unsigned char sha1[20];
 
-	if (!buf)
+	sprintf(name, "%s^0", sha1_to_hex(commit_sha1));
+	if (get_sha1(name, sha1))
 		return -1;
-
-	if (!name) {
-		static char commit_name[60];
-		strcpy(commit_name, sha1_to_hex(commit));
-		name = commit_name;
-	}
-
+	name[40] = 0;
+	commit = lookup_commit(sha1);
+	
 	/* Root commit? */
-	if (show_root_diff && memcmp(buf + 46, "parent ", 7)) {
-		header = generate_header(name, "root", buf, size);
-		diff_root_tree(commit, "");
+	if (show_root_diff && !commit->parents) {
+		header = generate_header(name, "root", commit->buffer);
+		diff_root_tree(commit_sha1, "");
 	}
 
 	/* More than one parent? */
-	if (ignore_merges) {
-		if (!memcmp(buf + 46 + 48, "parent ", 7))
+	if (ignore_merges && commit->parents && commit->parents->next)
 			return 0;
-	}
 
-	offset = 46;
-	while (offset + 48 < size && !memcmp(buf + offset, "parent ", 7)) {
-		unsigned char parent[20];
-		if (get_sha1_hex(buf + offset + 7, parent))
-			return -1;
-		header = generate_header(name, sha1_to_hex(parent), buf, size);
-		diff_tree_sha1_top(parent, commit, "");
+	for (parents = commit->parents; parents; parents = parents->next) {
+		struct commit *parent = parents->item;
+		header = generate_header(name,
+					 sha1_to_hex(parent->object.sha1),
+					 commit->buffer);
+		diff_tree_sha1_top(parent->object.sha1, commit_sha1, "");
 		if (!header && verbose_header) {
 			header_prefix = "\ndiff-tree ";
 			/*
 			 * don't print the diffs.
 			 */
 		}
-		offset += 48;
 	}
-	free(buf);
 	return 0;
 }
 
 		return diff_tree_sha1_top(parent, commit, "");
 	}
 	line[40] = 0;
-	return diff_tree_commit(commit, line);
+	return diff_tree_commit(commit);
 }
 
 static const char diff_tree_usage[] =
 	unsigned char sha1[2][20];
 	const char *prefix = setup_git_directory();
 
-	git_config(git_default_config);
+	git_config(git_diff_config);
 	nr_sha1 = 0;
 	diff_setup(&diff_options);
 
 			usage(diff_tree_usage);
 		break;
 	case 1:
-		diff_tree_commit(sha1[0], NULL);
+		diff_tree_commit(sha1[0]);
 		break;
 	case 2:
 		diff_tree_sha1_top(sha1[0], sha1[1], "");
 
 int diff_rename_limit_default = -1;
 
+int git_diff_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "diff.renamelimit")) {
+		diff_rename_limit_default = git_config_int(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value);
+}
+
 static char *quote_one(const char *str)
 {
 	int needlen;
 
 static int parse_num(const char **cp_p)
 {
-	int num, scale, ch, cnt;
+	unsigned long num, scale;
+	int ch, dot;
 	const char *cp = *cp_p;
 
-	cnt = num = 0;
+	num = 0;
 	scale = 1;
-	while ('0' <= (ch = *cp) && ch <= '9') {
-		if (cnt++ < 5) {
-			/* We simply ignore more than 5 digits precision. */
-			scale *= 10;
-			num = num * 10 + ch - '0';
+	dot = 0;
+	for(;;) {
+		ch = *cp;
+		if ( !dot && ch == '.' ) {
+			scale = 1;
+			dot = 1;
+		} else if ( ch == '%' ) {
+			scale = dot ? scale*100 : 100;
+			cp++;	/* % is always at the end */
+			break;
+		} else if ( ch >= '0' && ch <= '9' ) {
+			if ( scale < 100000 ) {
+				scale *= 10;
+				num = (num*10) + (ch-'0');
+			}
+		} else {
+			break;
 		}
 		cp++;
 	}
 	/* user says num divided by scale and we say internally that
 	 * is MAX_SCORE * num / scale.
 	 */
-	return (MAX_SCORE * num / scale);
+	return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
 }
 
 int diff_scoreopt_parse(const char *opt)
 #define DIFF_SETUP_USE_CACHE		2
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
+extern int git_diff_config(const char *var, const char *value);
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int);
 extern int diff_setup_done(struct diff_options *);

diffcore-rename.c

 	if (rename_count == rename_dst_nr)
 		goto cleanup;
 
+	if (minimum_score == MAX_SCORE)
+		goto cleanup;
+
 	num_create = (rename_dst_nr - rename_count);
 	num_src = rename_src_nr;
 	mx = xmalloc(sizeof(*mx) * num_create * num_src);
 #!/bin/sh
 #
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>"
 ##
 ## git-am is supposed to be the newer and better tool for this job.
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]"

git-applypatch.sh

 ##	$3 - "info" file with Author, email and subject
 ##	$4 - optional file containing signoff to add
 ##
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 final=.dotest/final-commit
 ##
 #!/bin/sh
-. git-sh-setup || dir "Not a git archive"
+. git-sh-setup
 
 usage() {
     echo >&2 'usage: git bisect [start|bad|good|next|reset|visualize]
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $(basename $0)"' [-d <branch>] | [[-f] <branch> [start-point]]
 #!/bin/sh
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     die "usage: git checkout [-f] [-b <new_branch>] [<branch>] [<paths>...]"
 		# rescuing paths and is never meant to remove what
 		# is not in the named tree-ish.
 		git-ls-tree -r "$new" "$@" |
-		sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' |
 		git-update-index --index-info || exit $?
 	fi
 	git-checkout-index -f -u -- "$@"
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 usage="usage: $0 "'[-v] <upstream> [<head>]
 
 # Copyright (c) 2005 Linus Torvalds
 #
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
 	die 'git commit [-a] [-s] [-v | --no-verify]  [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [<path>...]'
 esac
 
 case "$all,$#" in
-t,*)
+t,0)
 	git-diff-files --name-only -z |
 	git-update-index --remove -z --stdin
 	;;
+t,*)
+	die "Cannot use -a and explicit files at the same time."
+	;;
 ,0)
 	;;
 *)

git-count-objects.sh

 #!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
 
 . git-sh-setup
 
+dc </dev/null 2>/dev/null || {
+	# This is not a real DC at all -- it just knows how
+	# this script feeds DC and does the computation itself.
+	dc () {
+		while read a b
+		do
+			case $a,$b in
+			0,)	acc=0 ;;
+			*,+)	acc=$(($acc + $a)) ;;
+			p,)	echo "$acc" ;;
+			esac
+		done
+	}
+}
+
 echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
 $({
     echo 0

git-cvsimport.perl

 	if ($opt_P) {
 	    exec("cat", $opt_P);
 	} else {
-	    exec("cvsps",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+	    exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
 	    die "Could not start cvsps: $!\n";
 	}
 }
 #!/bin/sh
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 . git-parse-remote
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"

git-format-patch.sh

 # Copyright (c) 2005 Junio C Hamano
 #
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
 # Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
 # familiar with that syntax.
 
-case "$#,$1" in
+case "$#,$1$2" in
 1,?*..?*)
 	# single "rev1..rev2"
 	;;
 		rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
 		;;
 	*)
-		usage
+		rev1="$revpair^"
+		rev2="$revpair"
 		;;
 	esac
 	git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||

git-lost-found.sh

 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 laf="$GIT_DIR/lost-found"
 rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit
 #!/bin/sh
 #
-. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..."

git-merge-one-file.sh

 		echo "Removing $4"
 	fi
 	if test -f "$4"; then
-		rm -f -- "$4"
+		rm -f -- "$4" &&
+		rmdir -p "$(expr "$4" : '\(.*\)/')" 2>/dev/null
 	fi &&
 		exec git-update-index --remove -- "$4"
 	;;

git-merge-recursive.py

 
             try:
                 createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
-            except: 
+            except OSError:
                 createDir = True
             
             if createDir:
         except OSError, e:
             if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
                 raise
+        try:
+            os.removedirs(os.path.dirname(path))
+        except OSError:
+            pass
 
 def uniquePath(path, branch):
     def fileExists(path):
 # Copyright (c) 2005 Junio C Hamano
 #
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 LF='
 '
     die "git-merge [-n] [--no-commit] [-s <strategy>]... <merge-message> <head> <remote>+"
 }
 
-# all_strategies='resolve recursive stupid octopus'
-
 all_strategies='recursive octopus resolve stupid ours'
-default_strategies='resolve octopus'
+default_strategies='recursive'
 use_strategies=
 
 dropsave() {
 	shift
 done
 
-case "$use_strategies" in
-'')
-	use_strategies=$default_strategies
-	;;
-esac
 test "$#" -le 2 && usage ;# we need at least two heads.
 
 merge_msg="$1"
 	;;
 esac
 
+case "$use_strategies" in
+'')
+	case "$#" in
+	1)
+		use_strategies="$default_strategies" ;;
+	*)
+		use_strategies=octopus ;;
+	esac		
+	;;
+esac
+
 # At this point, we need a real merge.  No matter what strategy
 # we use, it would operate on the index, possibly affecting the
 # working tree, and when resolved cleanly, have the desired tree
 	$bad = "bad source '$src'";
     }
 
+    $safesrc = quotemeta($src);
+    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+
     $overwritten{$dst} = 0;
     if (($bad eq "") && -e $dst) {
 	$bad = "destination '$dst' already exists";
-	if (-f $dst && $opt_f) {
-	    print "Warning: $bad; will overwrite!\n";
-	    $bad = "";
-	    $overwritten{$dst} = 1;
+	if ($opt_f) {
+	    # only files can overwrite each other: check both source and destination
+	    if (-f $dst && (scalar @srcfiles == 1)) {
+		print "Warning: $bad; will overwrite!\n";
+		$bad = "";
+		$overwritten{$dst} = 1;
+	    }
+	    else {
+		$bad = "Can not overwrite '$src' with '$dst'";
+	    }
 	}
     }
     
     }
 
     if ($bad eq "") {
-	$safesrc = quotemeta($src);
-	@srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
         if (scalar @srcfiles == 0) {
 	    $bad = "'$src' not under version control";
 	}
 
     push @deletedfiles, @srcfiles;
     if (scalar @srcfiles == 1) {
+	# $dst can be a directory with 1 file inside
 	if ($overwritten{$dst} ==1) {
-	    push @changedfiles, $dst;
+	    push @changedfiles, $dstfiles[0];
+
 	} else {
-	    push @addedfiles, $dst;
+	    push @addedfiles, $dstfiles[0];
 	}
     }
     else {
 #
 # Resolve two or more trees recorded in $GIT_DIR/FETCH_HEAD.
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     die "usage: git octopus"

git-parse-remote.sh

 #!/bin/sh
 
-. git-sh-setup
+# git-ls-remote could be called from outside a git managed repository;
+# this would fail in that case and would issue an error message.
+GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
 
 get_data_source () {
 	case "$1" in
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 dryrun=
 echo=
 #
 # Fetch one or more remote refs and merge it/them into the current HEAD.
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0"' [-n] [--no-commit] [--no-summary] [--help]
 #!/bin/sh
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     die "Usage: git push [--all] [--force] <repository> [<refspec>]"
 # Copyright (c) 2005 Junio C Hamano.