diff --git a/.gitignore b/.gitignore index 0dd7b9c7b4..8a6bd02d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ git-rebase git-receive-pack git-relink git-repack +git-repo-config git-request-pull git-reset git-resolve diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 3783858302..2a8f371ec9 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -8,7 +8,7 @@ git-daemon - A really simple server for git repositories. 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 ----------- @@ -29,9 +29,15 @@ This is ideally suited for read-only updates, ie pulling from git repositories. 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:: @@ -57,9 +63,15 @@ OPTIONS --verbose:: Log details about the incoming connections and requested files. +:: + 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 and YOSHIFUJI Hideaki +Written by Linus Torvalds , YOSHIFUJI Hideaki + and the git-list Documentation -------------- diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt new file mode 100644 index 0000000000..5eefe02437 --- /dev/null +++ b/Documentation/git-repo-config.txt @@ -0,0 +1,170 @@ +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 + +Documentation +-------------- +Documentation by Johannes Schindelin. + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 31ec2076e7..6af3a4fdb9 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -14,19 +14,30 @@ DESCRIPTION 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 are lost. :: Commit to make the current HEAD. diff --git a/Documentation/git.txt b/Documentation/git.txt index 338e5acb8b..a518249863 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -108,6 +108,9 @@ gitlink:git-prune-packed[1]:: 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. diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt index b2c021d917..c2d4a91c7c 100644 --- a/Documentation/howto/rebase-from-internal-branch.txt +++ b/Documentation/howto/rebase-from-internal-branch.txt @@ -40,10 +40,7 @@ So I started from master, made a bunch of edits, and committed: $ 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: @@ -98,7 +95,7 @@ to do cherrypicking using only the core GIT tools. 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 diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt new file mode 100644 index 0000000000..dacaf17c2e --- /dev/null +++ b/Documentation/howto/update-hook-example.txt @@ -0,0 +1,105 @@ +From: Junio C Hamano +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. + + + + + + + + diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index ddd5823df7..6413d525ce 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -5,11 +5,31 @@ 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 diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 03eb4216f3..e2dfb00ab1 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -1534,7 +1534,10 @@ on that project and has an own "public repository" goes like this: 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 diff --git a/Makefile b/Makefile index 2d8853d69e..adff025a8a 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ # 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. @@ -102,6 +102,11 @@ SCRIPT_PERL = \ 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 \ @@ -125,18 +130,36 @@ PROGRAMS = \ 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 @@ -242,22 +265,15 @@ ifndef NO_CURL 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 @@ -330,25 +346,20 @@ endif 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' \ @@ -387,7 +398,8 @@ $(SIMPLE_PROGRAMS) : git-%$X : %.o $(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 @@ -431,9 +443,9 @@ check: ### 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)) @@ -470,7 +482,8 @@ deb: dist ### 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) diff --git a/cache.h b/cache.h index 99afa2c3c4..6ac94c5a1f 100644 --- a/cache.h +++ b/cache.h @@ -203,6 +203,7 @@ int git_mkstemp(char *path, size_t n, const char *template); 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); @@ -262,9 +263,8 @@ void datestamp(char *buf, int bufsize); 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) { @@ -386,6 +386,8 @@ extern int git_default_config(const char *, const char *); 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]; diff --git a/config.c b/config.c index 915bb97523..5cc853508a 100644 --- a/config.c +++ b/config.c @@ -1,5 +1,12 @@ - +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + * Copyright (C) Johannes Schindelin, 2005 + * + */ #include "cache.h" +#include #define MAXNAME (256) @@ -136,7 +143,7 @@ static int get_base_var(char *name) return -1; if (c == ']') return baselen; - if (!isalnum(c)) + if (!isalnum(c) && c != '.') return -1; if (baselen > MAXNAME / 2) return -1; @@ -229,11 +236,6 @@ int git_default_config(const char *var, const char *value) 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; } @@ -252,3 +254,327 @@ int git_config(config_fn_t fn) } 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; +} + + diff --git a/connect.c b/connect.c index c2badc71aa..93f6f80d3e 100644 --- a/connect.c +++ b/connect.c @@ -427,7 +427,7 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path) 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); @@ -448,42 +448,162 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path) #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"); diff --git a/daemon.c b/daemon.c index 2b81152d71..91b96569cd 100644 --- a/daemon.c +++ b/daemon.c @@ -15,10 +15,11 @@ static int verbose; 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; @@ -81,69 +82,52 @@ static void loginfo(const char *err, ...) 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; /* @@ -152,45 +136,17 @@ static int set_dir(const char *dir) * 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. @@ -216,7 +172,7 @@ static int execute(void) 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); @@ -510,8 +466,14 @@ static int socksetup(int port, int **socklist_p) return 0; } + if (listen(sockfd, 5) < 0) { + close(sockfd); + return 0; + } + *socklist_p = xmalloc(sizeof(int)); **socklist_p = sockfd; + return 1; } #endif @@ -617,6 +579,10 @@ int main(int argc, char **argv) init_timeout = atoi(arg+15); continue; } + if (!strcmp(arg, "--strict-paths")) { + strict_paths = 1; + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -631,6 +597,14 @@ int main(int argc, char **argv) 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(); diff --git a/debian/changelog b/debian/changelog index 1eda61fe57..7356fe7780 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +git-core (0.99.9k-0) unstable; urgency=low + + * GIT 0.99.9k but not 1.0rc yet. + + -- Junio C Hamano Fri, 25 Nov 2005 16:33:11 -0800 + git-core (0.99.9j-0) unstable; urgency=low * GIT 0.99.9j aka 1.0rc3 diff --git a/diff-files.c b/diff-files.c index 17899390b8..38599b5b75 100644 --- a/diff-files.c +++ b/diff-files.c @@ -38,7 +38,7 @@ int main(int argc, const char **argv) 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], "--")) { diff --git a/diff-index.c b/diff-index.c index c9a9f4c74d..0054883a5e 100644 --- a/diff-index.c +++ b/diff-index.c @@ -180,7 +180,7 @@ int main(int argc, const char **argv) 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]; diff --git a/diff-stages.c b/diff-stages.c index 85170b21d6..9968d6ce1c 100644 --- a/diff-stages.c +++ b/diff-stages.c @@ -55,6 +55,9 @@ int main(int ac, const char **av) { int stage1, stage2; + setup_git_directory(); + + git_config(git_diff_config); read_cache(); diff_setup(&diff_options); while (1 < ac && av[1][0] == '-') { diff --git a/diff-tree.c b/diff-tree.c index 09d16ad661..d56d921585 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -69,52 +69,50 @@ static int diff_root_tree(const unsigned char *new, const char *base) 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 "; /* @@ -122,9 +120,7 @@ static int diff_tree_commit(const unsigned char *commit, const char *name) * don't print the diffs. */ } - offset += 48; } - free(buf); return 0; } @@ -147,7 +143,7 @@ static int diff_tree_stdin(char *line) 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[] = @@ -164,7 +160,7 @@ int main(int argc, const char **argv) 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); @@ -250,7 +246,7 @@ int main(int argc, const char **argv) 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], ""); diff --git a/diff.c b/diff.c index 0391e8c423..2e0797bf3e 100644 --- a/diff.c +++ b/diff.c @@ -15,6 +15,16 @@ static int use_size_cache; 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; @@ -838,16 +848,29 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) 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++; } @@ -856,7 +879,7 @@ static int parse_num(const char **cp_p) /* 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) diff --git a/diff.h b/diff.h index 9b2e1e62bb..32b4780173 100644 --- a/diff.h +++ b/diff.h @@ -77,6 +77,7 @@ extern 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 *); diff --git a/diffcore-rename.c b/diffcore-rename.c index 6a9d95d059..dba965c0b4 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -307,6 +307,9 @@ void diffcore_rename(struct diff_options *options) 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); diff --git a/git-am.sh b/git-am.sh index 8f073c90f6..660b3a4b61 100755 --- a/git-am.sh +++ b/git-am.sh @@ -1,7 +1,7 @@ #!/bin/sh # # -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { echo >&2 "usage: $0 [--signoff] [--dotest=] [--utf8] [--binary] [--3way] " diff --git a/git-applymbox.sh b/git-applymbox.sh index 6de6932879..24d4a8cb4e 100755 --- a/git-applymbox.sh +++ b/git-applymbox.sh @@ -18,7 +18,7 @@ ## ## 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/ | mbox) [signoff]" diff --git a/git-applypatch.sh b/git-applypatch.sh index 66fd19ae2d..f0549960fb 100755 --- a/git-applypatch.sh +++ b/git-applypatch.sh @@ -10,7 +10,7 @@ ## $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 ## diff --git a/git-bisect.sh b/git-bisect.sh index 1ab2f187dc..d92993b94e 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,5 +1,5 @@ #!/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] diff --git a/git-branch.sh b/git-branch.sh index 2594518e9f..4cd5da16f7 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -1,6 +1,6 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { echo >&2 "usage: $(basename $0)"' [-d ] | [[-f] [start-point]] diff --git a/git-checkout.sh b/git-checkout.sh index 4c08f36b59..4cf30e2c05 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -1,5 +1,5 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { die "usage: git checkout [-f] [-b ] [] [...]" @@ -82,7 +82,6 @@ then # 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 -- "$@" diff --git a/git-cherry.sh b/git-cherry.sh index aad2e6171f..867522b37f 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -. git-sh-setup || die "Not a git archive." +. git-sh-setup usage="usage: $0 "'[-v] [] diff --git a/git-commit.sh b/git-commit.sh index 41955e8e64..3d250ec853 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -3,7 +3,7 @@ # 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 | -F | (-C|-c) ] [-e] [...]' @@ -92,10 +92,13 @@ tt*) 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) ;; *) diff --git a/git-count-objects.sh b/git-count-objects.sh index 843d2fd9f2..d6e9a3221f 100755 --- a/git-count-objects.sh +++ b/git-count-objects.sh @@ -1,7 +1,25 @@ #!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# . git-sh-setup +dc /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 diff --git a/git-cvsimport.perl b/git-cvsimport.perl index efe193439b..08a890c2bb 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -502,7 +502,7 @@ unless($pid) { 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"; } } diff --git a/git-fetch.sh b/git-fetch.sh index 6586e773e6..14ea295113 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -1,6 +1,6 @@ #!/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" diff --git a/git-format-patch.sh b/git-format-patch.sh index 7ee5d328c0..bc56876531 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -3,7 +3,7 @@ # 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] @@ -99,7 +99,7 @@ filelist=$tmp-files # 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" ;; @@ -131,7 +131,8 @@ do rev2=`expr "$revpair" : '.*\.\.\(.*\)'` ;; *) - usage + rev1="$revpair^" + rev2="$revpair" ;; esac git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 || diff --git a/git-lost-found.sh b/git-lost-found.sh index 3892f52005..9dd7430018 100755 --- a/git-lost-found.sh +++ b/git-lost-found.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/git-ls-remote.sh b/git-ls-remote.sh index f0f0b07f6f..dc6a775a9b 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -1,6 +1,5 @@ #!/bin/sh # -. git-sh-setup usage () { echo >&2 "usage: $0 [--heads] [--tags] ..." diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index b08597de29..c3eca8b332 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -25,7 +25,8 @@ case "${1:-.}${2:-.}${3:-.}" in 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" ;; diff --git a/git-merge-recursive.py b/git-merge-recursive.py index d7d36aa7d1..0129233550 100755 --- a/git-merge-recursive.py +++ b/git-merge-recursive.py @@ -245,7 +245,7 @@ def updateFileExt(sha, mode, path, updateCache, updateWd): try: createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except: + except OSError: createDir = True if createDir: @@ -293,6 +293,10 @@ def removeFile(clean, path): 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): diff --git a/git-merge.sh b/git-merge.sh index 7f481e4caa..d352a3cf65 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano # -. git-sh-setup || die "Not a git archive" +. git-sh-setup LF=' ' @@ -12,10 +12,8 @@ usage () { die "git-merge [-n] [--no-commit] [-s ]... +" } -# all_strategies='resolve recursive stupid octopus' - all_strategies='recursive octopus resolve stupid ours' -default_strategies='resolve octopus' +default_strategies='recursive' use_strategies= dropsave() { @@ -90,11 +88,6 @@ do shift done -case "$use_strategies" in -'') - use_strategies=$default_strategies - ;; -esac test "$#" -le 2 && usage ;# we need at least two heads. merge_msg="$1" @@ -185,6 +178,17 @@ case "$#,$common,$no_commit" in ;; 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 diff --git a/git-mv.perl b/git-mv.perl index a21d87eea8..bf54c38413 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -103,13 +103,22 @@ while(scalar @srcArgs > 0) { $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'"; + } } } @@ -118,8 +127,6 @@ while(scalar @srcArgs > 0) { } if ($bad eq "") { - $safesrc = quotemeta($src); - @srcfiles = grep /^$safesrc(\/|$)/, @allfiles; if (scalar @srcfiles == 0) { $bad = "'$src' not under version control"; } @@ -166,10 +173,12 @@ while(scalar @srcs > 0) { 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 { diff --git a/git-octopus.sh b/git-octopus.sh index d2471af3c8..2edbf52c42 100755 --- a/git-octopus.sh +++ b/git-octopus.sh @@ -4,7 +4,7 @@ # # 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" diff --git a/git-parse-remote.sh b/git-parse-remote.sh index aea7b0e549..5f158c613f 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -1,6 +1,8 @@ #!/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 diff --git a/git-prune.sh b/git-prune.sh index c4de7f5f25..1fd8c731cd 100755 --- a/git-prune.sh +++ b/git-prune.sh @@ -1,6 +1,6 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup dryrun= echo= diff --git a/git-pull.sh b/git-pull.sh index 3b875ad438..3a139849fb 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -4,7 +4,7 @@ # # 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] diff --git a/git-push.sh b/git-push.sh index edc0b8317a..140c8f85d5 100755 --- a/git-push.sh +++ b/git-push.sh @@ -1,5 +1,5 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { die "Usage: git push [--all] [--force] []" diff --git a/git-rebase.sh b/git-rebase.sh index 5289762883..2bc3a12995 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -. git-sh-setup || die "Not a git archive." +. git-sh-setup # The other head is given other=$(git-rev-parse --verify "$1^0") || exit diff --git a/git-repack.sh b/git-repack.sh index 55a7b27dcd..430ddc5a70 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # -. git-sh-setup || die "Not a git archive" +. git-sh-setup no_update_info= all_into_one= remove_redundant= local= while case "$#" in 0) break ;; esac @@ -32,24 +32,20 @@ case ",$all_into_one," in rev_list= rev_parse='--all' pack_objects= + + # Redundancy check in all-into-one case is trivial. + existing=`cd "$PACKDIR" && \ + find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` ;; esac if [ "$local" ]; then pack_objects="$pack_objects --local" fi -name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) | +name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) 2>&1 | git-pack-objects --non-empty $pack_objects .tmp-pack) || exit 1 if [ -z "$name" ]; then echo Nothing new to pack. - if test "$remove_redundant" = t ; then - echo "Removing redundant packs." - sync - redundant=$(git-pack-redundant --all) - if test "$redundant" != "" ; then - echo $redundant | xargs rm - fi - fi exit 0 fi echo "Pack pack-$name created." @@ -62,23 +58,20 @@ exit if test "$remove_redundant" = t then - sync - if test "$all_into_one" = t + # We know $existing are all redundant only when + # all-into-one is used. + if test "$all_into_one" != '' && test "$existing" != '' then - cd "$PACKDIR" - existing=`find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` - for e in $existing - do + sync + ( cd "$PACKDIR" && + for e in $existing + do case "$e" in ./pack-$name.pack | ./pack-$name.idx) ;; - *) rm -f $e ;; + *) rm -f $e ;; esac - done - else - redundant=$(git-pack-redundant --all) - if test "$redundant" != "" ; then - echo $redundant | xargs rm - fi + done + ) fi fi diff --git a/git-reset.sh b/git-reset.sh index 2086d26d34..72ef303aed 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -1,5 +1,5 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { die 'Usage: git reset [--mixed | --soft | --hard] []' diff --git a/git-resolve.sh b/git-resolve.sh index 7d8fb54f95..fcc5ad7349 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -4,7 +4,7 @@ # # Resolve two trees. # -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { die "git-resolve " diff --git a/git-revert.sh b/git-revert.sh index 4154fe0d15..c1aebb159c 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -3,15 +3,17 @@ # Copyright (c) 2005 Linus Torvalds # Copyright (c) 2005 Junio C Hamano # -. git-sh-setup || die "Not a git archive" +. git-sh-setup case "$0" in *-revert* ) + test -t 0 && edit=-e me=revert ;; *-cherry-pick* ) + edit= me=cherry-pick ;; * ) - die "What are ou talking about?" ;; + die "What are you talking about?" ;; esac usage () { @@ -33,6 +35,12 @@ do --no-commi|--no-commit) no_commit=t ;; + -e|--e|--ed|--edi|--edit) + edit=-e + ;; + -n|--n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit) + edit= + ;; -r|--r|--re|--rep|--repl|--repla|--replay) replay=t ;; @@ -163,7 +171,7 @@ echo >&2 "Finished one $me." case "$no_commit" in '') - git-commit -n -F .msg + git-commit -n -F .msg $edit rm -f .msg ;; esac diff --git a/git-sh-setup.sh b/git-sh-setup.sh index dbb98842bf..b4f10224ba 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -1,10 +1,9 @@ #!/bin/sh # -# Set up GIT_DIR and GIT_OBJECT_DIRECTORY -# and return true if everything looks ok -# -: ${GIT_DIR=.git} -: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} +# This is included in commands that either have to be run from the toplevel +# of the repository, or with GIT_DIR environment variable properly. +# If the GIT_DIR does not look like the right correct git-repository, +# it dies. # Having this variable in your environment would break scripts because # you would cause "cd" to be be taken to unexpected places. If you @@ -12,14 +11,13 @@ # exporting it. unset CDPATH +: ${GIT_DIR=.git} +: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} + die() { echo >&2 "$@" exit 1 } -case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in -refs/*) : ;; -*) false ;; -esac && -[ -d "$GIT_DIR/refs" ] && -[ -d "$GIT_OBJECT_DIRECTORY/" ] +# Make sure we are in a valid repository of a vintage we understand. +GIT_DIR="$GIT_DIR" git-var GIT_AUTHOR_IDENT >/dev/null || exit diff --git a/git-status.sh b/git-status.sh index 837f334d87..b90ffc198d 100755 --- a/git-status.sh +++ b/git-status.sh @@ -2,7 +2,7 @@ # # Copyright (c) 2005 Linus Torvalds # -. git-sh-setup || die "Not a git archive" +GIT_DIR=$(git-rev-parse --git-dir) || exit report () { header="# diff --git a/git-tag.sh b/git-tag.sh index 1375945307..16efc5b70a 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -1,7 +1,7 @@ #!/bin/sh # Copyright (c) 2005 Linus Torvalds -. git-sh-setup || die "Not a git archive" +. git-sh-setup usage () { echo >&2 "Usage: git-tag [-a | -s | -u ] [-f | -d] [-m ] []" diff --git a/git-verify-tag.sh b/git-verify-tag.sh index ed4c893968..3c65f4a6b5 100755 --- a/git-verify-tag.sh +++ b/git-verify-tag.sh @@ -1,5 +1,5 @@ #!/bin/sh -. git-sh-setup || die "Not a git archive" +. git-sh-setup type="$(git-cat-file -t "$1" 2>/dev/null)" || die "$1: no such object." diff --git a/git.c b/git.c index bdd3f8d01c..0b10b6e781 100644 --- a/git.c +++ b/git.c @@ -273,7 +273,7 @@ int main(int argc, char **argv, char **envp) while (!strncmp(exec_path, "./", 2)) { exec_path += 2; while (*exec_path == '/') - *exec_path++; + exec_path++; } snprintf(git_command + len, sizeof(git_command) - len, "/%s", exec_path); diff --git a/gitk b/gitk index a9d37d9c73..3dd97e291e 100755 --- a/gitk +++ b/gitk @@ -60,7 +60,7 @@ proc getcommits {rargs} { proc getcommitlines {commfd} { global commits parents cdate children - global commitlisted phase commitinfo nextupdate + global commitlisted phase nextupdate global stopped redisplaying leftover set stuff [read $commfd] @@ -196,42 +196,44 @@ proc parsecommit {id contents listed olds} { incr ncleft($p) } } - foreach line [split $contents "\n"] { - if {$inhdr} { - if {$line == {}} { - set inhdr 0 - } else { - set tag [lindex $line 0] - if {$tag == "author"} { - set x [expr {[llength $line] - 2}] - set audate [lindex $line $x] - set auname [lrange $line 1 [expr {$x - 1}]] - } elseif {$tag == "committer"} { - set x [expr {[llength $line] - 2}] - set comdate [lindex $line $x] - set comname [lrange $line 1 [expr {$x - 1}]] - } - } - } else { - if {$comment == {}} { - set headline [string trim $line] - } else { - append comment "\n" - } - if {!$listed} { - # git-rev-list indents the comment by 4 spaces; - # if we got this via git-cat-file, add the indentation - append comment " " - } - append comment $line + set hdrend [string first "\n\n" $contents] + if {$hdrend < 0} { + # should never happen... + set hdrend [string length $contents] + } + set header [string range $contents 0 [expr {$hdrend - 1}]] + set comment [string range $contents [expr {$hdrend + 2}] end] + foreach line [split $header "\n"] { + set tag [lindex $line 0] + if {$tag == "author"} { + set audate [lindex $line end-1] + set auname [lrange $line 1 end-2] + } elseif {$tag == "committer"} { + set comdate [lindex $line end-1] + set comname [lrange $line 1 end-2] } } - if {$audate != {}} { - set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"] + set headline {} + # take the first line of the comment as the headline + set i [string first "\n" $comment] + if {$i >= 0} { + set headline [string trim [string range $comment 0 $i]] + } else { + set headline $comment + } + if {!$listed} { + # git-rev-list indents the comment by 4 spaces; + # if we got this via git-cat-file, add the indentation + set newcomment {} + foreach line [split $comment "\n"] { + append newcomment " " + append newcomment $line + append newcomment "\n" + } + set comment $newcomment } if {$comdate != {}} { set cdate($id) $comdate - set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"] } set commitinfo($id) [list $headline $auname $audate \ $comname $comdate $comment] @@ -239,77 +241,43 @@ proc parsecommit {id contents listed olds} { proc readrefs {} { global tagids idtags headids idheads tagcontents - - set tags [glob -nocomplain -types f [gitdir]/refs/tags/*] - foreach f $tags { - catch { - set fd [open $f r] - set line [read $fd] - if {[regexp {^[0-9a-f]{40}} $line id]} { - set direct [file tail $f] - set tagids($direct) $id - lappend idtags($id) $direct - set tagblob [exec git-cat-file tag $id] - set contents [split $tagblob "\n"] - set obj {} - set type {} - set tag {} - foreach l $contents { - if {$l == {}} break - switch -- [lindex $l 0] { - "object" {set obj [lindex $l 1]} - "type" {set type [lindex $l 1]} - "tag" {set tag [string range $l 4 end]} - } - } - if {$obj != {} && $type == "commit" && $tag != {}} { - set tagids($tag) $obj - lappend idtags($obj) $tag - set tagcontents($tag) $tagblob - } - } - close $fd - } - } - set heads [glob -nocomplain -types f [gitdir]/refs/heads/*] - foreach f $heads { - catch { - set fd [open $f r] - set line [read $fd 40] - if {[regexp {^[0-9a-f]{40}} $line id]} { - set head [file tail $f] - set headids($head) $line - lappend idheads($line) $head - } - close $fd - } - } - readotherrefs refs {} {tags heads} -} - -proc readotherrefs {base dname excl} { global otherrefids idotherrefs - set git [gitdir] - set files [glob -nocomplain -types f [file join $git $base *]] - foreach f $files { - catch { - set fd [open $f r] - set line [read $fd 40] - if {[regexp {^[0-9a-f]{40}} $line id]} { - set name "$dname[file tail $f]" - set otherrefids($name) $id - lappend idotherrefs($id) $name + set refd [open [list | git-ls-remote [gitdir]] r] + while {0 <= [set n [gets $refd line]]} { + if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \ + match id path]} { + continue + } + if {![regexp {^(tags|heads)/(.*)$} $path match type name]} { + set type others + set name $path + } + if {$type == "tags"} { + set tagids($name) $id + lappend idtags($id) $name + set obj {} + set type {} + set tag {} + catch { + set commit [exec git-rev-parse "$id^0"] + if {"$commit" != "$id"} { + set tagids($name) $commit + lappend idtags($commit) $name + } + } + catch { + set tagcontents($name) [exec git-cat-file tag "$id"] } - close $fd + } elseif { $type == "heads" } { + set headids($name) $id + lappend idheads($id) $name + } else { + set otherrefids($name) $id + lappend idotherrefs($id) $name } } - set dirs [glob -nocomplain -types d [file join $git $base *]] - foreach d $dirs { - set dir [file tail $d] - if {[lsearch -exact $excl $dir] >= 0} continue - readotherrefs [file join $base $dir] "$dname$dir/" {} - } + close $refd } proc error_popup msg { @@ -683,7 +651,7 @@ Use and redistribute under the terms of the GNU General Public License} \ } proc assigncolor {id} { - global commitinfo colormap commcolors colors nextcolor + global colormap commcolors colors nextcolor global parents nparents children nchildren global cornercrossings crossings @@ -783,10 +751,12 @@ proc bindline {t id} { $canv bind $t "lineclick %x %y $id 1" } -proc drawlines {id xtra} { +proc drawlines {id xtra delold} { global mainline mainlinearrow sidelines lthickness colormap canv - $canv delete lines.$id + if {$delold} { + $canv delete lines.$id + } if {[info exists mainline($id)]} { set t [$canv create line $mainline($id) \ -width [expr {($xtra + 1) * $lthickness}] \ @@ -858,7 +828,7 @@ proc drawcommitline {level} { set mainline($id) [trimdiagstart $mainline($id)] } } - drawlines $id 0 + drawlines $id 0 0 set orad [expr {$linespc / 3}] set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \ [expr $x + $orad - 1] [expr $y1 + $orad - 1] \ @@ -878,6 +848,7 @@ proc drawcommitline {level} { set headline [lindex $commitinfo($id) 0] set name [lindex $commitinfo($id) 1] set date [lindex $commitinfo($id) 2] + set date [formatdate $date] set linehtag($lineno) [$canv create text $xt $y1 -anchor w \ -text $headline -font $mainfont ] $canv bind $linehtag($lineno) "rowmenu %X %Y $id" @@ -1446,8 +1417,8 @@ proc decidenext {{noread 0}} { } proc drawcommit {id} { - global phase todo nchildren datemode nextupdate - global numcommits ncmupdate displayorder todo onscreen + global phase todo nchildren datemode nextupdate revlistorder + global numcommits ncmupdate displayorder todo onscreen parents if {$phase != "incrdraw"} { set phase incrdraw @@ -1459,19 +1430,29 @@ proc drawcommit {id} { lappend todo $id set onscreen($id) 0 } - set level [decidenext 1] - if {$level == {} || $id != [lindex $todo $level]} { - return - } - while 1 { - lappend displayorder [lindex $todo $level] - if {[updatetodo $level $datemode]} { - set level [decidenext 1] - if {$level == {}} break + if {$revlistorder} { + set level [lsearch -exact $todo $id] + if {$level < 0} { + error_popup "oops, $id isn't in todo" + return + } + lappend displayorder $id + updatetodo $level 0 + } else { + set level [decidenext 1] + if {$level == {} || $id != [lindex $todo $level]} { + return } - set id [lindex $todo $level] - if {![info exists commitlisted($id)]} { - break + while 1 { + lappend displayorder [lindex $todo $level] + if {[updatetodo $level $datemode]} { + set level [decidenext 1] + if {$level == {}} break + } + set id [lindex $todo $level] + if {![info exists commitlisted($id)]} { + break + } } } drawmore 1 @@ -1523,7 +1504,7 @@ proc drawrest {} { global phase stopped redisplaying selectedline global datemode todo displayorder global numcommits ncmupdate - global nextupdate startmsecs + global nextupdate startmsecs revlistorder set level [decidenext] if {$level >= 0} { @@ -1536,8 +1517,8 @@ proc drawrest {} { if {$level < 0} break } } - drawmore 0 } + drawmore 0 set phase {} set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs] #puts "overall $drawmsecs ms for $numcommits commits" @@ -2146,8 +2127,10 @@ proc selectline {l isnew} { $ctext mark set fmark.0 0.0 $ctext mark gravity fmark.0 left set info $commitinfo($id) - $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n" - $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n" + set date [formatdate [lindex $info 2]] + $ctext insert end "Author: [lindex $info 1] $date\n" + set date [formatdate [lindex $info 4]] + $ctext insert end "Committer: [lindex $info 3] $date\n" if {[info exists idtags($id)]} { $ctext insert end "Tags:" foreach tag $idtags($id) { @@ -2805,8 +2788,7 @@ proc gettreediffs {ids} { set treepending $ids set treediff {} set id [lindex $ids 0] - set p [lindex $ids 1] - if [catch {set gdtf [open "|git-diff-tree -r $id" r]}] return + if [catch {set gdtf [open "|git-diff-tree --no-commit-id -r $id" r]}] return fconfigure $gdtf -blocking 0 fileevent $gdtf readable [list gettreediffline $gdtf $ids] } @@ -2840,9 +2822,8 @@ proc getblobdiffs {ids} { global difffilestart nextupdate diffinhdr treediffs set id [lindex $ids 0] - set p [lindex $ids 1] set env(GIT_DIFF_OPTS) $diffopts - set cmd [list | git-diff-tree -r -p -C $id] + set cmd [list | git-diff-tree --no-commit-id -r -p -C $id] if {[catch {set bdf [open $cmd r]} err]} { puts "error getting diffs: $err" return @@ -3143,7 +3124,7 @@ proc linehover {} { set t [$canv create rectangle $x0 $y0 $x1 $y1 \ -fill \#ffff80 -outline black -width 1 -tags hover] $canv raise $t - set t [$canv create text $x $y -anchor nw -text $text -tags hover] + set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont] $canv raise $t } @@ -3178,7 +3159,7 @@ proc clickisonarrow {id y} { } proc arrowjump {id dirn y} { - global mainline sidelines canv + global mainline sidelines canv canv2 canv3 set yt {} if {$dirn eq "down"} { @@ -3216,6 +3197,8 @@ proc arrowjump {id dirn y} { set yfrac 0 } $canv yview moveto $yfrac + $canv2 yview moveto $yfrac + $canv3 yview moveto $yfrac } proc lineclick {x y id isnew} { @@ -3226,7 +3209,7 @@ proc lineclick {x y id isnew} { normalline $canv delete hover # draw this line thicker than normal - drawlines $id 1 + drawlines $id 1 1 set thickerline $id if {$isnew} { set ymax [lindex [$canv cget -scrollregion] 3] @@ -3255,7 +3238,8 @@ proc lineclick {x y id isnew} { set info $commitinfo($id) $ctext insert end "\n\t[lindex $info 0]\n" $ctext insert end "\tAuthor:\t[lindex $info 1]\n" - $ctext insert end "\tDate:\t[lindex $info 2]\n" + set date [formatdate [lindex $info 2]] + $ctext insert end "\tDate:\t$date\n" if {[info exists children($id)]} { $ctext insert end "\nChildren:" set i 0 @@ -3267,7 +3251,8 @@ proc lineclick {x y id isnew} { $ctext tag bind link$i <1> [list selbyid $child] $ctext insert end "\n\t[lindex $info 0]" $ctext insert end "\n\tAuthor:\t[lindex $info 1]" - $ctext insert end "\n\tDate:\t[lindex $info 2]\n" + set date [formatdate [lindex $info 2]] + $ctext insert end "\n\tDate:\t$date\n" } } $ctext conf -state disabled @@ -3278,7 +3263,7 @@ proc lineclick {x y id isnew} { proc normalline {} { global thickerline if {[info exists thickerline]} { - drawlines $thickerline 0 + drawlines $thickerline 0 1 unset thickerline } } @@ -3650,6 +3635,23 @@ proc doquit {} { destroy . } +proc formatdate {d} { + global hours nhours tfd fastdate + + if {!$fastdate} { + return [clock format $d -format "%Y-%m-%d %H:%M:%S"] + } + set hr [expr {$d / 3600}] + set ms [expr {$d % 3600}] + if {![info exists hours($hr)]} { + set hours($hr) [clock format $d -format "%Y-%m-%d %H"] + set nhours($hr) 0 + } + incr nhours($hr) + set minsec [format "%.2d:%.2d" [expr {$ms/60}] [expr {$ms%60}]] + return "$hours($hr):$minsec" +} + # defaults... set datemode 0 set boldnames 0 @@ -3662,6 +3664,8 @@ set findmergefiles 0 set gaudydiff 0 set maxgraphpct 50 set maxwidth 16 +set revlistorder 0 +set fastdate 0 set colors {green red blue magenta darkgrey brown orange} @@ -3678,6 +3682,7 @@ foreach arg $argv { "^$" { } "^-b" { set boldnames 1 } "^-d" { set datemode 1 } + "^-r" { set revlistorder 1 } default { lappend revtreeargs $arg } diff --git a/http-fetch.c b/http-fetch.c index 21cc1b960c..435317342b 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -2,44 +2,14 @@ #include "commit.h" #include "pack.h" #include "fetch.h" - -#include -#include - -#if LIBCURL_VERSION_NUM >= 0x070908 -#define USE_CURL_MULTI -#define DEFAULT_MAX_REQUESTS 5 -#endif - -#if LIBCURL_VERSION_NUM < 0x070704 -#define curl_global_cleanup() do { /* nothing */ } while(0) -#endif -#if LIBCURL_VERSION_NUM < 0x070800 -#define curl_global_init(a) do { /* nothing */ } while(0) -#endif - -#if LIBCURL_VERSION_NUM < 0x070c04 -#define NO_CURL_EASY_DUPHANDLE -#endif +#include "http.h" #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 static int got_alternates = -1; -static int active_requests = 0; -static int data_received; -#ifdef USE_CURL_MULTI -static int max_requests = -1; -static CURLM *curlm; -#endif -#ifndef NO_CURL_EASY_DUPHANDLE -static CURL *curl_default; -#endif -static struct curl_slist *pragma_header; static struct curl_slist *no_pragma_header; -static struct curl_slist *no_range_header; -static char curl_errorstr[CURL_ERROR_SIZE]; struct alt_base { @@ -51,14 +21,14 @@ struct alt_base static struct alt_base *alt = NULL; -enum transfer_state { +enum object_request_state { WAITING, ABORTED, ACTIVE, COMPLETE, }; -struct transfer_request +struct object_request { unsigned char sha1[20]; struct alt_base *repo; @@ -66,7 +36,7 @@ struct transfer_request char filename[PATH_MAX]; char tmpfile[PATH_MAX]; int local; - enum transfer_state state; + enum object_request_state state; CURLcode curl_result; char errorstr[CURL_ERROR_SIZE]; long http_code; @@ -76,23 +46,10 @@ struct transfer_request int zret; int rename; struct active_request_slot *slot; - struct transfer_request *next; -}; - -struct active_request_slot -{ - CURL *curl; - FILE *local; - int in_use; - int done; - CURLcode curl_result; - long http_code; - void *callback_data; - void (*callback_func)(void *data); - struct active_request_slot *next; + struct object_request *next; }; -struct alt_request { +struct alternates_request { char *base; char *url; struct buffer *buffer; @@ -100,120 +57,7 @@ struct alt_request { int http_specific; }; -static struct transfer_request *request_queue_head = NULL; -static struct active_request_slot *active_queue_head = NULL; - -static int curl_ssl_verify = -1; -static char *ssl_cert = NULL; -#if LIBCURL_VERSION_NUM >= 0x070902 -static char *ssl_key = NULL; -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 -static char *ssl_capath = NULL; -#endif -static char *ssl_cainfo = NULL; -static long curl_low_speed_limit = -1; -static long curl_low_speed_time = -1; - -struct buffer -{ - size_t posn; - size_t size; - void *buffer; -}; - -static int http_options(const char *var, const char *value) -{ - if (!strcmp("http.sslverify", var)) { - if (curl_ssl_verify == -1) { - curl_ssl_verify = git_config_bool(var, value); - } - return 0; - } - - if (!strcmp("http.sslcert", var)) { - if (ssl_cert == NULL) { - ssl_cert = xmalloc(strlen(value)+1); - strcpy(ssl_cert, value); - } - return 0; - } -#if LIBCURL_VERSION_NUM >= 0x070902 - if (!strcmp("http.sslkey", var)) { - if (ssl_key == NULL) { - ssl_key = xmalloc(strlen(value)+1); - strcpy(ssl_key, value); - } - return 0; - } -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - if (!strcmp("http.sslcapath", var)) { - if (ssl_capath == NULL) { - ssl_capath = xmalloc(strlen(value)+1); - strcpy(ssl_capath, value); - } - return 0; - } -#endif - if (!strcmp("http.sslcainfo", var)) { - if (ssl_cainfo == NULL) { - ssl_cainfo = xmalloc(strlen(value)+1); - strcpy(ssl_cainfo, value); - } - return 0; - } - -#ifdef USE_CURL_MULTI - if (!strcmp("http.maxrequests", var)) { - if (max_requests == -1) - max_requests = git_config_int(var, value); - return 0; - } -#endif - - if (!strcmp("http.lowspeedlimit", var)) { - if (curl_low_speed_limit == -1) - curl_low_speed_limit = (long)git_config_int(var, value); - return 0; - } - if (!strcmp("http.lowspeedtime", var)) { - if (curl_low_speed_time == -1) - curl_low_speed_time = (long)git_config_int(var, value); - return 0; - } - - /* Fall back on the default ones */ - return git_default_config(var, value); -} - -static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb, - struct buffer *buffer) -{ - size_t size = eltsize * nmemb; - if (size > buffer->size - buffer->posn) - size = buffer->size - buffer->posn; - memcpy(buffer->buffer + buffer->posn, ptr, size); - buffer->posn += size; - data_received++; - return size; -} - -static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize, - size_t nmemb, struct buffer *buffer) -{ - size_t size = eltsize * nmemb; - if (size > buffer->size - buffer->posn) { - buffer->size = buffer->size * 3 / 2; - if (buffer->size < buffer->posn + size) - buffer->size = buffer->posn + size; - buffer->buffer = xrealloc(buffer->buffer, buffer->size); - } - memcpy(buffer->buffer + buffer->posn, ptr, size); - buffer->posn += size; - data_received++; - return size; -} +static struct object_request *object_queue_head = NULL; static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, void *data) @@ -221,194 +65,35 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, unsigned char expn[4096]; size_t size = eltsize * nmemb; int posn = 0; - struct transfer_request *request = (struct transfer_request *)data; + struct object_request *obj_req = (struct object_request *)data; do { - ssize_t retval = write(request->local, + ssize_t retval = write(obj_req->local, ptr + posn, size - posn); if (retval < 0) return posn; posn += retval; } while (posn < size); - request->stream.avail_in = size; - request->stream.next_in = ptr; + obj_req->stream.avail_in = size; + obj_req->stream.next_in = ptr; do { - request->stream.next_out = expn; - request->stream.avail_out = sizeof(expn); - request->zret = inflate(&request->stream, Z_SYNC_FLUSH); - SHA1_Update(&request->c, expn, - sizeof(expn) - request->stream.avail_out); - } while (request->stream.avail_in && request->zret == Z_OK); + obj_req->stream.next_out = expn; + obj_req->stream.avail_out = sizeof(expn); + obj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH); + SHA1_Update(&obj_req->c, expn, + sizeof(expn) - obj_req->stream.avail_out); + } while (obj_req->stream.avail_in && obj_req->zret == Z_OK); data_received++; return size; } -#ifdef USE_CURL_MULTI -static void process_curl_messages(void); -static void process_request_queue(void); -#endif static void fetch_alternates(char *base); -static CURL* get_curl_handle(void) -{ - CURL* result = curl_easy_init(); - - curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify); -#if LIBCURL_VERSION_NUM >= 0x070907 - curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); -#endif - - if (ssl_cert != NULL) - curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); -#if LIBCURL_VERSION_NUM >= 0x070902 - if (ssl_key != NULL) - curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - if (ssl_capath != NULL) - curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); -#endif - if (ssl_cainfo != NULL) - curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); - curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); - - if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { - curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, - curl_low_speed_limit); - curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME, - curl_low_speed_time); - } - - curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); - - return result; -} - -static struct active_request_slot *get_active_slot(void) -{ - struct active_request_slot *slot = active_queue_head; - struct active_request_slot *newslot; +static void process_object_response(void *callback_data); -#ifdef USE_CURL_MULTI - int num_transfers; - - /* Wait for a slot to open up if the queue is full */ - while (active_requests >= max_requests) { - curl_multi_perform(curlm, &num_transfers); - if (num_transfers < active_requests) { - process_curl_messages(); - } - } -#endif - - while (slot != NULL && slot->in_use) { - slot = slot->next; - } - if (slot == NULL) { - newslot = xmalloc(sizeof(*newslot)); - newslot->curl = NULL; - newslot->in_use = 0; - newslot->next = NULL; - - slot = active_queue_head; - if (slot == NULL) { - active_queue_head = newslot; - } else { - while (slot->next != NULL) { - slot = slot->next; - } - slot->next = newslot; - } - slot = newslot; - } - - if (slot->curl == NULL) { -#ifdef NO_CURL_EASY_DUPHANDLE - slot->curl = get_curl_handle(); -#else - slot->curl = curl_easy_duphandle(curl_default); -#endif - } - - active_requests++; - slot->in_use = 1; - slot->done = 0; - slot->local = NULL; - slot->callback_data = NULL; - slot->callback_func = NULL; - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header); - curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); - - return slot; -} - -static int start_active_slot(struct active_request_slot *slot) +static void start_object_request(struct object_request *obj_req) { -#ifdef USE_CURL_MULTI - CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); - - if (curlm_result != CURLM_OK && - curlm_result != CURLM_CALL_MULTI_PERFORM) { - active_requests--; - slot->in_use = 0; - return 0; - } -#endif - return 1; -} - -static void run_active_slot(struct active_request_slot *slot) -{ -#ifdef USE_CURL_MULTI - int num_transfers; - long last_pos = 0; - long current_pos; - fd_set readfds; - fd_set writefds; - fd_set excfds; - int max_fd; - struct timeval select_timeout; - CURLMcode curlm_result; - - while (!slot->done) { - data_received = 0; - do { - curlm_result = curl_multi_perform(curlm, - &num_transfers); - } while (curlm_result == CURLM_CALL_MULTI_PERFORM); - if (num_transfers < active_requests) { - process_curl_messages(); - process_request_queue(); - } - - if (!data_received && slot->local != NULL) { - current_pos = ftell(slot->local); - if (current_pos > last_pos) - data_received++; - last_pos = current_pos; - } - - if (!slot->done && !data_received) { - max_fd = 0; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&excfds); - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 50000; - select(max_fd, &readfds, &writefds, - &excfds, &select_timeout); - } - } -#else - slot->curl_result = curl_easy_perform(slot->curl); - active_requests--; -#endif -} - -static void start_request(struct transfer_request *request) -{ - char *hex = sha1_to_hex(request->sha1); + char *hex = sha1_to_hex(obj_req->sha1); char prevfile[PATH_MAX]; char *url; char *posn; @@ -420,53 +105,53 @@ static void start_request(struct transfer_request *request) struct curl_slist *range_header = NULL; struct active_request_slot *slot; - snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename); + snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename); unlink(prevfile); - rename(request->tmpfile, prevfile); - unlink(request->tmpfile); + rename(obj_req->tmpfile, prevfile); + unlink(obj_req->tmpfile); - if (request->local != -1) - error("fd leakage in start: %d", request->local); - request->local = open(request->tmpfile, + if (obj_req->local != -1) + error("fd leakage in start: %d", obj_req->local); + obj_req->local = open(obj_req->tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666); /* This could have failed due to the "lazy directory creation"; * try to mkdir the last path component. */ - if (request->local < 0 && errno == ENOENT) { - char *dir = strrchr(request->tmpfile, '/'); + if (obj_req->local < 0 && errno == ENOENT) { + char *dir = strrchr(obj_req->tmpfile, '/'); if (dir) { *dir = 0; - mkdir(request->tmpfile, 0777); + mkdir(obj_req->tmpfile, 0777); *dir = '/'; } - request->local = open(request->tmpfile, + obj_req->local = open(obj_req->tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666); } - if (request->local < 0) { - request->state = ABORTED; + if (obj_req->local < 0) { + obj_req->state = ABORTED; error("Couldn't create temporary file %s for %s: %s\n", - request->tmpfile, request->filename, strerror(errno)); + obj_req->tmpfile, obj_req->filename, strerror(errno)); return; } - memset(&request->stream, 0, sizeof(request->stream)); + memset(&obj_req->stream, 0, sizeof(obj_req->stream)); - inflateInit(&request->stream); + inflateInit(&obj_req->stream); - SHA1_Init(&request->c); + SHA1_Init(&obj_req->c); - url = xmalloc(strlen(request->repo->base) + 50); - request->url = xmalloc(strlen(request->repo->base) + 50); - strcpy(url, request->repo->base); - posn = url + strlen(request->repo->base); + url = xmalloc(strlen(obj_req->repo->base) + 50); + obj_req->url = xmalloc(strlen(obj_req->repo->base) + 50); + strcpy(url, obj_req->repo->base); + posn = url + strlen(obj_req->repo->base); strcpy(posn, "objects/"); posn += 8; memcpy(posn, hex, 2); posn += 2; *(posn++) = '/'; strcpy(posn, hex + 2); - strcpy(request->url, url); + strcpy(obj_req->url, url); /* If a previous temp file is present, process what was already fetched. */ @@ -478,7 +163,7 @@ static void start_request(struct transfer_request *request) if (fwrite_sha1_file(prev_buf, 1, prev_read, - request) == prev_read) { + obj_req) == prev_read) { prev_posn += prev_read; } else { prev_read = -1; @@ -492,20 +177,24 @@ static void start_request(struct transfer_request *request) /* Reset inflate/SHA1 if there was an error reading the previous temp file; also rewind to the beginning of the local file. */ if (prev_read == -1) { - memset(&request->stream, 0, sizeof(request->stream)); - inflateInit(&request->stream); - SHA1_Init(&request->c); + memset(&obj_req->stream, 0, sizeof(obj_req->stream)); + inflateInit(&obj_req->stream); + SHA1_Init(&obj_req->c); if (prev_posn>0) { prev_posn = 0; - lseek(request->local, SEEK_SET, 0); - ftruncate(request->local, 0); + lseek(obj_req->local, SEEK_SET, 0); + ftruncate(obj_req->local, 0); } } slot = get_active_slot(); - curl_easy_setopt(slot->curl, CURLOPT_FILE, request); + slot->callback_func = process_object_response; + slot->callback_data = obj_req; + obj_req->slot = slot; + + curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); - curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); + curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); @@ -523,151 +212,111 @@ static void start_request(struct transfer_request *request) } /* Try to get the request started, abort the request on error */ + obj_req->state = ACTIVE; if (!start_active_slot(slot)) { - request->state = ABORTED; - close(request->local); request->local = -1; - free(request->url); + obj_req->state = ABORTED; + obj_req->slot = NULL; + close(obj_req->local); obj_req->local = -1; + free(obj_req->url); return; } - request->slot = slot; - request->state = ACTIVE; } -static void finish_request(struct transfer_request *request) +static void finish_object_request(struct object_request *obj_req) { struct stat st; - fchmod(request->local, 0444); - close(request->local); request->local = -1; + fchmod(obj_req->local, 0444); + close(obj_req->local); obj_req->local = -1; - if (request->http_code == 416) { + if (obj_req->http_code == 416) { fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); - } else if (request->curl_result != CURLE_OK) { - if (stat(request->tmpfile, &st) == 0) + } else if (obj_req->curl_result != CURLE_OK) { + if (stat(obj_req->tmpfile, &st) == 0) if (st.st_size == 0) - unlink(request->tmpfile); + unlink(obj_req->tmpfile); return; } - inflateEnd(&request->stream); - SHA1_Final(request->real_sha1, &request->c); - if (request->zret != Z_STREAM_END) { - unlink(request->tmpfile); + inflateEnd(&obj_req->stream); + SHA1_Final(obj_req->real_sha1, &obj_req->c); + if (obj_req->zret != Z_STREAM_END) { + unlink(obj_req->tmpfile); return; } - if (memcmp(request->sha1, request->real_sha1, 20)) { - unlink(request->tmpfile); + if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + unlink(obj_req->tmpfile); return; } - request->rename = - move_temp_to_file(request->tmpfile, request->filename); + obj_req->rename = + move_temp_to_file(obj_req->tmpfile, obj_req->filename); - if (request->rename == 0) - pull_say("got %s\n", sha1_to_hex(request->sha1)); + if (obj_req->rename == 0) + pull_say("got %s\n", sha1_to_hex(obj_req->sha1)); } -static void release_request(struct transfer_request *request) +static void process_object_response(void *callback_data) { - struct transfer_request *entry = request_queue_head; + struct object_request *obj_req = + (struct object_request *)callback_data; - if (request->local != -1) - error("fd leakage in release: %d", request->local); - if (request == request_queue_head) { - request_queue_head = request->next; - } else { - while (entry->next != NULL && entry->next != request) - entry = entry->next; - if (entry->next == request) - entry->next = entry->next->next; + obj_req->curl_result = obj_req->slot->curl_result; + obj_req->http_code = obj_req->slot->http_code; + obj_req->slot = NULL; + obj_req->state = COMPLETE; + + /* Use alternates if necessary */ + if (obj_req->http_code == 404) { + fetch_alternates(alt->base); + if (obj_req->repo->next != NULL) { + obj_req->repo = + obj_req->repo->next; + close(obj_req->local); + obj_req->local = -1; + start_object_request(obj_req); + return; + } } - free(request->url); - free(request); + finish_object_request(obj_req); } -#ifdef USE_CURL_MULTI -static void process_curl_messages(void) +static void release_object_request(struct object_request *obj_req) { - int num_messages; - struct active_request_slot *slot; - struct transfer_request *request = NULL; - CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages); - - while (curl_message != NULL) { - if (curl_message->msg == CURLMSG_DONE) { - int curl_result = curl_message->data.result; - slot = active_queue_head; - while (slot != NULL && - slot->curl != curl_message->easy_handle) - slot = slot->next; - if (slot != NULL) { - curl_multi_remove_handle(curlm, slot->curl); - active_requests--; - slot->done = 1; - slot->in_use = 0; - slot->curl_result = curl_result; - curl_easy_getinfo(slot->curl, - CURLINFO_HTTP_CODE, - &slot->http_code); - request = request_queue_head; - while (request != NULL && - request->slot != slot) - request = request->next; - } else { - fprintf(stderr, "Received DONE message for unknown request!\n"); - } + struct object_request *entry = object_queue_head; - /* Process slot callback if appropriate */ - if (slot->callback_func != NULL) { - slot->callback_func(slot->callback_data); - } - - if (request != NULL) { - request->curl_result = curl_result; - request->http_code = slot->http_code; - request->slot = NULL; - request->state = COMPLETE; - - /* Use alternates if necessary */ - if (request->http_code == 404) { - fetch_alternates(alt->base); - if (request->repo->next != NULL) { - request->repo = - request->repo->next; - close(request->local); - request->local = -1; - start_request(request); - } else { - finish_request(request); - } - } else { - finish_request(request); - } - } - } else { - fprintf(stderr, "Unknown CURL message received: %d\n", - (int)curl_message->msg); - } - curl_message = curl_multi_info_read(curlm, &num_messages); + if (obj_req->local != -1) + error("fd leakage in release: %d", obj_req->local); + if (obj_req == object_queue_head) { + object_queue_head = obj_req->next; + } else { + while (entry->next != NULL && entry->next != obj_req) + entry = entry->next; + if (entry->next == obj_req) + entry->next = entry->next->next; } + + free(obj_req->url); + free(obj_req); } -static void process_request_queue(void) +#ifdef USE_CURL_MULTI +void fill_active_slots(void) { - struct transfer_request *request = request_queue_head; + struct object_request *obj_req = object_queue_head; struct active_request_slot *slot = active_queue_head; int num_transfers; - while (active_requests < max_requests && request != NULL) { - if (request->state == WAITING) { - if (has_sha1_file(request->sha1)) - release_request(request); + while (active_requests < max_requests && obj_req != NULL) { + if (obj_req->state == WAITING) { + if (has_sha1_file(obj_req->sha1)) + release_object_request(obj_req); else - start_request(request); + start_object_request(obj_req); curl_multi_perform(curlm, &num_transfers); } - request = request->next; + obj_req = obj_req->next; } while (slot != NULL) { @@ -682,8 +331,8 @@ static void process_request_queue(void) void prefetch(unsigned char *sha1) { - struct transfer_request *newreq; - struct transfer_request *tail; + struct object_request *newreq; + struct object_request *tail; char *filename = sha1_file_name(sha1); newreq = xmalloc(sizeof(*newreq)); @@ -697,18 +346,19 @@ void prefetch(unsigned char *sha1) "%s.temp", filename); newreq->next = NULL; - if (request_queue_head == NULL) { - request_queue_head = newreq; + if (object_queue_head == NULL) { + object_queue_head = newreq; } else { - tail = request_queue_head; + tail = object_queue_head; while (tail->next != NULL) { tail = tail->next; } tail->next = newreq; } + #ifdef USE_CURL_MULTI - process_request_queue(); - process_curl_messages(); + fill_active_slots(); + step_active_slots(); #endif } @@ -793,9 +443,10 @@ static int setup_index(struct alt_base *repo, unsigned char *sha1) return 0; } -static void process_alternates(void *callback_data) +static void process_alternates_response(void *callback_data) { - struct alt_request *alt_req = (struct alt_request *)callback_data; + struct alternates_request *alt_req = + (struct alternates_request *)callback_data; struct active_request_slot *slot = alt_req->slot; struct alt_base *tail = alt; char *base = alt_req->base; @@ -815,12 +466,11 @@ static void process_alternates(void *callback_data) alt_req->url); active_requests++; slot->in_use = 1; - slot->done = 0; if (start_active_slot(slot)) { return; } else { got_alternates = -1; - slot->done = 1; + slot->in_use = 0; return; } } @@ -831,7 +481,7 @@ static void process_alternates(void *callback_data) } } - fwrite_buffer_dynamic(&null_byte, 1, 1, alt_req->buffer); + fwrite_buffer(&null_byte, 1, 1, alt_req->buffer); alt_req->buffer->posn--; data = alt_req->buffer->buffer; @@ -901,17 +551,16 @@ static void fetch_alternates(char *base) char *url; char *data; struct active_request_slot *slot; - static struct alt_request alt_req; - int num_transfers; + static struct alternates_request alt_req; /* If another request has already started fetching alternates, wait for them to arrive and return to processing this request's curl message */ +#ifdef USE_CURL_MULTI while (got_alternates == 0) { - curl_multi_perform(curlm, &num_transfers); - process_curl_messages(); - process_request_queue(); + step_active_slots(); } +#endif /* Nothing to do if they've already been fetched */ if (got_alternates == 1) @@ -934,12 +583,11 @@ static void fetch_alternates(char *base) /* Use a callback to process the result, since another request may fail and need to have alternates loaded before continuing */ slot = get_active_slot(); - slot->callback_func = process_alternates; + slot->callback_func = process_alternates_response; slot->callback_data = &alt_req; curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, - fwrite_buffer_dynamic); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); alt_req.base = base; @@ -983,17 +631,24 @@ static int fetch_indices(struct alt_base *repo) slot = get_active_slot(); curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, - fwrite_buffer_dynamic); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); if (start_active_slot(slot)) { run_active_slot(slot); if (slot->curl_result != CURLE_OK) { - free(buffer.buffer); - return error("%s", curl_errorstr); + if (slot->http_code == 404) { + repo->got_indices = 1; + free(buffer.buffer); + return 0; + } else { + repo->got_indices = 0; + free(buffer.buffer); + return error("%s", curl_errorstr); + } } } else { + repo->got_indices = 0; free(buffer.buffer); return error("Unable to start request"); } @@ -1115,94 +770,56 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) static int fetch_object(struct alt_base *repo, unsigned char *sha1) { char *hex = sha1_to_hex(sha1); - int ret; - struct transfer_request *request = request_queue_head; + int ret = 0; + struct object_request *obj_req = object_queue_head; - while (request != NULL && memcmp(request->sha1, sha1, 20)) - request = request->next; - if (request == NULL) + while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20)) + obj_req = obj_req->next; + if (obj_req == NULL) return error("Couldn't find request for %s in the queue", hex); - if (has_sha1_file(request->sha1)) { - release_request(request); + if (has_sha1_file(obj_req->sha1)) { + release_object_request(obj_req); return 0; } #ifdef USE_CURL_MULTI - while (request->state == WAITING) { - int num_transfers; - curl_multi_perform(curlm, &num_transfers); - if (num_transfers < active_requests) { - process_curl_messages(); - process_request_queue(); - } + while (obj_req->state == WAITING) { + step_active_slots(); } #else - start_request(request); + start_object_request(obj_req); #endif - while (request->state == ACTIVE) { - run_active_slot(request->slot); -#ifndef USE_CURL_MULTI - request->curl_result = request->slot->curl_result; - request->http_code = request->slot->http_code; - request->slot = NULL; - - /* Use alternates if necessary */ - if (request->http_code == 404) { - fetch_alternates(alt->base); - if (request->repo->next != NULL) { - request->repo = request->repo->next; - close(request->local); request->local = -1; - start_request(request); - } - } else { - finish_request(request); - request->state = COMPLETE; - } -#endif + while (obj_req->state == ACTIVE) { + run_active_slot(obj_req->slot); } - if (request->local != -1) { - close(request->local); request->local = -1; + if (obj_req->local != -1) { + close(obj_req->local); obj_req->local = -1; } - if (request->state == ABORTED) { - release_request(request); - return error("Request for %s aborted", hex); - } - - if (request->curl_result != CURLE_OK && request->http_code != 416) { - if (request->http_code == 404) + if (obj_req->state == ABORTED) { + ret = error("Request for %s aborted", hex); + } else if (obj_req->curl_result != CURLE_OK && + obj_req->http_code != 416) { + if (obj_req->http_code == 404) ret = -1; /* Be silent, it is probably in a pack. */ else ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)", - request->errorstr, request->curl_result, - request->http_code, hex); - release_request(request); - return ret; - } - - if (request->zret != Z_STREAM_END) { - ret = error("File %s (%s) corrupt\n", hex, request->url); - release_request(request); - return ret; - } - - if (memcmp(request->sha1, request->real_sha1, 20)) { - release_request(request); - return error("File %s has bad hash\n", hex); - } - - if (request->rename < 0) { + obj_req->errorstr, obj_req->curl_result, + obj_req->http_code, hex); + } else if (obj_req->zret != Z_STREAM_END) { + ret = error("File %s (%s) corrupt\n", hex, obj_req->url); + } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + ret = error("File %s has bad hash\n", hex); + } else if (obj_req->rename < 0) { ret = error("unable to write sha1 filename %s: %s", - request->filename, - strerror(request->rename)); - release_request(request); - return ret; + obj_req->filename, + strerror(obj_req->rename)); } - release_request(request); - return 0; + release_object_request(obj_req); + return ret; } int fetch(unsigned char *sha1) @@ -1303,10 +920,6 @@ int main(int argc, char **argv) char *commit_id; char *url; int arg = 1; - struct active_request_slot *slot; - char *low_speed_limit; - char *low_speed_time; - char *wait_url; int rc = 0; while (arg < argc && argv[arg][0] == '-') { @@ -1335,58 +948,9 @@ int main(int argc, char **argv) commit_id = argv[arg]; url = argv[arg + 1]; - curl_global_init(CURL_GLOBAL_ALL); - -#ifdef USE_CURL_MULTI - { - char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); - if (http_max_requests != NULL) - max_requests = atoi(http_max_requests); - } - - curlm = curl_multi_init(); - if (curlm == NULL) { - fprintf(stderr, "Error creating curl multi handle.\n"); - return 1; - } -#endif - - if (getenv("GIT_SSL_NO_VERIFY")) - curl_ssl_verify = 0; - - ssl_cert = getenv("GIT_SSL_CERT"); -#if LIBCURL_VERSION_NUM >= 0x070902 - ssl_key = getenv("GIT_SSL_KEY"); -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - ssl_capath = getenv("GIT_SSL_CAPATH"); -#endif - ssl_cainfo = getenv("GIT_SSL_CAINFO"); - - low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); - if (low_speed_limit != NULL) - curl_low_speed_limit = strtol(low_speed_limit, NULL, 10); - low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME"); - if (low_speed_time != NULL) - curl_low_speed_time = strtol(low_speed_time, NULL, 10); + http_init(); - git_config(http_options); - - if (curl_ssl_verify == -1) - curl_ssl_verify = 1; - -#ifdef USE_CURL_MULTI - if (max_requests < 1) - max_requests = DEFAULT_MAX_REQUESTS; -#endif - - pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); - no_range_header = curl_slist_append(no_range_header, "Range:"); - -#ifndef NO_CURL_EASY_DUPHANDLE - curl_default = get_curl_handle(); -#endif alt = xmalloc(sizeof(*alt)); alt->base = url; @@ -1397,30 +961,9 @@ int main(int argc, char **argv) if (pull(commit_id)) rc = 1; - curl_slist_free_all(pragma_header); curl_slist_free_all(no_pragma_header); - curl_slist_free_all(no_range_header); -#ifndef NO_CURL_EASY_DUPHANDLE - curl_easy_cleanup(curl_default); -#endif - slot = active_queue_head; - while (slot != NULL) { - if (slot->in_use) { - if (get_verbosely) { - curl_easy_getinfo(slot->curl, - CURLINFO_EFFECTIVE_URL, - &wait_url); - fprintf(stderr, "Waiting for %s\n", wait_url); - } - run_active_slot(slot); - } - if (slot->curl != NULL) - curl_easy_cleanup(slot->curl); - slot = slot->next; - } -#ifdef USE_CURL_MULTI - curl_multi_cleanup(curlm); -#endif - curl_global_cleanup(); + + http_cleanup(); + return rc; } diff --git a/http-push.c b/http-push.c index 8866189332..76c788673e 100644 --- a/http-push.c +++ b/http-push.c @@ -4,30 +4,13 @@ #include "fetch.h" #include "tag.h" #include "blob.h" +#include "http.h" -#include -#include #include static const char http_push_usage[] = "git-http-push [--complete] [--force] [--verbose] [...]\n"; -#if LIBCURL_VERSION_NUM >= 0x070908 -#define USE_CURL_MULTI -#define DEFAULT_MAX_REQUESTS 5 -#endif - -#if LIBCURL_VERSION_NUM < 0x070704 -#define curl_global_cleanup() do { /* nothing */ } while(0) -#endif -#if LIBCURL_VERSION_NUM < 0x070800 -#define curl_global_init(a) do { /* nothing */ } while(0) -#endif - -#if LIBCURL_VERSION_NUM < 0x070c04 -#define NO_CURL_EASY_DUPHANDLE -#endif - #ifndef XML_STATUS_OK enum XML_Status { XML_STATUS_OK = 1, @@ -39,47 +22,45 @@ enum XML_Status { #define RANGE_HEADER_SIZE 30 -/* DAV method names and request body templates */ +/* DAV methods */ #define DAV_LOCK "LOCK" #define DAV_MKCOL "MKCOL" #define DAV_MOVE "MOVE" #define DAV_PROPFIND "PROPFIND" #define DAV_PUT "PUT" #define DAV_UNLOCK "UNLOCK" + +/* DAV lock flags */ +#define DAV_PROP_LOCKWR (1u << 0) +#define DAV_PROP_LOCKEX (1u << 1) +#define DAV_LOCK_OK (1u << 2) + +/* DAV XML properties */ +#define DAV_CTX_LOCKENTRY ".multistatus.response.propstat.prop.supportedlock.lockentry" +#define DAV_CTX_LOCKTYPE_WRITE ".multistatus.response.propstat.prop.supportedlock.lockentry.locktype.write" +#define DAV_CTX_LOCKTYPE_EXCLUSIVE ".multistatus.response.propstat.prop.supportedlock.lockentry.lockscope.exclusive" +#define DAV_ACTIVELOCK_OWNER ".prop.lockdiscovery.activelock.owner.href" +#define DAV_ACTIVELOCK_TIMEOUT ".prop.lockdiscovery.activelock.timeout" +#define DAV_ACTIVELOCK_TOKEN ".prop.lockdiscovery.activelock.locktoken.href" + +/* DAV request body templates */ #define PROPFIND_REQUEST "\n\n\n\n\n" #define LOCK_REQUEST "\n\n\n\n\nmailto:%s\n\n" #define LOCK_TIME 600 #define LOCK_REFRESH 30 -static int active_requests = 0; -static int data_received; static int pushing = 0; static int aborted = 0; static char remote_dir_exists[256]; -#ifdef USE_CURL_MULTI -static int max_requests = -1; -static CURLM *curlm; -#endif -#ifndef NO_CURL_EASY_DUPHANDLE -static CURL *curl_default; -#endif static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; -static char curl_errorstr[CURL_ERROR_SIZE]; static int push_verbosely = 0; static int push_all = 0; static int force_all = 0; -struct buffer -{ - size_t posn; - size_t size; - void *buffer; -}; - struct repo { char *url; @@ -122,40 +103,19 @@ struct transfer_request struct transfer_request *next; }; -struct active_request_slot -{ - CURL *curl; - FILE *local; - int in_use; - int done; - CURLcode curl_result; - long http_code; - struct active_request_slot *next; -}; - static struct transfer_request *request_queue_head = NULL; -static struct active_request_slot *active_queue_head = NULL; -static int curl_ssl_verify = -1; -static char *ssl_cert = NULL; -#if LIBCURL_VERSION_NUM >= 0x070902 -static char *ssl_key = NULL; -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 -static char *ssl_capath = NULL; -#endif -static char *ssl_cainfo = NULL; -static long curl_low_speed_limit = -1; -static long curl_low_speed_time = -1; +struct xml_ctx +{ + char *name; + int len; + char *cdata; + void (*userFunc)(struct xml_ctx *ctx, int tag_closed); + void *userData; +}; struct active_lock { - int ctx_activelock; - int ctx_owner; - int ctx_owner_href; - int ctx_timeout; - int ctx_locktoken; - int ctx_locktoken_href; char *url; char *owner; char *token; @@ -164,270 +124,14 @@ struct active_lock int refreshing; }; -struct lockprop -{ - int supported_lock; - int lock_entry; - int lock_scope; - int lock_type; - int lock_exclusive; - int lock_exclusive_write; -}; +static void finish_request(struct transfer_request *request); -static int http_options(const char *var, const char *value) +static void process_response(void *callback_data) { - if (!strcmp("http.sslverify", var)) { - if (curl_ssl_verify == -1) { - curl_ssl_verify = git_config_bool(var, value); - } - return 0; - } + struct transfer_request *request = + (struct transfer_request *)callback_data; - if (!strcmp("http.sslcert", var)) { - if (ssl_cert == NULL) { - ssl_cert = xmalloc(strlen(value)+1); - strcpy(ssl_cert, value); - } - return 0; - } -#if LIBCURL_VERSION_NUM >= 0x070902 - if (!strcmp("http.sslkey", var)) { - if (ssl_key == NULL) { - ssl_key = xmalloc(strlen(value)+1); - strcpy(ssl_key, value); - } - return 0; - } -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - if (!strcmp("http.sslcapath", var)) { - if (ssl_capath == NULL) { - ssl_capath = xmalloc(strlen(value)+1); - strcpy(ssl_capath, value); - } - return 0; - } -#endif - if (!strcmp("http.sslcainfo", var)) { - if (ssl_cainfo == NULL) { - ssl_cainfo = xmalloc(strlen(value)+1); - strcpy(ssl_cainfo, value); - } - return 0; - } - -#ifdef USE_CURL_MULTI - if (!strcmp("http.maxrequests", var)) { - if (max_requests == -1) - max_requests = git_config_int(var, value); - return 0; - } -#endif - - if (!strcmp("http.lowspeedlimit", var)) { - if (curl_low_speed_limit == -1) - curl_low_speed_limit = (long)git_config_int(var, value); - return 0; - } - if (!strcmp("http.lowspeedtime", var)) { - if (curl_low_speed_time == -1) - curl_low_speed_time = (long)git_config_int(var, value); - return 0; - } - - /* Fall back on the default ones */ - return git_default_config(var, value); -} - -static size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, - struct buffer *buffer) -{ - size_t size = eltsize * nmemb; - if (size > buffer->size - buffer->posn) - size = buffer->size - buffer->posn; - memcpy(ptr, buffer->buffer + buffer->posn, size); - buffer->posn += size; - return size; -} - -static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize, - size_t nmemb, struct buffer *buffer) -{ - size_t size = eltsize * nmemb; - if (size > buffer->size - buffer->posn) { - buffer->size = buffer->size * 3 / 2; - if (buffer->size < buffer->posn + size) - buffer->size = buffer->posn + size; - buffer->buffer = xrealloc(buffer->buffer, buffer->size); - } - memcpy(buffer->buffer + buffer->posn, ptr, size); - buffer->posn += size; - data_received++; - return size; -} - -static size_t fwrite_null(const void *ptr, size_t eltsize, - size_t nmemb, struct buffer *buffer) -{ - data_received++; - return eltsize * nmemb; -} - -#ifdef USE_CURL_MULTI -static void process_curl_messages(void); -static void process_request_queue(void); -#endif - -static CURL* get_curl_handle(void) -{ - CURL* result = curl_easy_init(); - - curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify); -#if LIBCURL_VERSION_NUM >= 0x070907 - curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); -#endif - - if (ssl_cert != NULL) - curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); -#if LIBCURL_VERSION_NUM >= 0x070902 - if (ssl_key != NULL) - curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - if (ssl_capath != NULL) - curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); -#endif - if (ssl_cainfo != NULL) - curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); - curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); - - if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { - curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, - curl_low_speed_limit); - curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME, - curl_low_speed_time); - } - - return result; -} - -static struct active_request_slot *get_active_slot(void) -{ - struct active_request_slot *slot = active_queue_head; - struct active_request_slot *newslot; - -#ifdef USE_CURL_MULTI - int num_transfers; - - /* Wait for a slot to open up if the queue is full */ - while (active_requests >= max_requests) { - curl_multi_perform(curlm, &num_transfers); - if (num_transfers < active_requests) { - process_curl_messages(); - } - } -#endif - - while (slot != NULL && slot->in_use) { - slot = slot->next; - } - if (slot == NULL) { - newslot = xmalloc(sizeof(*newslot)); - newslot->curl = NULL; - newslot->in_use = 0; - newslot->next = NULL; - - slot = active_queue_head; - if (slot == NULL) { - active_queue_head = newslot; - } else { - while (slot->next != NULL) { - slot = slot->next; - } - slot->next = newslot; - } - slot = newslot; - } - - if (slot->curl == NULL) { -#ifdef NO_CURL_EASY_DUPHANDLE - slot->curl = get_curl_handle(); -#else - slot->curl = curl_easy_duphandle(curl_default); -#endif - } - - active_requests++; - slot->in_use = 1; - slot->done = 0; - slot->local = NULL; - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, default_headers); - curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); - - return slot; -} - -static int start_active_slot(struct active_request_slot *slot) -{ -#ifdef USE_CURL_MULTI - CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); - - if (curlm_result != CURLM_OK && - curlm_result != CURLM_CALL_MULTI_PERFORM) { - active_requests--; - slot->in_use = 0; - return 0; - } -#endif - return 1; -} - -static void run_active_slot(struct active_request_slot *slot) -{ -#ifdef USE_CURL_MULTI - int num_transfers; - long last_pos = 0; - long current_pos; - fd_set readfds; - fd_set writefds; - fd_set excfds; - int max_fd; - struct timeval select_timeout; - CURLMcode curlm_result; - - while (!slot->done) { - data_received = 0; - do { - curlm_result = curl_multi_perform(curlm, - &num_transfers); - } while (curlm_result == CURLM_CALL_MULTI_PERFORM); - if (num_transfers < active_requests) { - process_curl_messages(); - process_request_queue(); - } - - if (!data_received && slot->local != NULL) { - current_pos = ftell(slot->local); - if (current_pos > last_pos) - data_received++; - last_pos = current_pos; - } - - if (!slot->done && !data_received) { - max_fd = 0; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&excfds); - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 50000; - select(max_fd, &readfds, &writefds, - &excfds, &select_timeout); - } - } -#else - slot->curl_result = curl_easy_perform(slot->curl); - active_requests--; -#endif + finish_request(request); } static void start_check(struct transfer_request *request) @@ -447,6 +151,8 @@ static void start_check(struct transfer_request *request) strcpy(posn, hex + 2); slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); @@ -457,6 +163,7 @@ static void start_check(struct transfer_request *request) } else { request->state = ABORTED; free(request->url); + request->url = NULL; } } @@ -476,6 +183,8 @@ static void start_mkcol(struct transfer_request *request) strcpy(posn, "/"); slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */ curl_easy_setopt(slot->curl, CURLOPT_URL, request->url); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); @@ -488,6 +197,7 @@ static void start_mkcol(struct transfer_request *request) } else { request->state = ABORTED; free(request->url); + request->url = NULL; } } @@ -534,8 +244,6 @@ static void start_put(struct transfer_request *request) request->buffer.size = stream.total_out; request->buffer.posn = 0; - if (request->url != NULL) - free(request->url); request->url = xmalloc(strlen(remote->url) + strlen(request->lock->token) + 51); strcpy(request->url, remote->url); @@ -553,6 +261,8 @@ static void start_put(struct transfer_request *request) strcpy(posn, request->lock->token); slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); @@ -569,6 +279,7 @@ static void start_put(struct transfer_request *request) } else { request->state = ABORTED; free(request->url); + request->url = NULL; } } @@ -578,6 +289,8 @@ static void start_move(struct transfer_request *request) struct curl_slist *dav_headers = NULL; slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE); dav_headers = curl_slist_append(dav_headers, request->dest); @@ -592,6 +305,7 @@ static void start_move(struct transfer_request *request) } else { request->state = ABORTED; free(request->url); + request->url = NULL; } } @@ -656,6 +370,13 @@ static void finish_request(struct transfer_request *request) if (request->headers != NULL) curl_slist_free_all(request->headers); + + /* URL is reused for MOVE after PUT */ + if (request->state != RUN_PUT) { + free(request->url); + request->url = NULL; + } + if (request->state == RUN_HEAD) { if (request->http_code == 404) { request->state = NEED_PUSH; @@ -721,52 +442,12 @@ static void release_request(struct transfer_request *request) entry->next = entry->next->next; } - free(request->url); + if (request->url != NULL) + free(request->url); free(request); } -#ifdef USE_CURL_MULTI -static void process_curl_messages(void) -{ - int num_messages; - struct active_request_slot *slot; - struct transfer_request *request = NULL; - CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages); - - while (curl_message != NULL) { - if (curl_message->msg == CURLMSG_DONE) { - slot = active_queue_head; - while (slot != NULL && - slot->curl != curl_message->easy_handle) - slot = slot->next; - if (slot != NULL) { - int curl_result = curl_message->data.result; - curl_multi_remove_handle(curlm, slot->curl); - active_requests--; - slot->done = 1; - slot->in_use = 0; - slot->curl_result = curl_result; - curl_easy_getinfo(slot->curl, - CURLINFO_HTTP_CODE, - &slot->http_code); - request = request_queue_head; - while (request != NULL && - request->slot != slot) - request = request->next; - if (request != NULL) - finish_request(request); - } else { - fprintf(stderr, "Received DONE message for unknown request!\n"); - } - } else { - fprintf(stderr, "Unknown CURL message received: %d\n", - (int)curl_message->msg); - } - curl_message = curl_multi_info_read(curlm, &num_messages); - } -} - -static void process_request_queue(void) +void fill_active_slots(void) { struct transfer_request *request = request_queue_head; struct active_request_slot *slot = active_queue_head; @@ -797,20 +478,6 @@ static void process_request_queue(void) slot = slot->next; } } -#endif - -static void process_waiting_requests(void) -{ - struct active_request_slot *slot = active_queue_head; - - while (slot != NULL) - if (slot->in_use) { - run_active_slot(slot); - slot = active_queue_head; - } else { - slot = slot->next; - } -} static void add_request(unsigned char *sha1, struct active_lock *lock) { @@ -834,10 +501,9 @@ static void add_request(unsigned char *sha1, struct active_lock *lock) request->state = NEED_CHECK; request->next = request_queue_head; request_queue_head = request; -#ifdef USE_CURL_MULTI - process_request_queue(); - process_curl_messages(); -#endif + + fill_active_slots(); + step_active_slots(); } static int fetch_index(unsigned char *sha1) @@ -917,6 +583,7 @@ static int fetch_index(unsigned char *sha1) } } else { free(url); + fclose(indexfile); return error("Unable to start request"); } @@ -963,8 +630,7 @@ static int fetch_indices(void) slot = get_active_slot(); curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, - fwrite_buffer_dynamic); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); if (start_active_slot(slot)) { @@ -1068,8 +734,7 @@ int fetch_ref(char *ref, unsigned char *sha1) url = quote_ref_url(base, ref); slot = get_active_slot(); curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, - fwrite_buffer_dynamic); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); curl_easy_setopt(slot->curl, CURLOPT_URL, url); if (start_active_slot(slot)) { @@ -1086,107 +751,101 @@ int fetch_ref(char *ref, unsigned char *sha1) return 0; } -static void -start_activelock_element(void *userData, const char *name, const char **atts) +static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed) { - struct active_lock *lock = (struct active_lock *)userData; - - if (lock->ctx_activelock && !strcmp(name, "D:timeout")) - lock->ctx_timeout = 1; - else if (lock->ctx_owner && strstr(name, "href")) - lock->ctx_owner_href = 1; - else if (lock->ctx_activelock && strstr(name, "owner")) - lock->ctx_owner = 1; - else if (lock->ctx_locktoken && !strcmp(name, "D:href")) - lock->ctx_locktoken_href = 1; - else if (lock->ctx_activelock && !strcmp(name, "D:locktoken")) - lock->ctx_locktoken = 1; - else if (!strcmp(name, "D:activelock")) - lock->ctx_activelock = 1; + int *lock_flags = (int *)ctx->userData; + + if (tag_closed) { + if (!strcmp(ctx->name, DAV_CTX_LOCKENTRY)) { + if ((*lock_flags & DAV_PROP_LOCKEX) && + (*lock_flags & DAV_PROP_LOCKWR)) { + *lock_flags |= DAV_LOCK_OK; + } + *lock_flags &= DAV_LOCK_OK; + } else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_WRITE)) { + *lock_flags |= DAV_PROP_LOCKWR; + } else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_EXCLUSIVE)) { + *lock_flags |= DAV_PROP_LOCKEX; + } + } } -static void -end_activelock_element(void *userData, const char *name) +static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) { - struct active_lock *lock = (struct active_lock *)userData; - - if (lock->ctx_timeout && !strcmp(name, "D:timeout")) { - lock->ctx_timeout = 0; - } else if (lock->ctx_owner_href && strstr(name, "href")) { - lock->ctx_owner_href = 0; - } else if (lock->ctx_owner && strstr(name, "owner")) { - lock->ctx_owner = 0; - } else if (lock->ctx_locktoken_href && !strcmp(name, "D:href")) { - lock->ctx_locktoken_href = 0; - } else if (lock->ctx_locktoken && !strcmp(name, "D:locktoken")) { - lock->ctx_locktoken = 0; - } else if (lock->ctx_activelock && !strcmp(name, "D:activelock")) { - lock->ctx_activelock = 0; + struct active_lock *lock = (struct active_lock *)ctx->userData; + + if (tag_closed && ctx->cdata) { + if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) { + lock->owner = xmalloc(strlen(ctx->cdata) + 1); + strcpy(lock->owner, ctx->cdata); + } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TIMEOUT)) { + if (!strncmp(ctx->cdata, "Second-", 7)) + lock->timeout = + strtol(ctx->cdata + 7, NULL, 10); + } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) { + if (!strncmp(ctx->cdata, "opaquelocktoken:", 16)) { + lock->token = xmalloc(strlen(ctx->cdata - 15)); + strcpy(lock->token, ctx->cdata + 16); + } + } } } static void -activelock_cdata(void *userData, const XML_Char *s, int len) +xml_start_tag(void *userData, const char *name, const char **atts) { - struct active_lock *lock = (struct active_lock *)userData; - char *this = malloc(len+1); - strncpy(this, s, len); - - if (lock->ctx_owner_href) { - lock->owner = malloc(len+1); - strcpy(lock->owner, this); - } else if (lock->ctx_locktoken_href) { - if (!strncmp(this, "opaquelocktoken:", 16)) { - lock->token = malloc(len-15); - strcpy(lock->token, this+16); - } - } else if (lock->ctx_timeout) { - if (!strncmp(this, "Second-", 7)) - lock->timeout = strtol(this+7, NULL, 10); + struct xml_ctx *ctx = (struct xml_ctx *)userData; + const char *c = index(name, ':'); + int new_len; + + if (c == NULL) + c = name; + else + c++; + + new_len = strlen(ctx->name) + strlen(c) + 2; + + if (new_len > ctx->len) { + ctx->name = xrealloc(ctx->name, new_len); + ctx->len = new_len; + } + strcat(ctx->name, "."); + strcat(ctx->name, c); + + if (ctx->cdata) { + free(ctx->cdata); + ctx->cdata = NULL; } - free(this); + ctx->userFunc(ctx, 0); } static void -start_lockprop_element(void *userData, const char *name, const char **atts) +xml_end_tag(void *userData, const char *name) { - struct lockprop *prop = (struct lockprop *)userData; + struct xml_ctx *ctx = (struct xml_ctx *)userData; + const char *c = index(name, ':'); + char *ep; - if (prop->lock_type && !strcmp(name, "D:write")) { - if (prop->lock_exclusive) { - prop->lock_exclusive_write = 1; - } - } else if (prop->lock_scope && !strcmp(name, "D:exclusive")) { - prop->lock_exclusive = 1; - } else if (prop->lock_entry) { - if (!strcmp(name, "D:lockscope")) { - prop->lock_scope = 1; - } else if (!strcmp(name, "D:locktype")) { - prop->lock_type = 1; - } - } else if (prop->supported_lock) { - if (!strcmp(name, "D:lockentry")) { - prop->lock_entry = 1; - } - } else if (!strcmp(name, "D:supportedlock")) { - prop->supported_lock = 1; - } + ctx->userFunc(ctx, 1); + + if (c == NULL) + c = name; + else + c++; + + ep = ctx->name + strlen(ctx->name) - strlen(c) - 1; + *ep = 0; } static void -end_lockprop_element(void *userData, const char *name) +xml_cdata(void *userData, const XML_Char *s, int len) { - struct lockprop *prop = (struct lockprop *)userData; - - if (!strcmp(name, "D:lockentry")) { - prop->lock_entry = 0; - prop->lock_scope = 0; - prop->lock_type = 0; - prop->lock_exclusive = 0; - } else if (!strcmp(name, "D:supportedlock")) { - prop->supported_lock = 0; - } + struct xml_ctx *ctx = (struct xml_ctx *)userData; + if (ctx->cdata) + free(ctx->cdata); + ctx->cdata = xcalloc(len+1, 1); + strncpy(ctx->cdata, s, len); } static struct active_lock *lock_remote(char *file, long timeout) @@ -1199,10 +858,11 @@ static struct active_lock *lock_remote(char *file, long timeout) char *url; char *ep; char timeout_header[25]; - struct active_lock *new_lock; + struct active_lock *new_lock = NULL; XML_Parser parser = XML_ParserCreate(NULL); enum XML_Status result; struct curl_slist *dav_headers = NULL; + struct xml_ctx ctx; url = xmalloc(strlen(remote->url) + strlen(file) + 1); sprintf(url, "%s%s", remote->url, file); @@ -1246,12 +906,6 @@ static struct active_lock *lock_remote(char *file, long timeout) in_buffer.posn = 0; in_buffer.buffer = in_data; - new_lock = xcalloc(1, sizeof(*new_lock)); - new_lock->owner = NULL; - new_lock->token = NULL; - new_lock->timeout = -1; - new_lock->refreshing = 0; - sprintf(timeout_header, "Timeout: Second-%ld", timeout); dav_headers = curl_slist_append(dav_headers, timeout_header); dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); @@ -1261,47 +915,47 @@ static struct active_lock *lock_remote(char *file, long timeout) curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, - fwrite_buffer_dynamic); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + new_lock = xcalloc(1, sizeof(*new_lock)); + new_lock->owner = NULL; + new_lock->token = NULL; + new_lock->timeout = -1; + new_lock->refreshing = 0; + if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result != CURLE_OK) { - fprintf(stderr, "Got HTTP error %ld\n", slot->http_code); - free(new_lock); - free(url); - free(out_data); - free(in_data); - return NULL; + if (slot->curl_result == CURLE_OK) { + ctx.name = xcalloc(10, 1); + ctx.len = 0; + ctx.cdata = NULL; + ctx.userFunc = handle_new_lock_ctx; + ctx.userData = new_lock; + XML_SetUserData(parser, &ctx); + XML_SetElementHandler(parser, xml_start_tag, + xml_end_tag); + XML_SetCharacterDataHandler(parser, xml_cdata); + result = XML_Parse(parser, in_buffer.buffer, + in_buffer.posn, 1); + free(ctx.name); + if (result != XML_STATUS_OK) { + fprintf(stderr, "XML error: %s\n", + XML_ErrorString( + XML_GetErrorCode(parser))); + new_lock->timeout = -1; + } } } else { - free(new_lock); - free(url); - free(out_data); - free(in_data); fprintf(stderr, "Unable to start request\n"); - return NULL; } + curl_slist_free_all(dav_headers); free(out_data); - - XML_SetUserData(parser, new_lock); - XML_SetElementHandler(parser, start_activelock_element, - end_activelock_element); - XML_SetCharacterDataHandler(parser, activelock_cdata); - result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1); free(in_data); - if (result != XML_STATUS_OK) { - fprintf(stderr, "%s", XML_ErrorString( - XML_GetErrorCode(parser))); - free(url); - free(new_lock); - return NULL; - } if (new_lock->token == NULL || new_lock->timeout <= 0) { if (new_lock->token != NULL) @@ -1310,11 +964,12 @@ static struct active_lock *lock_remote(char *file, long timeout) free(new_lock->owner); free(url); free(new_lock); - return NULL; + new_lock = NULL; + } else { + new_lock->url = url; + new_lock->start_time = time(NULL); } - new_lock->url = url; - new_lock->start_time = time(NULL); return new_lock; } @@ -1353,13 +1008,15 @@ static int unlock_remote(struct active_lock *lock) if (lock->owner != NULL) free(lock->owner); free(lock->url); +/* Freeing the token causes a segfault... free(lock->token); +*/ free(lock); return rc; } -static int check_locking(void) +static int locking_available(void) { struct active_request_slot *slot; struct buffer in_buffer; @@ -1368,8 +1025,9 @@ static int check_locking(void) char *out_data; XML_Parser parser = XML_ParserCreate(NULL); enum XML_Status result; - struct lockprop supported_lock; struct curl_slist *dav_headers = NULL; + struct xml_ctx ctx; + int lock_flags = 0; out_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2; out_data = xmalloc(out_buffer.size + 1); @@ -1390,8 +1048,7 @@ static int check_locking(void) curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, - fwrite_buffer_dynamic); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); @@ -1399,30 +1056,35 @@ static int check_locking(void) if (start_active_slot(slot)) { run_active_slot(slot); - free(out_data); - if (slot->curl_result != CURLE_OK) { - free(in_buffer.buffer); - return -1; + if (slot->curl_result == CURLE_OK) { + ctx.name = xcalloc(10, 1); + ctx.len = 0; + ctx.cdata = NULL; + ctx.userFunc = handle_lockprop_ctx; + ctx.userData = &lock_flags; + XML_SetUserData(parser, &ctx); + XML_SetElementHandler(parser, xml_start_tag, + xml_end_tag); + result = XML_Parse(parser, in_buffer.buffer, + in_buffer.posn, 1); + free(ctx.name); + + if (result != XML_STATUS_OK) { + fprintf(stderr, "XML error: %s\n", + XML_ErrorString( + XML_GetErrorCode(parser))); + lock_flags = 0; + } } - - XML_SetUserData(parser, &supported_lock); - XML_SetElementHandler(parser, start_lockprop_element, - end_lockprop_element); - result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1); - free(in_buffer.buffer); - if (result != XML_STATUS_OK) - return error("%s", XML_ErrorString( - XML_GetErrorCode(parser))); } else { - free(out_data); - free(in_buffer.buffer); - return error("Unable to start request"); + fprintf(stderr, "Unable to start request\n"); } - if (supported_lock.lock_exclusive_write) - return 0; - else - return 1; + free(out_data); + free(in_buffer.buffer); + curl_slist_free_all(dav_headers); + + return lock_flags; } static int is_ancestor(unsigned char *sha1, struct commit *commit) @@ -1560,8 +1222,6 @@ static int update_remote(unsigned char *sha1, struct active_lock *lock) int main(int argc, char **argv) { - struct active_request_slot *slot; - struct active_request_slot *next_slot; struct transfer_request *request; struct transfer_request *next_request; int nr_refspec = 0; @@ -1576,8 +1236,6 @@ int main(int argc, char **argv) unsigned char remote_sha1[20]; struct active_lock *remote_lock; char *remote_path = NULL; - char *low_speed_limit; - char *low_speed_time; int rc = 0; int i; @@ -1617,50 +1275,7 @@ int main(int argc, char **argv) memset(remote_dir_exists, 0, 256); - curl_global_init(CURL_GLOBAL_ALL); - -#ifdef USE_CURL_MULTI - { - char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); - if (http_max_requests != NULL) - max_requests = atoi(http_max_requests); - } - - curlm = curl_multi_init(); - if (curlm == NULL) { - fprintf(stderr, "Error creating curl multi handle.\n"); - return 1; - } -#endif - - if (getenv("GIT_SSL_NO_VERIFY")) - curl_ssl_verify = 0; - - ssl_cert = getenv("GIT_SSL_CERT"); -#if LIBCURL_VERSION_NUM >= 0x070902 - ssl_key = getenv("GIT_SSL_KEY"); -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - ssl_capath = getenv("GIT_SSL_CAPATH"); -#endif - ssl_cainfo = getenv("GIT_SSL_CAINFO"); - - low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); - if (low_speed_limit != NULL) - curl_low_speed_limit = strtol(low_speed_limit, NULL, 10); - low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME"); - if (low_speed_time != NULL) - curl_low_speed_time = strtol(low_speed_time, NULL, 10); - - git_config(http_options); - - if (curl_ssl_verify == -1) - curl_ssl_verify = 1; - -#ifdef USE_CURL_MULTI - if (max_requests < 1) - max_requests = DEFAULT_MAX_REQUESTS; -#endif + http_init(); no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); default_headers = curl_slist_append(default_headers, "Range:"); @@ -1669,12 +1284,8 @@ int main(int argc, char **argv) default_headers = curl_slist_append(default_headers, "Pragma: no-cache"); -#ifndef NO_CURL_EASY_DUPHANDLE - curl_default = get_curl_handle(); -#endif - /* Verify DAV compliance/lock support */ - if (check_locking() != 0) { + if (!locking_available()) { fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url); rc = 1; goto cleanup; @@ -1766,13 +1377,13 @@ int main(int argc, char **argv) fetch_indices(); get_delta(push_all ? NULL : remote_sha1, local_object, remote_lock); - process_waiting_requests(); + finish_all_active_slots(); /* Push missing objects to remote, this would be a convenient time to pack them first if appropriate. */ pushing = 1; - process_request_queue(); - process_waiting_requests(); + fill_active_slots(); + finish_all_active_slots(); /* Update the remote branch if all went well */ if (do_remote_update) { @@ -1802,14 +1413,7 @@ int main(int argc, char **argv) curl_slist_free_all(no_pragma_header); curl_slist_free_all(default_headers); - slot = active_queue_head; - while (slot != NULL) { - next_slot = slot->next; - if (slot->curl != NULL) - curl_easy_cleanup(slot->curl); - free(slot); - slot = next_slot; - } + http_cleanup(); request = request_queue_head; while (request != NULL) { @@ -1818,12 +1422,5 @@ int main(int argc, char **argv) request = next_request; } -#ifndef NO_CURL_EASY_DUPHANDLE - curl_easy_cleanup(curl_default); -#endif -#ifdef USE_CURL_MULTI - curl_multi_cleanup(curlm); -#endif - curl_global_cleanup(); return rc; } diff --git a/http.c b/http.c new file mode 100644 index 0000000000..75e6717a94 --- /dev/null +++ b/http.c @@ -0,0 +1,442 @@ +#include "http.h" + +int data_received; +int active_requests = 0; + +#ifdef USE_CURL_MULTI +int max_requests = -1; +CURLM *curlm; +#endif +#ifndef NO_CURL_EASY_DUPHANDLE +CURL *curl_default; +#endif +char curl_errorstr[CURL_ERROR_SIZE]; + +int curl_ssl_verify = -1; +char *ssl_cert = NULL; +#if LIBCURL_VERSION_NUM >= 0x070902 +char *ssl_key = NULL; +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 +char *ssl_capath = NULL; +#endif +char *ssl_cainfo = NULL; +long curl_low_speed_limit = -1; +long curl_low_speed_time = -1; + +struct curl_slist *pragma_header; +struct curl_slist *no_range_header; + +struct active_request_slot *active_queue_head = NULL; + +size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, + struct buffer *buffer) +{ + size_t size = eltsize * nmemb; + if (size > buffer->size - buffer->posn) + size = buffer->size - buffer->posn; + memcpy(ptr, buffer->buffer + buffer->posn, size); + buffer->posn += size; + return size; +} + +size_t fwrite_buffer(const void *ptr, size_t eltsize, + size_t nmemb, struct buffer *buffer) +{ + size_t size = eltsize * nmemb; + if (size > buffer->size - buffer->posn) { + buffer->size = buffer->size * 3 / 2; + if (buffer->size < buffer->posn + size) + buffer->size = buffer->posn + size; + buffer->buffer = xrealloc(buffer->buffer, buffer->size); + } + memcpy(buffer->buffer + buffer->posn, ptr, size); + buffer->posn += size; + data_received++; + return size; +} + +size_t fwrite_null(const void *ptr, size_t eltsize, + size_t nmemb, struct buffer *buffer) +{ + data_received++; + return eltsize * nmemb; +} + +static void finish_active_slot(struct active_request_slot *slot); + +#ifdef USE_CURL_MULTI +static void process_curl_messages(void) +{ + int num_messages; + struct active_request_slot *slot; + CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages); + + while (curl_message != NULL) { + if (curl_message->msg == CURLMSG_DONE) { + int curl_result = curl_message->data.result; + slot = active_queue_head; + while (slot != NULL && + slot->curl != curl_message->easy_handle) + slot = slot->next; + if (slot != NULL) { + curl_multi_remove_handle(curlm, slot->curl); + slot->curl_result = curl_result; + finish_active_slot(slot); + } else { + fprintf(stderr, "Received DONE message for unknown request!\n"); + } + } else { + fprintf(stderr, "Unknown CURL message received: %d\n", + (int)curl_message->msg); + } + curl_message = curl_multi_info_read(curlm, &num_messages); + } +} +#endif + +static int http_options(const char *var, const char *value) +{ + if (!strcmp("http.sslverify", var)) { + if (curl_ssl_verify == -1) { + curl_ssl_verify = git_config_bool(var, value); + } + return 0; + } + + if (!strcmp("http.sslcert", var)) { + if (ssl_cert == NULL) { + ssl_cert = xmalloc(strlen(value)+1); + strcpy(ssl_cert, value); + } + return 0; + } +#if LIBCURL_VERSION_NUM >= 0x070902 + if (!strcmp("http.sslkey", var)) { + if (ssl_key == NULL) { + ssl_key = xmalloc(strlen(value)+1); + strcpy(ssl_key, value); + } + return 0; + } +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 + if (!strcmp("http.sslcapath", var)) { + if (ssl_capath == NULL) { + ssl_capath = xmalloc(strlen(value)+1); + strcpy(ssl_capath, value); + } + return 0; + } +#endif + if (!strcmp("http.sslcainfo", var)) { + if (ssl_cainfo == NULL) { + ssl_cainfo = xmalloc(strlen(value)+1); + strcpy(ssl_cainfo, value); + } + return 0; + } + +#ifdef USE_CURL_MULTI + if (!strcmp("http.maxrequests", var)) { + if (max_requests == -1) + max_requests = git_config_int(var, value); + return 0; + } +#endif + + if (!strcmp("http.lowspeedlimit", var)) { + if (curl_low_speed_limit == -1) + curl_low_speed_limit = (long)git_config_int(var, value); + return 0; + } + if (!strcmp("http.lowspeedtime", var)) { + if (curl_low_speed_time == -1) + curl_low_speed_time = (long)git_config_int(var, value); + return 0; + } + + /* Fall back on the default ones */ + return git_default_config(var, value); +} + +static CURL* get_curl_handle(void) +{ + CURL* result = curl_easy_init(); + + curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify); +#if LIBCURL_VERSION_NUM >= 0x070907 + curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); +#endif + + if (ssl_cert != NULL) + curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); +#if LIBCURL_VERSION_NUM >= 0x070902 + if (ssl_key != NULL) + curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 + if (ssl_capath != NULL) + curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); +#endif + if (ssl_cainfo != NULL) + curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); + curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); + + if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { + curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, + curl_low_speed_limit); + curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME, + curl_low_speed_time); + } + + curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); + + return result; +} + +void http_init(void) +{ + char *low_speed_limit; + char *low_speed_time; + + curl_global_init(CURL_GLOBAL_ALL); + + pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); + no_range_header = curl_slist_append(no_range_header, "Range:"); + +#ifdef USE_CURL_MULTI + { + char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); + if (http_max_requests != NULL) + max_requests = atoi(http_max_requests); + } + + curlm = curl_multi_init(); + if (curlm == NULL) { + fprintf(stderr, "Error creating curl multi handle.\n"); + exit(1); + } +#endif + + if (getenv("GIT_SSL_NO_VERIFY")) + curl_ssl_verify = 0; + + ssl_cert = getenv("GIT_SSL_CERT"); +#if LIBCURL_VERSION_NUM >= 0x070902 + ssl_key = getenv("GIT_SSL_KEY"); +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 + ssl_capath = getenv("GIT_SSL_CAPATH"); +#endif + ssl_cainfo = getenv("GIT_SSL_CAINFO"); + + low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); + if (low_speed_limit != NULL) + curl_low_speed_limit = strtol(low_speed_limit, NULL, 10); + low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME"); + if (low_speed_time != NULL) + curl_low_speed_time = strtol(low_speed_time, NULL, 10); + + git_config(http_options); + + if (curl_ssl_verify == -1) + curl_ssl_verify = 1; + +#ifdef USE_CURL_MULTI + if (max_requests < 1) + max_requests = DEFAULT_MAX_REQUESTS; +#endif + +#ifndef NO_CURL_EASY_DUPHANDLE + curl_default = get_curl_handle(); +#endif +} + +void http_cleanup(void) +{ + struct active_request_slot *slot = active_queue_head; +#ifdef USE_CURL_MULTI + char *wait_url; +#endif + + while (slot != NULL) { +#ifdef USE_CURL_MULTI + if (slot->in_use) { + curl_easy_getinfo(slot->curl, + CURLINFO_EFFECTIVE_URL, + &wait_url); + fprintf(stderr, "Waiting for %s\n", wait_url); + run_active_slot(slot); + } +#endif + if (slot->curl != NULL) + curl_easy_cleanup(slot->curl); + slot = slot->next; + } + +#ifndef NO_CURL_EASY_DUPHANDLE + curl_easy_cleanup(curl_default); +#endif + +#ifdef USE_CURL_MULTI + curl_multi_cleanup(curlm); +#endif + curl_global_cleanup(); + +} + +struct active_request_slot *get_active_slot(void) +{ + struct active_request_slot *slot = active_queue_head; + struct active_request_slot *newslot; + +#ifdef USE_CURL_MULTI + int num_transfers; + + /* Wait for a slot to open up if the queue is full */ + while (active_requests >= max_requests) { + curl_multi_perform(curlm, &num_transfers); + if (num_transfers < active_requests) { + process_curl_messages(); + } + } +#endif + + while (slot != NULL && slot->in_use) { + slot = slot->next; + } + if (slot == NULL) { + newslot = xmalloc(sizeof(*newslot)); + newslot->curl = NULL; + newslot->in_use = 0; + newslot->next = NULL; + + slot = active_queue_head; + if (slot == NULL) { + active_queue_head = newslot; + } else { + while (slot->next != NULL) { + slot = slot->next; + } + slot->next = newslot; + } + slot = newslot; + } + + if (slot->curl == NULL) { +#ifdef NO_CURL_EASY_DUPHANDLE + slot->curl = get_curl_handle(); +#else + slot->curl = curl_easy_duphandle(curl_default); +#endif + } + + active_requests++; + slot->in_use = 1; + slot->local = NULL; + slot->callback_data = NULL; + slot->callback_func = NULL; + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header); + curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); + + return slot; +} + +int start_active_slot(struct active_request_slot *slot) +{ +#ifdef USE_CURL_MULTI + CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); + + if (curlm_result != CURLM_OK && + curlm_result != CURLM_CALL_MULTI_PERFORM) { + active_requests--; + slot->in_use = 0; + return 0; + } +#endif + return 1; +} + +#ifdef USE_CURL_MULTI +void step_active_slots(void) +{ + int num_transfers; + CURLMcode curlm_result; + + do { + curlm_result = curl_multi_perform(curlm, &num_transfers); + } while (curlm_result == CURLM_CALL_MULTI_PERFORM); + if (num_transfers < active_requests) { + process_curl_messages(); + fill_active_slots(); + } +} +#endif + +void run_active_slot(struct active_request_slot *slot) +{ +#ifdef USE_CURL_MULTI + long last_pos = 0; + long current_pos; + fd_set readfds; + fd_set writefds; + fd_set excfds; + int max_fd; + struct timeval select_timeout; + + while (slot->in_use) { + data_received = 0; + step_active_slots(); + + if (!data_received && slot->local != NULL) { + current_pos = ftell(slot->local); + if (current_pos > last_pos) + data_received++; + last_pos = current_pos; + } + + if (slot->in_use && !data_received) { + max_fd = 0; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&excfds); + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; + select(max_fd, &readfds, &writefds, + &excfds, &select_timeout); + } + } +#else + while (slot->in_use) { + slot->curl_result = curl_easy_perform(slot->curl); + finish_active_slot(slot); + } +#endif +} + +static void finish_active_slot(struct active_request_slot *slot) +{ + active_requests--; + slot->in_use = 0; + curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); + + /* Run callback if appropriate */ + if (slot->callback_func != NULL) { + slot->callback_func(slot->callback_data); + } +} + +void finish_all_active_slots(void) +{ + struct active_request_slot *slot = active_queue_head; + + while (slot != NULL) + if (slot->in_use) { + run_active_slot(slot); + slot = active_queue_head; + } else { + slot = slot->next; + } +} diff --git a/http.h b/http.h new file mode 100644 index 0000000000..ed4ea3340e --- /dev/null +++ b/http.h @@ -0,0 +1,95 @@ +#ifndef HTTP_H +#define HTTP_H + +#include "cache.h" + +#include +#include + +#if LIBCURL_VERSION_NUM >= 0x070908 +#define USE_CURL_MULTI +#define DEFAULT_MAX_REQUESTS 5 +#endif + +#if LIBCURL_VERSION_NUM < 0x070704 +#define curl_global_cleanup() do { /* nothing */ } while(0) +#endif +#if LIBCURL_VERSION_NUM < 0x070800 +#define curl_global_init(a) do { /* nothing */ } while(0) +#endif + +#if LIBCURL_VERSION_NUM < 0x070c04 +#define NO_CURL_EASY_DUPHANDLE +#endif + +struct active_request_slot +{ + CURL *curl; + FILE *local; + int in_use; + CURLcode curl_result; + long http_code; + void *callback_data; + void (*callback_func)(void *data); + struct active_request_slot *next; +}; + +struct buffer +{ + size_t posn; + size_t size; + void *buffer; +}; + +/* Curl request read/write callbacks */ +extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, + struct buffer *buffer); +extern size_t fwrite_buffer(const void *ptr, size_t eltsize, + size_t nmemb, struct buffer *buffer); +extern size_t fwrite_null(const void *ptr, size_t eltsize, + size_t nmemb, struct buffer *buffer); + +/* Slot lifecycle functions */ +extern struct active_request_slot *get_active_slot(void); +extern int start_active_slot(struct active_request_slot *slot); +extern void run_active_slot(struct active_request_slot *slot); +extern void finish_all_active_slots(void); + +#ifdef USE_CURL_MULTI +extern void fill_active_slots(void); +extern void step_active_slots(void); +#endif + +extern void http_init(void); +extern void http_cleanup(void); + +extern int data_received; +extern int active_requests; + +#ifdef USE_CURL_MULTI +extern int max_requests; +extern CURLM *curlm; +#endif +#ifndef NO_CURL_EASY_DUPHANDLE +extern CURL *curl_default; +#endif +extern char curl_errorstr[CURL_ERROR_SIZE]; + +extern int curl_ssl_verify; +extern char *ssl_cert; +#if LIBCURL_VERSION_NUM >= 0x070902 +extern char *ssl_key; +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 +extern char *ssl_capath; +#endif +extern char *ssl_cainfo; +extern long curl_low_speed_limit; +extern long curl_low_speed_time; + +extern struct curl_slist *pragma_header; +extern struct curl_slist *no_range_header; + +extern struct active_request_slot *active_queue_head; + +#endif /* HTTP_H */ diff --git a/ident.c b/ident.c index bc89e1d04c..ac1c27f199 100644 --- a/ident.c +++ b/ident.c @@ -156,7 +156,8 @@ static int copy(char *buf, int size, int offset, const char *src) return offset; } -char *get_ident(const char *name, const char *email, const char *date_str) +static const char *get_ident(const char *name, const char *email, + const char *date_str) { static char buffer[1000]; char date[50]; @@ -181,12 +182,16 @@ char *get_ident(const char *name, const char *email, const char *date_str) return buffer; } -char *git_author_info(void) +const char *git_author_info(void) { - return get_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), getenv("GIT_AUTHOR_DATE")); + return get_ident(getenv("GIT_AUTHOR_NAME"), + getenv("GIT_AUTHOR_EMAIL"), + getenv("GIT_AUTHOR_DATE")); } -char *git_committer_info(void) +const char *git_committer_info(void) { - return get_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE")); + return get_ident(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"), + getenv("GIT_COMMITTER_DATE")); } diff --git a/name-rev.c b/name-rev.c index 59194f1349..817e36b793 100644 --- a/name-rev.c +++ b/name-rev.c @@ -230,8 +230,6 @@ int main(int argc, char **argv) fwrite(p_start, p - p_start, 1, stdout); } } else if (all) { - extern struct object **objs; - extern int nr_objs; int i; for (i = 0; i < nr_objs; i++) diff --git a/pack-objects.c b/pack-objects.c index 4e941e7392..8864a31cc1 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -524,7 +524,7 @@ int main(int argc, char **argv) unsigned char sha1[20]; if (get_sha1_hex(line, sha1)) - die("expected sha1, got garbage"); + die("expected sha1, got garbage:\n %s", line); hash = 0; p = line+40; while (*p) { diff --git a/pack-redundant.c b/pack-redundant.c index fb6cb48502..793fa08096 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -11,19 +11,19 @@ static const char pack_redundant_usage[] = "git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; -int load_all_packs = 0, verbose = 0, alt_odb = 0; +static int load_all_packs = 0, verbose = 0, alt_odb = 0; struct llist_item { struct llist_item *next; - char *sha1; + unsigned char *sha1; }; -struct llist { +static struct llist { struct llist_item *front; struct llist_item *back; size_t size; } *all_objects; /* all objects which must be present in local packfiles */ -struct pack_list { +static struct pack_list { struct pack_list *next; struct packed_git *pack; struct llist *unique_objects; @@ -36,23 +36,43 @@ struct pll { size_t pl_size; }; -inline void llist_free(struct llist *list) +static struct llist_item *free_nodes = NULL; + +static inline struct llist_item *llist_item_get() +{ + struct llist_item *new; + if ( free_nodes ) { + new = free_nodes; + free_nodes = free_nodes->next; + } else + new = xmalloc(sizeof(struct llist_item)); + + return new; +} + +static inline void llist_item_put(struct llist_item *item) +{ + item->next = free_nodes; + free_nodes = item; +} + +static void llist_free(struct llist *list) { while((list->back = list->front)) { list->front = list->front->next; - free(list->back); + llist_item_put(list->back); } free(list); } -inline void llist_init(struct llist **list) +static inline void llist_init(struct llist **list) { *list = xmalloc(sizeof(struct llist)); (*list)->front = (*list)->back = NULL; (*list)->size = 0; } -struct llist * llist_copy(struct llist *list) +static struct llist * llist_copy(struct llist *list) { struct llist *ret; struct llist_item *new, *old, *prev; @@ -62,13 +82,13 @@ struct llist * llist_copy(struct llist *list) if ((ret->size = list->size) == 0) return ret; - new = ret->front = xmalloc(sizeof(struct llist_item)); + new = ret->front = llist_item_get(); new->sha1 = list->front->sha1; old = list->front->next; while (old) { prev = new; - new = xmalloc(sizeof(struct llist_item)); + new = llist_item_get(); prev->next = new; new->sha1 = old->sha1; old = old->next; @@ -79,10 +99,11 @@ struct llist * llist_copy(struct llist *list) return ret; } -inline struct llist_item * llist_insert(struct llist *list, - struct llist_item *after, char *sha1) +static inline struct llist_item * llist_insert(struct llist *list, + struct llist_item *after, + unsigned char *sha1) { - struct llist_item *new = xmalloc(sizeof(struct llist_item)); + struct llist_item *new = llist_item_get(); new->sha1 = sha1; new->next = NULL; @@ -102,13 +123,12 @@ inline struct llist_item * llist_insert(struct llist *list, return new; } -inline struct llist_item * llist_insert_back(struct llist *list, char *sha1) +static inline struct llist_item *llist_insert_back(struct llist *list, unsigned char *sha1) { return llist_insert(list, list->back, sha1); } -inline struct llist_item * llist_insert_sorted_unique(struct llist *list, - char *sha1, struct llist_item *hint) +static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, unsigned char *sha1, struct llist_item *hint) { struct llist_item *prev = NULL, *l; @@ -129,8 +149,7 @@ inline struct llist_item * llist_insert_sorted_unique(struct llist *list, } /* returns a pointer to an item in front of sha1 */ -inline struct llist_item * llist_sorted_remove(struct llist *list, char *sha1, - struct llist_item *hint) +static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint) { struct llist_item *prev, *l; @@ -153,7 +172,7 @@ redo_from_start: prev->next = l->next; if (l == list->back) list->back = prev; - free(l); + llist_item_put(l); list->size--; return prev; } @@ -164,7 +183,7 @@ redo_from_start: } /* computes A\B */ -void llist_sorted_difference_inplace(struct llist *A, +static void llist_sorted_difference_inplace(struct llist *A, struct llist *B) { struct llist_item *hint, *b; @@ -178,7 +197,7 @@ void llist_sorted_difference_inplace(struct llist *A, } } -inline struct pack_list * pack_list_insert(struct pack_list **pl, +static inline struct pack_list * pack_list_insert(struct pack_list **pl, struct pack_list *entry) { struct pack_list *p = xmalloc(sizeof(struct pack_list)); @@ -188,7 +207,7 @@ inline struct pack_list * pack_list_insert(struct pack_list **pl, return p; } -inline size_t pack_list_size(struct pack_list *pl) +static inline size_t pack_list_size(struct pack_list *pl) { size_t ret = 0; while(pl) { @@ -198,10 +217,11 @@ inline size_t pack_list_size(struct pack_list *pl) return ret; } -struct pack_list * pack_list_difference(struct pack_list *A, - struct pack_list *B) +static struct pack_list * pack_list_difference(const struct pack_list *A, + const struct pack_list *B) { - struct pack_list *ret, *pl; + struct pack_list *ret; + const struct pack_list *pl; if (A == NULL) return NULL; @@ -218,7 +238,7 @@ struct pack_list * pack_list_difference(struct pack_list *A, return ret; } -void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) +static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) { int p1_off, p2_off; void *p1_base, *p2_base; @@ -250,7 +270,7 @@ void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) } } -void pll_insert(struct pll **pll, struct pll **hint_table) +static void pll_insert(struct pll **pll, struct pll **hint_table) { struct pll *prev; int i = (*pll)->pl_size - 1; @@ -276,7 +296,7 @@ void pll_insert(struct pll **pll, struct pll **hint_table) /* all the permutations have to be free()d at the same time, * since they refer to each other */ -struct pll * get_all_permutations(struct pack_list *list) +static struct pll * get_all_permutations(struct pack_list *list) { struct pll *subset, *pll, *new_pll = NULL; /*silence warning*/ static struct pll **hint = NULL; @@ -323,15 +343,14 @@ struct pll * get_all_permutations(struct pack_list *list) return hint[0]; } -int is_superset(struct pack_list *pl, struct llist *list) +static int is_superset(struct pack_list *pl, struct llist *list) { struct llist *diff; diff = llist_copy(list); while (pl) { - llist_sorted_difference_inplace(diff, - pl->all_objects); + llist_sorted_difference_inplace(diff, pl->all_objects); if (diff->size == 0) { /* we're done */ llist_free(diff); return 1; @@ -342,7 +361,7 @@ int is_superset(struct pack_list *pl, struct llist *list) return 0; } -size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) +static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; int p1_off, p2_off; @@ -373,14 +392,14 @@ size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) } /* another O(n^2) function ... */ -size_t get_pack_redundancy(struct pack_list *pl) +static size_t get_pack_redundancy(struct pack_list *pl) { struct pack_list *subset; + size_t ret = 0; if (pl == NULL) return 0; - size_t ret = 0; while ((subset = pl->next)) { while(subset) { ret += sizeof_union(pl->pack, subset->pack); @@ -391,7 +410,7 @@ size_t get_pack_redundancy(struct pack_list *pl) return ret; } -inline size_t pack_set_bytecount(struct pack_list *pl) +static inline size_t pack_set_bytecount(struct pack_list *pl) { size_t ret = 0; while (pl) { @@ -402,7 +421,7 @@ inline size_t pack_set_bytecount(struct pack_list *pl) return ret; } -void minimize(struct pack_list **min) +static void minimize(struct pack_list **min) { struct pack_list *pl, *unique = NULL, *non_unique = NULL, *min_perm = NULL; @@ -469,16 +488,14 @@ void minimize(struct pack_list **min) } } -void load_all_objects() +static void load_all_objects(void) { struct pack_list *pl = local_packs; struct llist_item *hint, *l; - int i; llist_init(&all_objects); while (pl) { - i = 0; hint = NULL; l = pl->all_objects->front; while (l) { @@ -497,7 +514,7 @@ void load_all_objects() } /* this scales like O(n^2) */ -void cmp_local_packs() +static void cmp_local_packs(void) { struct pack_list *subset, *pl = local_packs; @@ -508,7 +525,7 @@ void cmp_local_packs() } } -void scan_alt_odb_packs() +static void scan_alt_odb_packs(void) { struct pack_list *local, *alt; @@ -524,7 +541,7 @@ void scan_alt_odb_packs() } } -struct pack_list * add_pack(struct packed_git *p) +static struct pack_list * add_pack(struct packed_git *p) { struct pack_list l; size_t off; @@ -550,7 +567,7 @@ struct pack_list * add_pack(struct packed_git *p) return pack_list_insert(&altodb_packs, &l); } -struct pack_list * add_pack_file(char *filename) +static struct pack_list * add_pack_file(char *filename) { struct packed_git *p = packed_git; @@ -565,7 +582,7 @@ struct pack_list * add_pack_file(char *filename) die("Filename %s not found in packed_git\n", filename); } -void load_all() +static void load_all(void) { struct packed_git *p = packed_git; @@ -579,6 +596,9 @@ int main(int argc, char **argv) { int i; struct pack_list *min, *red, *pl; + struct llist *ignore; + unsigned char *sha1; + char buf[42]; /* 40 byte sha1 + \n + \0 */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -621,6 +641,23 @@ int main(int argc, char **argv) if (alt_odb) scan_alt_odb_packs(); + /* ignore objects given on stdin */ + llist_init(&ignore); + if (!isatty(0)) { + while (fgets(buf, sizeof(buf), stdin)) { + sha1 = xmalloc(20); + if (get_sha1_hex(buf, sha1)) + die("Bad sha1 on stdin: %s", buf); + llist_insert_sorted_unique(ignore, sha1, NULL); + } + } + llist_sorted_difference_inplace(all_objects, ignore); + pl = local_packs; + while (pl) { + llist_sorted_difference_inplace(pl->unique_objects, ignore); + pl = pl->next; + } + minimize(&min); if (verbose) { @@ -647,6 +684,9 @@ int main(int argc, char **argv) pl->pack->pack_name); pl = pl->next; } + if (verbose) + fprintf(stderr, "%luMB of redundant packs in total.\n", + (unsigned long)pack_set_bytecount(red)/(1024*1024)); return 0; } diff --git a/path.c b/path.c index 495d17ca4c..4d889473a7 100644 --- a/path.c +++ b/path.c @@ -11,6 +11,7 @@ * which is what it's designed for. */ #include "cache.h" +#include static char pathname[PATH_MAX]; static char bad_path[] = "/bad-path/"; @@ -89,3 +90,117 @@ char *safe_strncpy(char *dest, const char *src, size_t n) return dest; } + +int validate_symref(const char *path) +{ + struct stat st; + char *buf, buffer[256]; + int len, fd; + + if (lstat(path, &st) < 0) + return -1; + + /* Make sure it is a "refs/.." symlink */ + if (S_ISLNK(st.st_mode)) { + len = readlink(path, buffer, sizeof(buffer)-1); + if (len >= 5 && !memcmp("refs/", buffer, 5)) + return 0; + return -1; + } + + /* + * Anything else, just open it and try to see if it is a symbolic ref. + */ + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + len = read(fd, buffer, sizeof(buffer)-1); + close(fd); + + /* + * Is it a symbolic ref? + */ + if (len < 4 || memcmp("ref:", buffer, 4)) + return -1; + buf = buffer + 4; + len -= 4; + while (len && isspace(*buf)) + buf++, len--; + if (len >= 5 && !memcmp("refs/", buf, 5)) + return 0; + return -1; +} + +static char *current_dir(void) +{ + return getcwd(pathname, sizeof(pathname)); +} + +static int user_chdir(char *path) +{ + char *dir = path; + + if(*dir == '~') { /* user-relative path */ + struct passwd *pw; + char *slash = strchr(dir, '/'); + + dir++; + /* '~/' and '~' (no slash) means users own home-dir */ + if(!*dir || *dir == '/') + pw = getpwuid(getuid()); + else { + if (slash) { + *slash = '\0'; + pw = getpwnam(dir); + *slash = '/'; + } + else + pw = getpwnam(dir); + } + + /* make sure we got something back that we can chdir() to */ + if(!pw || chdir(pw->pw_dir) < 0) + return -1; + + if(!slash || !slash[1]) /* no path following username */ + return 0; + + dir = slash + 1; + } + + /* ~foo/path/to/repo is now path/to/repo and we're in foo's homedir */ + if(chdir(dir) < 0) + return -1; + + return 0; +} + +char *enter_repo(char *path, int strict) +{ + if(!path) + return NULL; + + if (strict) { + if (chdir(path) < 0) + return NULL; + } + else { + if (!*path) + ; /* happy -- no chdir */ + else if (!user_chdir(path)) + ; /* happy -- as given */ + else if (!user_chdir(mkpath("%s.git", path))) + ; /* happy -- uemacs --> uemacs.git */ + else + return NULL; + (void)chdir(".git"); + } + + if(access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && + validate_symref("HEAD") == 0) { + putenv("GIT_DIR=."); + return current_dir(); + } + + return NULL; +} diff --git a/receive-pack.c b/receive-pack.c index 8f157bc3f0..1873506120 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -248,11 +248,11 @@ static void unpack(void) int main(int argc, char **argv) { int i; - const char *dir = NULL; + char *dir = NULL; argv++; for (i = 1; i < argc; i++) { - const char *arg = *argv++; + char *arg = *argv++; if (*arg == '-') { /* Do flag handling here */ @@ -265,18 +265,9 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - /* chdir to the directory. If that fails, try appending ".git" */ - if (chdir(dir) < 0) { - if (chdir(mkpath("%s.git", dir)) < 0) - die("unable to cd to %s", dir); - } - - /* If we have a ".git" directory, chdir to it */ - chdir(".git"); - putenv("GIT_DIR=."); + if(!enter_repo(dir, 0)) + die("'%s': unable to chdir or not a git archive", dir); - if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0) - die("%s doesn't appear to be a git directory", dir); write_head_info(); /* EOF */ diff --git a/refs.c b/refs.c index f324be5032..ac2619851d 100644 --- a/refs.c +++ b/refs.c @@ -10,46 +10,6 @@ #define USE_SYMLINK_HEAD 1 #endif -int validate_symref(const char *path) -{ - struct stat st; - char *buf, buffer[256]; - int len, fd; - - if (lstat(path, &st) < 0) - return -1; - - /* Make sure it is a "refs/.." symlink */ - if (S_ISLNK(st.st_mode)) { - len = readlink(path, buffer, sizeof(buffer)-1); - if (len >= 5 && !memcmp("refs/", buffer, 5)) - return 0; - return -1; - } - - /* - * Anything else, just open it and try to see if it is a symbolic ref. - */ - fd = open(path, O_RDONLY); - if (fd < 0) - return -1; - len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - - /* - * Is it a symbolic ref? - */ - if (len < 4 || memcmp("ref:", buffer, 4)) - return -1; - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - if (len >= 5 && !memcmp("refs/", buf, 5)) - return 0; - return -1; -} - const char *resolve_ref(const char *path, unsigned char *sha1, int reading) { int depth = MAXDEPTH, len; diff --git a/repo-config.c b/repo-config.c new file mode 100644 index 0000000000..b2569b7901 --- /dev/null +++ b/repo-config.c @@ -0,0 +1,116 @@ +#include "cache.h" +#include + +static const char git_config_set_usage[] = +"git-repo-config [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]"; + +static char* key = NULL; +static char* value = NULL; +static regex_t* regex = NULL; +static int do_all = 0; +static int do_not_match = 0; +static int seen = 0; + +static int show_config(const char* key_, const char* value_) +{ + if (!strcmp(key_, key) && + (regex == NULL || + (do_not_match ^ + !regexec(regex, value_, 0, NULL, 0)))) { + if (do_all) { + printf("%s\n", value_); + return 0; + } + if (seen > 0) { + fprintf(stderr, "More than one value: %s\n", value); + free(value); + } + value = strdup(value_); + seen++; + } + return 0; +} + +static int get_value(const char* key_, const char* regex_) +{ + int i; + + key = malloc(strlen(key_)+1); + for (i = 0; key_[i]; i++) + key[i] = tolower(key_[i]); + key[i] = 0; + + if (regex_) { + if (regex_[0] == '!') { + do_not_match = 1; + regex_++; + } + + regex = (regex_t*)malloc(sizeof(regex_t)); + if (regcomp(regex, regex_, REG_EXTENDED)) { + fprintf(stderr, "Invalid pattern: %s\n", regex_); + return -1; + } + } + + i = git_config(show_config); + if (value) { + printf("%s\n", value); + free(value); + } + free(key); + if (regex) { + regfree(regex); + free(regex); + } + + if (do_all) + return 0; + + return seen == 1 ? 0 : 1; +} + +int main(int argc, const char **argv) +{ + setup_git_directory(); + switch (argc) { + case 2: + return get_value(argv[1], NULL); + case 3: + if (!strcmp(argv[1], "--unset")) + return git_config_set(argv[2], NULL); + else if (!strcmp(argv[1], "--unset-all")) + return git_config_set_multivar(argv[2], NULL, NULL, 1); + else if (!strcmp(argv[1], "--get")) + return get_value(argv[2], NULL); + else if (!strcmp(argv[1], "--get-all")) { + do_all = 1; + return get_value(argv[2], NULL); + } else + + return git_config_set(argv[1], argv[2]); + case 4: + if (!strcmp(argv[1], "--unset")) + return git_config_set_multivar(argv[2], NULL, argv[3], 0); + else if (!strcmp(argv[1], "--unset-all")) + return git_config_set_multivar(argv[2], NULL, argv[3], 1); + else if (!strcmp(argv[1], "--get")) + return get_value(argv[2], argv[3]); + else if (!strcmp(argv[1], "--get-all")) { + do_all = 1; + return get_value(argv[2], argv[3]); + } else if (!strcmp(argv[1], "--replace-all")) + + return git_config_set_multivar(argv[2], argv[3], NULL, 1); + else + + return git_config_set_multivar(argv[1], argv[2], argv[3], 0); + case 5: + if (!strcmp(argv[1], "--replace-all")) + return git_config_set_multivar(argv[2], argv[3], argv[4], 1); + case 1: + default: + usage(git_config_set_usage); + } + return 0; +} diff --git a/rev-list.c b/rev-list.c index 6e6ffde396..e17f928061 100644 --- a/rev-list.c +++ b/rev-list.c @@ -124,8 +124,6 @@ static int filter_commit(struct commit * commit) stop_traversal=1; return CONTINUE; } - if (max_count != -1 && !max_count--) - return STOP; if (no_merges && (commit->parents && commit->parents->next)) return CONTINUE; if (paths && dense) { @@ -148,6 +146,9 @@ static int process_commit(struct commit * commit) return CONTINUE; } + if (max_count != -1 && !max_count--) + return STOP; + show_commit(commit); return CONTINUE; diff --git a/setup.c b/setup.c index c487d7eb9d..ab3c778e80 100644 --- a/setup.c +++ b/setup.c @@ -73,8 +73,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec) } /* - * Test it it looks like we're at the top - * level git directory. We want to see a + * Test if it looks like we're at the top level git directory. + * We want to see: * * - either a .git/objects/ directory _or_ the proper * GIT_OBJECT_DIRECTORY environment variable @@ -92,17 +92,43 @@ static int is_toplevel_directory(void) return 1; } -const char *setup_git_directory(void) +static const char *setup_git_directory_1(void) { static char cwd[PATH_MAX+1]; int len, offset; /* * If GIT_DIR is set explicitly, we're not going - * to do any discovery + * to do any discovery, but we still do repository + * validation. */ - if (getenv(GIT_DIR_ENVIRONMENT)) + if (getenv(GIT_DIR_ENVIRONMENT)) { + char path[PATH_MAX]; + int len = strlen(getenv(GIT_DIR_ENVIRONMENT)); + if (sizeof(path) - 40 < len) + die("'$%s' too big", GIT_DIR_ENVIRONMENT); + memcpy(path, getenv(GIT_DIR_ENVIRONMENT), len); + + strcpy(path + len, "/refs"); + if (access(path, X_OK)) + goto bad_dir_environ; + strcpy(path + len, "/HEAD"); + if (validate_symref(path)) + goto bad_dir_environ; + if (getenv(DB_ENVIRONMENT)) { + if (access(DB_ENVIRONMENT, X_OK)) + goto bad_dir_environ; + } + else { + strcpy(path + len, "/objects"); + if (access(path, X_OK)) + goto bad_dir_environ; + } return NULL; + bad_dir_environ: + path[len] = 0; + die("Not a git repository: '%s'", path); + } if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/') die("Unable to read current working directory"); @@ -127,3 +153,9 @@ const char *setup_git_directory(void) cwd[len] = 0; return cwd + offset; } + +const char *setup_git_directory(void) +{ + const char *retval = setup_git_directory_1(); + return retval; +} diff --git a/sha1_name.c b/sha1_name.c index be1755a70b..faac158b16 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -236,6 +236,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) NULL }; const char **p; + int found = 0; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; @@ -246,10 +247,20 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = prefix; *p; p++) { char *pathname = git_path("%s/%.*s", *p, len, str); - if (!read_ref(pathname, sha1)) - return 0; + if (!read_ref(pathname, sha1)) { + /* Must be unique; i.e. when heads/foo and + * tags/foo are both present, reject "foo". + * Note that read_ref() eventually calls + * get_sha1_hex() which can smudge initial + * part of the buffer even if what is read + * is found to be invalid halfway. + */ + if (1 < found++) + return -1; + } } - + if (found == 1) + return 0; return -1; } diff --git a/show-branch.c b/show-branch.c index 631336cd9d..d8808eefce 100644 --- a/show-branch.c +++ b/show-branch.c @@ -313,9 +313,16 @@ static int append_ref(const char *refname, const unsigned char *sha1) static int append_head_ref(const char *refname, const unsigned char *sha1) { - if (strncmp(refname, "refs/heads/", 11)) + unsigned char tmp[20]; + int ofs = 11; + if (strncmp(refname, "refs/heads/", ofs)) return 0; - return append_ref(refname + 11, sha1); + /* If both heads/foo and tags/foo exists, get_sha1 would + * get confused. + */ + if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) + ofs = 5; + return append_ref(refname + ofs, sha1); } static int append_tag_ref(const char *refname, const unsigned char *sha1) @@ -470,7 +477,7 @@ int main(int ac, char **av) if (MAX_REVS <= num_rev) die("cannot handle more than %d revs.", MAX_REVS); if (get_sha1(ref_name[num_rev], revkey)) - usage(show_branch_usage); + die("'%s' is not a valid ref.\n", ref_name[num_rev]); commit = lookup_commit_reference(revkey); if (!commit) die("cannot find commit %s (%s)", diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh new file mode 100644 index 0000000000..5e994ff009 --- /dev/null +++ b/t/t1300-repo-config.sh @@ -0,0 +1,271 @@ +#!/bin/sh +# +# Copyright (c) 2005 Johannes Schindelin +# + +test_description='Test git-repo-config in different settings' + +. ./test-lib.sh + +test -f .git/config && rm .git/config + +git-repo-config core.penguin "little blue" + +cat > expect << EOF +# +# This is the config file +# + +[core] + penguin = little blue +EOF + +test_expect_success 'initial' 'cmp .git/config expect' + +git-repo-config Core.Movie BadPhysics + +cat > expect << EOF +# +# This is the config file +# + +[core] + penguin = little blue + Movie = BadPhysics +EOF + +test_expect_success 'mixed case' 'cmp .git/config expect' + +git-repo-config Cores.WhatEver Second + +cat > expect << EOF +# +# This is the config file +# + +[core] + penguin = little blue + Movie = BadPhysics +[Cores] + WhatEver = Second +EOF + +test_expect_success 'similar section' 'cmp .git/config expect' + +git-repo-config CORE.UPPERCASE true + +cat > expect << EOF +# +# This is the config file +# + +[core] + penguin = little blue + Movie = BadPhysics + UPPERCASE = true +[Cores] + WhatEver = Second +EOF + +test_expect_success 'similar section' 'cmp .git/config expect' + +test_expect_success 'replace with non-match' \ + 'git-repo-config core.penguin kingpin !blue' + +test_expect_success 'replace with non-match (actually matching)' \ + 'git-repo-config core.penguin "very blue" !kingpin' + +cat > expect << EOF +# +# This is the config file +# + +[core] + penguin = very blue + Movie = BadPhysics + UPPERCASE = true + penguin = kingpin +[Cores] + WhatEver = Second +EOF + +test_expect_success 'non-match result' 'cmp .git/config expect' + +cat > .git/config << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment + haha ="beta" # last silly comment +haha = hello + haha = bello +[nextSection] noNewline = ouch +EOF + +cp .git/config .git/config2 + +test_expect_success 'multiple unset' \ + 'git-repo-config --unset-all beta.haha' + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment +[nextSection] noNewline = ouch +EOF + +test_expect_success 'multiple unset is correct' 'cmp .git/config expect' + +mv .git/config2 .git/config + +test_expect_success '--replace-all' \ + 'git-repo-config --replace-all beta.haha gamma' + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment + haha = gamma +[nextSection] noNewline = ouch +EOF + +test_expect_success 'all replaced' 'cmp .git/config expect' + +git-repo-config beta.haha alpha + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment + haha = alpha +[nextSection] noNewline = ouch +EOF + +test_expect_success 'really mean test' 'cmp .git/config expect' + +git-repo-config nextsection.nonewline wow + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment + haha = alpha +[nextSection] + nonewline = wow +EOF + +test_expect_success 'really really mean test' 'cmp .git/config expect' + +test_expect_success 'get value' 'test alpha = $(git-repo-config beta.haha)' +git-repo-config --unset beta.haha + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment +[nextSection] + nonewline = wow +EOF + +test_expect_success 'unset' 'cmp .git/config expect' + +git-repo-config nextsection.NoNewLine "wow2 for me" "for me$" + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment +[nextSection] + nonewline = wow + NoNewLine = wow2 for me +EOF + +test_expect_success 'multivar' 'cmp .git/config expect' + +test_expect_success 'non-match' \ + 'git-repo-config --get nextsection.nonewline !for' + +test_expect_success 'non-match value' \ + 'test wow = $(git-repo-config --get nextsection.nonewline !for)' + +test_expect_failure 'ambiguous get' \ + 'git-repo-config --get nextsection.nonewline' + +test_expect_success 'get multivar' \ + 'git-repo-config --get-all nextsection.nonewline' + +git-repo-config nextsection.nonewline "wow3" "wow$" + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment +[nextSection] + nonewline = wow3 + NoNewLine = wow2 for me +EOF + +test_expect_success 'multivar replace' 'cmp .git/config expect' + +test_expect_failure 'ambiguous value' 'git-repo-config nextsection.nonewline' + +test_expect_failure 'ambiguous unset' \ + 'git-repo-config --unset nextsection.nonewline' + +test_expect_failure 'invalid unset' \ + 'git-repo-config --unset somesection.nonewline' + +git-repo-config --unset nextsection.nonewline "wow3$" + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment +[nextSection] + NoNewLine = wow2 for me +EOF + +test_expect_success 'multivar unset' 'cmp .git/config expect' + +test_expect_failure 'invalid key' 'git-repo-config inval.2key blabla' + +test_expect_success 'correct key' 'git-repo-config 123456.a123 987' + +test_expect_success 'hierarchical section' \ + 'git-repo-config 1.2.3.alpha beta' + +cat > expect << EOF +[beta] ; silly comment # another comment +noIndent= sillyValue ; 'nother silly comment + +# empty line + ; comment +[nextSection] + NoNewLine = wow2 for me +[123456] + a123 = 987 +[1.2.3] + alpha = beta +EOF + +test_expect_success 'hierarchical section value' 'cmp .git/config expect' + +test_done + diff --git a/templates/hooks--update b/templates/hooks--update index 3f38b82a47..6db555f658 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -8,14 +8,14 @@ # (2) make this file executable by "chmod +x update". # -recipient="commit-list@mydomain.xz" +recipient="commit-list@example.com" if expr "$2" : '0*$' >/dev/null then echo "Created a new ref, with the following commits:" git-rev-list --pretty "$3" else - $base=$(git-merge-base "$2" "$3") + base=$(git-merge-base "$2" "$3") case "$base" in "$2") echo "New commits:" @@ -24,8 +24,7 @@ else echo "Rebased ref, commits from common ancestor:" ;; esac -fi -git-rev-list --pretty "$3" "^$base" + git-rev-list --pretty "$3" "^$base" fi | mail -s "Changes to ref $1" "$recipient" exit 0 diff --git a/update-index.c b/update-index.c index 5bbc3de289..11b7f6a516 100644 --- a/update-index.c +++ b/update-index.c @@ -338,7 +338,7 @@ static void read_index_info(int line_termination) struct strbuf buf; strbuf_init(&buf); while (1) { - char *ptr; + char *ptr, *tab; char *path_name; unsigned char sha1[20]; unsigned int mode; @@ -348,12 +348,15 @@ static void read_index_info(int line_termination) break; mode = strtoul(buf.buf, &ptr, 8); - if (ptr == buf.buf || *ptr != ' ' || - get_sha1_hex(ptr + 1, sha1) || - ptr[41] != '\t') + if (ptr == buf.buf || *ptr != ' ') goto bad_line; - ptr += 42; + tab = strchr(ptr, '\t'); + if (!tab || tab - ptr < 41) + goto bad_line; + if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ') + goto bad_line; + ptr = tab + 1; if (line_termination && ptr[0] == '"') path_name = unquote_c_style(ptr, NULL); diff --git a/upload-pack.c b/upload-pack.c index be63132804..1834b6ba8c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -248,7 +248,7 @@ static int upload_pack(void) int main(int argc, char **argv) { - const char *dir; + char *dir; int i; int strict = 0; @@ -275,18 +275,9 @@ int main(int argc, char **argv) usage(upload_pack_usage); dir = argv[i]; - /* chdir to the directory. If that fails, try appending ".git" */ - if (chdir(dir) < 0) { - if (strict || chdir(mkpath("%s.git", dir)) < 0) - die("git-upload-pack unable to chdir to %s", dir); - } - if (!strict) - chdir(".git"); - - if (access("objects", X_OK) || access("refs", X_OK)) - die("git-upload-pack: %s doesn't seem to be a git archive", dir); + if (!enter_repo(dir, strict)) + die("'%s': unable to chdir or not a git archive", dir); - putenv("GIT_DIR=."); upload_pack(); return 0; } diff --git a/var.c b/var.c index 51cf86a584..59da56da0f 100644 --- a/var.c +++ b/var.c @@ -12,7 +12,7 @@ static const char var_usage[] = "git-var [-l | ]"; struct git_var { const char *name; - char *(*read)(void); + const char *(*read)(void); }; static struct git_var git_vars[] = { { "GIT_COMMITTER_IDENT", git_committer_info }, @@ -57,6 +57,8 @@ int main(int argc, char **argv) if (argc != 2) { usage(var_usage); } + + setup_git_directory(); setup_ident(); val = NULL;