diff --git a/Documentation/config.txt b/Documentation/config.txt index d9e73da2a7..9d3c71c3b8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -301,7 +301,16 @@ imap:: The configuration variables in the 'imap' section are described in gitlink:git-imap-send[1]. -receive.denyNonFastforwads:: +receive.unpackLimit:: + If the number of objects received in a push is below this + limit then the objects will be unpacked into loose object + files. However if the number of received objects equals or + exceeds this limit then the received pack will be stored as + a pack, after adding any missing delta bases. Storing the + pack from a push can make the push operation complete faster, + especially on slow filesystems. + +receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is not a fast forward. Use this to prevent such an update via a push, even if that push is forced. This configuration variable is diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index f973c64313..86060472ad 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -75,6 +75,7 @@ OPTIONS this option is used, neither the `origin` branch nor the default `remotes/origin` file is created. +--origin :: -o :: Instead of using the branch name 'origin' to keep track of the upstream repository, use instead. Note diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 9cd43f105b..2df581c2c9 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -54,7 +54,7 @@ If '--cached' is specified, it allows you to ask: For example, let's say that you have worked on your working directory, updated some files in the index and are ready to commit. You want to see exactly -*what* you are going to commit is without having to write a new tree +*what* you are going to commit, without having to write a new tree object and compare it that way, and to do that, you just do git-diff-index --cached HEAD @@ -68,7 +68,7 @@ matches my working directory. But doing a "git-diff-index" does: -100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c +100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c -You can trivially see that the above is a rename. +You can see easily that the above is a rename. In fact, "git-diff-index --cached" *should* always be entirely equivalent to actually doing a "git-write-tree" and comparing that. Except this one is much diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index bff9aa6939..3e6cd880b0 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -32,7 +32,8 @@ OPTIONS -k:: Do not invoke 'git-unpack-objects' on received data, but create a single packfile out of it instead, and store it - in the object database. + in the object database. If provided twice then the pack is + locked against repacking. --exec=:: Use this to specify the path to 'git-upload-pack' on the diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 1235416e09..2229ee86b7 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -69,6 +69,17 @@ OPTIONS locate any which have outlived their usefulness. +Note +---- + +Once the index has been created, the list of object names is sorted +and the SHA1 hash of that list is printed to stdout. If --stdin was +also used then this is prefixed by either "pack\t", or "keep\t" if a +new .keep file was successfully created. This is useful to remove a +.keep file used as a lock to prevent the race with gitlink:git-repack[1] +mentioned above. + + Author ------ Written by Sergey Vlasov diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 8a1ab61e94..8199615dde 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -3,19 +3,19 @@ git-repo-config(1) NAME ---- -git-repo-config - Get and set options in .git/config +git-repo-config - Get and set repository or global options. SYNOPSIS -------- [verse] -'git-repo-config' [type] name [value [value_regex]] -'git-repo-config' [type] --replace-all name [value [value_regex]] -'git-repo-config' [type] --get name [value_regex] -'git-repo-config' [type] --get-all name [value_regex] -'git-repo-config' [type] --unset name [value_regex] -'git-repo-config' [type] --unset-all name [value_regex] -'git-repo-config' -l | --list +'git-repo-config' [--global] [type] name [value [value_regex]] +'git-repo-config' [--global] [type] --replace-all name [value [value_regex]] +'git-repo-config' [--global] [type] --get name [value_regex] +'git-repo-config' [--global] [type] --get-all name [value_regex] +'git-repo-config' [--global] [type] --unset name [value_regex] +'git-repo-config' [--global] [type] --unset-all name [value_regex] +'git-repo-config' [--global] -l | --list DESCRIPTION ----------- @@ -41,8 +41,9 @@ This command will fail if: . Can not write to .git/config, . 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. +. you try to unset an option which does not exist, +. you try to unset/set an option for which multiple lines match, or +. you use --global option without $HOME being properly set. OPTIONS @@ -64,14 +65,17 @@ OPTIONS --get-regexp:: Like --get-all, but interprets the name as a regular expression. +--global:: + Use global ~/.gitconfig file rather than the repository .git/config. + --unset:: - Remove the line matching the key from .git/config. + Remove the line matching the key from config file. --unset-all:: - Remove all matching lines from .git/config. + Remove all matching lines from config file. -l, --list:: - List all variables set in .git/config. + List all variables set in config file. ENVIRONMENT @@ -79,6 +83,7 @@ ENVIRONMENT GIT_CONFIG:: Take the configuration from the given file instead of .git/config. + Using the "--global" option forces this to ~/.gitconfig. GIT_CONFIG_LOCAL:: Currently the same as $GIT_CONFIG; when Git will support global diff --git a/Makefile b/Makefile index 28a5d913ca..80d4a4e28c 100644 --- a/Makefile +++ b/Makefile @@ -260,7 +260,7 @@ LIB_OBJS = \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ - fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ + revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o diff --git a/builtin-branch.c b/builtin-branch.c index e028a5390f..368b68ec91 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -103,6 +103,7 @@ static int ref_cmp(const void *r1, const void *r2) static void print_ref_list(int remote_only) { int i; + char c; if (remote_only) for_each_remote_ref(append_ref, NULL); @@ -112,10 +113,11 @@ static void print_ref_list(int remote_only) qsort(ref_list, ref_index, sizeof(char *), ref_cmp); for (i = 0; i < ref_index; i++) { + c = ' '; if (!strcmp(ref_list[i], head)) - printf("* %s\n", ref_list[i]); - else - printf(" %s\n", ref_list[i]); + c = '*'; + + printf("%c %s\n", c, ref_list[i]); } } diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 93d3d7eef0..173bf38735 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -59,6 +59,8 @@ static struct { { "taggername" }, { "taggeremail" }, { "taggerdate", FIELD_TIME }, + { "creator" }, + { "creatordate", FIELD_TIME }, { "subject" }, { "body" }, { "contents" }, @@ -401,6 +403,29 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru else if (!strcmp(name + wholen, "date")) grab_date(wholine, v); } + + /* For a tag or a commit object, if "creator" or "creatordate" is + * requested, do something special. + */ + if (strcmp(who, "tagger") && strcmp(who, "committer")) + return; /* "author" for commit object is not wanted */ + if (!wholine) + wholine = find_wholine(who, wholen, buf, sz); + if (!wholine) + return; + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + + if (!strcmp(name, "creatordate")) + grab_date(wholine, v); + else if (!strcmp(name, "creator")) + v->s = copy_line(wholine); + } } static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 1087657674..042d2718f9 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -12,6 +12,7 @@ struct ref_to_prune { struct pack_refs_cb_data { int prune; + int all; struct ref_to_prune *ref_to_prune; FILE *refs_file; }; @@ -29,6 +30,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, { struct pack_refs_cb_data *cb = cb_data; + if (!cb->all && strncmp(path, "refs/tags/", 10)) + return 0; /* Do not pack the symbolic refs */ if (!(flags & REF_ISSYMREF)) fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); @@ -68,7 +71,6 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) { int fd, i; struct pack_refs_cb_data cbdata; - int (*iterate_ref)(each_ref_fn, void *) = for_each_tag_ref; memset(&cbdata, 0, sizeof(cbdata)); @@ -79,7 +81,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--all")) { - iterate_ref = for_each_ref; + cbdata.all = 1; continue; } /* perhaps other parameters later... */ @@ -93,7 +95,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); - iterate_ref(handle_one_ref, &cbdata); + for_each_ref(handle_one_ref, &cbdata); fflush(cbdata.refs_file); fsync(fd); fclose(cbdata.refs_file); diff --git a/builtin-repo-config.c b/builtin-repo-config.c index f60cee1dc5..7b6e5725ae 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -3,7 +3,7 @@ #include static const char git_config_set_usage[] = -"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; +"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; static char *key; static regex_t *key_regexp; @@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix) type = T_BOOL; else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) return git_config(show_all_config); - else + else if (!strcmp(argv[1], "--global")) { + char *home = getenv("HOME"); + if (home) { + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + setenv("GIT_CONFIG", user_config, 1); + free(user_config); + } else { + die("$HOME not set"); + } + } else break; argc--; argv++; diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 74a90c1129..e6d7574844 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -371,6 +371,21 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) recover = 1; continue; } + if (!strncmp(arg, "--pack_header=", 14)) { + struct pack_header *hdr; + char *c; + + hdr = (struct pack_header *)buffer; + hdr->hdr_signature = htonl(PACK_SIGNATURE); + hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); + if (*c != ',') + die("bad %s", arg); + hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); + if (*c) + die("bad %s", arg); + len = sizeof(*hdr); + continue; + } usage(unpack_usage); } diff --git a/cache.h b/cache.h index e997a85005..f2ec5c8c14 100644 --- a/cache.h +++ b/cache.h @@ -376,6 +376,7 @@ extern struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path); extern void prepare_packed_git(void); +extern void reprepare_packed_git(void); extern void install_packed_git(struct packed_git *pack); extern struct packed_git *find_sha1_pack(const unsigned char *sha1, @@ -415,10 +416,6 @@ extern int copy_fd(int ifd, int ofd); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); -/* Finish off pack transfer receiving end */ -extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int); -extern int receive_keep_pack(int fd[2], const char *me, int quiet, int); - /* pager.c */ extern void setup_pager(void); extern int pager_in_use; diff --git a/fetch-clone.c b/fetch-clone.c deleted file mode 100644 index f629d8dacc..0000000000 --- a/fetch-clone.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" -#include "pkt-line.h" -#include "sideband.h" -#include - -static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) -{ - pid_t side_pid; - - if (!sideband) { - fd[0] = xd[0]; - fd[1] = xd[1]; - return 0; - } - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through our fd[0]. - */ - if (pipe(fd) < 0) - die("%s: unable to set up pipe", me); - side_pid = fork(); - if (side_pid < 0) - die("%s: unable to fork off sideband demultiplexer", me); - if (!side_pid) { - /* subprocess */ - close(fd[0]); - if (xd[0] != xd[1]) - close(xd[1]); - if (recv_sideband(me, xd[0], fd[1], 2)) - exit(1); - exit(0); - } - close(xd[0]); - close(fd[1]); - fd[1] = xd[1]; - return side_pid; -} - -static int get_pack(int xd[2], const char *me, int sideband, const char **argv) -{ - int status; - pid_t pid, side_pid; - int fd[2]; - - side_pid = setup_sideband(sideband, me, fd, xd); - pid = fork(); - if (pid < 0) - die("%s: unable to fork off %s", me, argv[0]); - if (!pid) { - dup2(fd[0], 0); - close(fd[0]); - close(fd[1]); - execv_git_cmd(argv); - die("%s exec failed", argv[0]); - } - close(fd[0]); - close(fd[1]); - while (waitpid(pid, &status, 0) < 0) { - if (errno != EINTR) - die("waiting for %s: %s", argv[0], strerror(errno)); - } - if (WIFEXITED(status)) { - int code = WEXITSTATUS(status); - if (code) - die("%s died with error code %d", argv[0], code); - return 0; - } - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - die("%s died of signal %d", argv[0], sig); - } - die("%s died of unnatural causes %d", argv[0], status); -} - -int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband) -{ - const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL }; - return get_pack(xd, me, sideband, argv); -} - -int receive_keep_pack(int xd[2], const char *me, int quiet, int sideband) -{ - const char *argv[5] = { "index-pack", "--stdin", "--fix-thin", - quiet ? NULL : "-v", NULL }; - return get_pack(xd, me, sideband, argv); -} diff --git a/fetch-pack.c b/fetch-pack.c index 36ea092d1b..0a169dce85 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -3,6 +3,9 @@ #include "pkt-line.h" #include "commit.h" #include "tag.h" +#include "exec_cmd.h" +#include "sideband.h" +#include static int keep_pack; static int quiet; @@ -416,6 +419,103 @@ static int everything_local(struct ref **refs, int nr_match, char **match) return retval; } +static pid_t setup_sideband(int fd[2], int xd[2]) +{ + pid_t side_pid; + + if (!use_sideband) { + fd[0] = xd[0]; + fd[1] = xd[1]; + return 0; + } + /* xd[] is talking with upload-pack; subprocess reads from + * xd[0], spits out band#2 to stderr, and feeds us band#1 + * through our fd[0]. + */ + if (pipe(fd) < 0) + die("fetch-pack: unable to set up pipe"); + side_pid = fork(); + if (side_pid < 0) + die("fetch-pack: unable to fork off sideband demultiplexer"); + if (!side_pid) { + /* subprocess */ + close(fd[0]); + if (xd[0] != xd[1]) + close(xd[1]); + if (recv_sideband("fetch-pack", xd[0], fd[1], 2)) + exit(1); + exit(0); + } + close(xd[0]); + close(fd[1]); + fd[1] = xd[1]; + return side_pid; +} + +static int get_pack(int xd[2], const char **argv) +{ + int status; + pid_t pid, side_pid; + int fd[2]; + + side_pid = setup_sideband(fd, xd); + pid = fork(); + if (pid < 0) + die("fetch-pack: unable to fork off %s", argv[0]); + if (!pid) { + dup2(fd[0], 0); + close(fd[0]); + close(fd[1]); + execv_git_cmd(argv); + die("%s exec failed", argv[0]); + } + close(fd[0]); + close(fd[1]); + while (waitpid(pid, &status, 0) < 0) { + if (errno != EINTR) + die("waiting for %s: %s", argv[0], strerror(errno)); + } + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + if (code) + die("%s died with error code %d", argv[0], code); + return 0; + } + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + die("%s died of signal %d", argv[0], sig); + } + die("%s died of unnatural causes %d", argv[0], status); +} + +static int explode_rx_pack(int xd[2]) +{ + const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL }; + return get_pack(xd, argv); +} + +static int keep_rx_pack(int xd[2]) +{ + const char *argv[6]; + char keep_arg[256]; + int n = 0; + + argv[n++] = "index-pack"; + argv[n++] = "--stdin"; + if (!quiet) + argv[n++] = "-v"; + if (use_thin_pack) + argv[n++] = "--fix-thin"; + if (keep_pack > 1) { + int s = sprintf(keep_arg, "--keep=fetch-pack %i on ", getpid()); + if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) + strcpy(keep_arg + s, "localhost"); + argv[n++] = keep_arg; + } + argv[n] = NULL; + return get_pack(xd, argv); +} + static int fetch_pack(int fd[2], int nr_match, char **match) { struct ref *ref; @@ -447,17 +547,13 @@ static int fetch_pack(int fd[2], int nr_match, char **match) goto all_done; } if (find_common(fd, sha1, ref) < 0) - if (!keep_pack) + if (keep_pack != 1) /* When cloning, it is not unusual to have * no common commit. */ fprintf(stderr, "warning: no common commits\n"); - if (keep_pack) - status = receive_keep_pack(fd, "git-fetch-pack", quiet, use_sideband); - else - status = receive_unpack_pack(fd, "git-fetch-pack", quiet, use_sideband); - + status = (keep_pack) ? keep_rx_pack(fd) : explode_rx_pack(fd); if (status) die("git-fetch-pack: fetch failed."); @@ -494,7 +590,7 @@ int main(int argc, char **argv) continue; } if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { - keep_pack = 1; + keep_pack++; continue; } if (!strcmp("--thin", arg)) { diff --git a/git-fetch.sh b/git-fetch.sh index 539dff6ee0..7442dd2ca5 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -20,7 +20,7 @@ verbose= update_head_ok= exec= upload_pack= -keep=--thin +keep= while case "$#" in 0) break ;; esac do case "$1" in @@ -51,7 +51,7 @@ do verbose=Yes ;; -k|--k|--ke|--kee|--keep) - keep=--keep + keep='-k -k' ;; --reflog-action=*) rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` @@ -368,9 +368,10 @@ fetch_main () { ;; # we are already done. *) ( : subshell because we muck with IFS + pack_lockfile= IFS=" $LF" ( - git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote" + git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote" ) | while read sha1 remote_name do @@ -378,6 +379,12 @@ fetch_main () { failed) echo >&2 "Fetch failure: $remote" exit 1 ;; + # special line coming from index-pack with the pack name + pack) + continue ;; + keep) + pack_lockfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep" + continue ;; esac found= single_force= @@ -408,6 +415,7 @@ fetch_main () { append_fetch_head "$sha1" "$remote" \ "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" done + if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi ) || exit ;; esac diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d93f0f2884..256f4e93dd 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -570,12 +570,17 @@ sub esc_url { } # replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html { +sub esc_html ($;%) { my $str = shift; + my %opts = @_; + $str = to_utf8($str); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) + if ($opts{'-nbsp'}) { + $str =~ s/ / /g; + } return $str; } @@ -800,7 +805,7 @@ sub format_diff_line { $diff_class = " incomplete"; } $line = untabify($line); - return "
" . esc_html($line) . "
\n"; + return "
" . esc_html($line, -nbsp=>1) . "
\n"; } ## ---------------------------------------------------------------------- @@ -1318,47 +1323,88 @@ sub parse_ls_tree_line ($;%) { ## ...................................................................... ## parse to array of hashes functions -sub git_get_refs_list { - my $type = shift || ""; - my %refs; - my @reflist; +sub git_get_heads_list { + my $limit = shift; + my @headslist; - my @refs; - open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + open my $fd, '-|', git_cmd(), 'for-each-ref', + ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', + '--format=%(objectname) %(refname) %(subject)%00%(committer)', + 'refs/heads' or return; while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) { - if (defined $refs{$1}) { - push @{$refs{$1}}, $2; - } else { - $refs{$1} = [ $2 ]; - } + my %ref_item; - if (! $4) { # unpeeled, direct reference - push @refs, { hash => $1, name => $3 }; # without type - } elsif ($3 eq $refs[-1]{'name'}) { - # most likely a tag is followed by its peeled - # (deref) one, and when that happens we know the - # previous one was of type 'tag'. - $refs[-1]{'type'} = "tag"; - } + chomp $line; + my ($refinfo, $committerinfo) = split(/\0/, $line); + my ($hash, $name, $title) = split(' ', $refinfo, 3); + my ($committer, $epoch, $tz) = + ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); + $name =~ s!^refs/heads/!!; + + $ref_item{'name'} = $name; + $ref_item{'id'} = $hash; + $ref_item{'title'} = $title || '(no commit message)'; + $ref_item{'epoch'} = $epoch; + if ($epoch) { + $ref_item{'age'} = age_string(time - $ref_item{'epoch'}); + } else { + $ref_item{'age'} = "unknown"; } + + push @headslist, \%ref_item; } close $fd; - foreach my $ref (@refs) { - my $ref_file = $ref->{'name'}; - my $ref_id = $ref->{'hash'}; + return wantarray ? @headslist : \@headslist; +} - my $type = $ref->{'type'} || git_get_type($ref_id) || next; - my %ref_item = parse_ref($ref_file, $ref_id, $type); +sub git_get_tags_list { + my $limit = shift; + my @tagslist; - push @reflist, \%ref_item; + open my $fd, '-|', git_cmd(), 'for-each-ref', + ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate', + '--format=%(objectname) %(objecttype) %(refname) '. + '%(*objectname) %(*objecttype) %(subject)%00%(creator)', + 'refs/tags' + or return; + while (my $line = <$fd>) { + my %ref_item; + + chomp $line; + my ($refinfo, $creatorinfo) = split(/\0/, $line); + my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6); + my ($creator, $epoch, $tz) = + ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/); + $name =~ s!^refs/tags/!!; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $id; + $ref_item{'name'} = $name; + if ($type eq "tag") { + $ref_item{'subject'} = $title; + $ref_item{'reftype'} = $reftype; + $ref_item{'refid'} = $refid; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'refid'} = $id; + } + + if ($type eq "tag" || $type eq "commit") { + $ref_item{'epoch'} = $epoch; + if ($epoch) { + $ref_item{'age'} = age_string(time - $ref_item{'epoch'}); + } else { + $ref_item{'age'} = "unknown"; + } + } + + push @tagslist, \%ref_item; } - # sort refs by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return (\@reflist, \%refs); + close $fd; + + return wantarray ? @tagslist : \@tagslist; } ## ---------------------------------------------------------------------- @@ -1969,19 +2015,19 @@ sub git_difftree_body { print "\n"; print "$mode_chnge\n"; print ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not onlu mode changed) + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, hash_base=>$hash, file_name=>$diff{'file'})}, @@ -2012,19 +2058,19 @@ sub git_difftree_body { -class => "list"}, esc_html($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]\n" . ""; - if ($diff{'to_id'} ne $diff{'from_id'}) { - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not only pure rename or copy) + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff") . + " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'from_file'})}, @@ -2075,13 +2121,6 @@ sub git_patchset_body { } $patch_idx++; - # for now, no extended header, hence we skip empty patches - # companion to next LINE if $in_header; - if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change - $in_header = 1; - next LINE; - } - if ($diffinfo->{'status'} eq "A") { # added print "
" . file_type($diffinfo->{'to_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash, @@ -2406,8 +2445,7 @@ sub git_tags_body { for (my $i = $from; $i <= $to; $i++) { my $entry = $taglist->[$i]; my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; + my $comment = $tag{'subject'}; my $comment_short; if (defined $comment) { $comment_short = chop_str($comment, 30, 5); @@ -2440,7 +2478,7 @@ sub git_tags_body { $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); if ($tag{'reftype'} eq "commit") { print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . - " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log"); + " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log"); } elsif ($tag{'reftype'} eq "blob") { print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); } @@ -2465,23 +2503,23 @@ sub git_heads_body { my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $headlist->[$i]; - my %tag = %$entry; - my $curr = $tag{'id'} eq $head; + my %ref = %$entry; + my $curr = $ref{'id'} eq $head; if ($alternate) { print "\n"; } else { print "\n"; } $alternate ^= 1; - print "$tag{'age'}\n" . - ($tag{'id'} eq $head ? "" : "") . - $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), - -class => "list name"},esc_html($tag{'name'})) . + print "$ref{'age'}\n" . + ($curr ? "" : "") . + $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}), + -class => "list name"},esc_html($ref{'name'})) . "\n" . "" . - $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . - $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") . + $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " . + $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") . "\n" . ""; } @@ -2570,18 +2608,9 @@ sub git_summary { my $owner = git_get_project_owner($project); - my ($reflist, $refs) = git_get_refs_list(); - - my @taglist; - my @headlist; - foreach my $ref (@$reflist) { - if ($ref->{'name'} =~ s!^heads/!!) { - push @headlist, $ref; - } else { - $ref->{'name'} =~ s!^tags/!!; - push @taglist, $ref; - } - } + my $refs = git_get_references(); + my @taglist = git_get_tags_list(15); + my @headlist = git_get_heads_list(15); my @forklist; if (gitweb_check_feature('forks')) { @forklist = git_get_projects_list($project); @@ -2884,9 +2913,9 @@ sub git_tags { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my ($taglist) = git_get_refs_list("tags"); - if (@$taglist) { - git_tags_body($taglist); + my @tagslist = git_get_tags_list(); + if (@tagslist) { + git_tags_body(\@tagslist); } git_footer_html(); } @@ -2897,9 +2926,9 @@ sub git_heads { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my ($headlist) = git_get_refs_list("heads"); - if (@$headlist) { - git_heads_body($headlist, $head); + my @headslist = git_get_heads_list(); + if (@headslist) { + git_heads_body(\@headslist, $head); } git_footer_html(); } @@ -3012,7 +3041,7 @@ sub git_blob { $nr++; $line = untabify($line); printf "
%4i %s
\n", - $nr, $nr, $nr, esc_html($line); + $nr, $nr, $nr, esc_html($line, -nbsp=>1); } close $fd or print "Reading blob failed.\n"; diff --git a/index-pack.c b/index-pack.c index b37dd78729..042aea8842 100644 --- a/index-pack.c +++ b/index-pack.c @@ -757,6 +757,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, const char *keep_name, const char *keep_msg, unsigned char *sha1) { + char *report = "pack"; char name[PATH_MAX]; int err; @@ -767,18 +768,6 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (err) die("error while closing pack file: %s", strerror(errno)); chmod(curr_pack_name, 0444); - - /* - * Let's just mimic git-unpack-objects here and write - * the last part of the buffer to stdout. - */ - while (input_len) { - err = xwrite(1, input_buffer + input_offset, input_len); - if (err <= 0) - break; - input_len -= err; - input_offset += err; - } } if (keep_msg) { @@ -788,14 +777,18 @@ static void final(const char *final_pack_name, const char *curr_pack_name, get_object_directory(), sha1_to_hex(sha1)); keep_name = name; } - keep_fd = open(keep_name, O_RDWR | O_CREAT, 0600); - if (keep_fd < 0) - die("cannot write keep file"); - if (keep_msg_len > 0) { - write_or_die(keep_fd, keep_msg, keep_msg_len); - write_or_die(keep_fd, "\n", 1); + keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600); + if (keep_fd < 0) { + if (errno != EEXIST) + die("cannot write keep file"); + } else { + if (keep_msg_len > 0) { + write_or_die(keep_fd, keep_msg, keep_msg_len); + write_or_die(keep_fd, "\n", 1); + } + close(keep_fd); + report = "keep"; } - close(keep_fd); } if (final_pack_name != curr_pack_name) { @@ -818,6 +811,27 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (move_temp_to_file(curr_index_name, final_index_name)) die("cannot store index file"); } + + if (!from_stdin) { + printf("%s\n", sha1_to_hex(sha1)); + } else { + char buf[48]; + int len = snprintf(buf, sizeof(buf), "%s\t%s\n", + report, sha1_to_hex(sha1)); + xwrite(1, buf, len); + + /* + * Let's just mimic git-unpack-objects here and write + * the last part of the input buffer to stdout. + */ + while (input_len) { + err = xwrite(1, input_buffer + input_offset, input_len); + if (err <= 0) + break; + input_len -= err; + input_offset += err; + } + } } int main(int argc, char **argv) @@ -841,6 +855,19 @@ int main(int argc, char **argv) keep_msg = ""; } else if (!strncmp(arg, "--keep=", 7)) { keep_msg = arg + 7; + } else if (!strncmp(arg, "--pack_header=", 14)) { + struct pack_header *hdr; + char *c; + + hdr = (struct pack_header *)input_buffer; + hdr->hdr_signature = htonl(PACK_SIGNATURE); + hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); + if (*c != ',') + die("bad %s", arg); + hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); + if (*c) + die("bad %s", arg); + input_len = sizeof(*hdr); } else if (!strcmp(arg, "-v")) { verbose = 1; } else if (!strcmp(arg, "-o")) { @@ -921,8 +948,5 @@ int main(int argc, char **argv) free(index_name_buf); free(keep_name_buf); - if (!from_stdin) - printf("%s\n", sha1_to_hex(sha1)); - return 0; } diff --git a/receive-pack.c b/receive-pack.c index e2ba8a8ae3..8bb5683f7d 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -1,15 +1,17 @@ #include "cache.h" +#include "pack.h" #include "refs.h" #include "pkt-line.h" #include "run-command.h" +#include "exec_cmd.h" #include "commit.h" #include "object.h" +#include static const char receive_pack_usage[] = "git-receive-pack "; -static const char *unpacker[] = { "unpack-objects", NULL }; - static int deny_non_fast_forwards = 0; +static int unpack_limit = 5000; static int report_status; static char capabilities[] = "report-status"; @@ -25,6 +27,12 @@ static int receive_pack_config(const char *var, const char *value) return 0; } + if (strcmp(var, "receive.unpacklimit") == 0) + { + unpack_limit = git_config_int(var, value); + return 0; + } + return 0; } @@ -227,27 +235,127 @@ static void read_head_info(void) } } +static const char *parse_pack_header(struct pack_header *hdr) +{ + char *c = (char*)hdr; + ssize_t remaining = sizeof(struct pack_header); + do { + ssize_t r = xread(0, c, remaining); + if (r <= 0) + return "eof before pack header was fully read"; + remaining -= r; + c += r; + } while (remaining > 0); + if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) + return "protocol error (pack signature mismatch detected)"; + if (!pack_version_ok(hdr->hdr_version)) + return "protocol error (pack version unsupported)"; + return NULL; +} + +static const char *pack_lockfile; + static const char *unpack(void) { - int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); + struct pack_header hdr; + const char *hdr_err; + char hdr_arg[38]; - switch (code) { - case 0: - return NULL; - case -ERR_RUN_COMMAND_FORK: - return "unpack fork failed"; - case -ERR_RUN_COMMAND_EXEC: - return "unpack execute failed"; - case -ERR_RUN_COMMAND_WAITPID: - return "waitpid failed"; - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - return "waitpid is confused"; - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - return "unpacker died of signal"; - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - return "unpacker died strangely"; - default: - return "unpacker exited with error code"; + hdr_err = parse_pack_header(&hdr); + if (hdr_err) + return hdr_err; + snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u", + ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries)); + + if (ntohl(hdr.hdr_entries) < unpack_limit) { + int code; + const char *unpacker[3]; + unpacker[0] = "unpack-objects"; + unpacker[1] = hdr_arg; + unpacker[2] = NULL; + code = run_command_v_opt(1, unpacker, RUN_GIT_CMD); + switch (code) { + case 0: + return NULL; + case -ERR_RUN_COMMAND_FORK: + return "unpack fork failed"; + case -ERR_RUN_COMMAND_EXEC: + return "unpack execute failed"; + case -ERR_RUN_COMMAND_WAITPID: + return "waitpid failed"; + case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: + return "waitpid is confused"; + case -ERR_RUN_COMMAND_WAITPID_SIGNAL: + return "unpacker died of signal"; + case -ERR_RUN_COMMAND_WAITPID_NOEXIT: + return "unpacker died strangely"; + default: + return "unpacker exited with error code"; + } + } else { + const char *keeper[6]; + int fd[2], s, len, status; + pid_t pid; + char keep_arg[256]; + char packname[46]; + + s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid()); + if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) + strcpy(keep_arg + s, "localhost"); + + keeper[0] = "index-pack"; + keeper[1] = "--stdin"; + keeper[2] = "--fix-thin"; + keeper[3] = hdr_arg; + keeper[4] = keep_arg; + keeper[5] = NULL; + + if (pipe(fd) < 0) + return "index-pack pipe failed"; + pid = fork(); + if (pid < 0) + return "index-pack fork failed"; + if (!pid) { + dup2(fd[1], 1); + close(fd[1]); + close(fd[0]); + execv_git_cmd(keeper); + die("execv of index-pack failed"); + } + close(fd[1]); + + /* + * The first thing we expects from index-pack's output + * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where + * %40s is the newly created pack SHA1 name. In the "keep" + * case, we need it to remove the corresponding .keep file + * later on. If we don't get that then tough luck with it. + */ + for (len = 0; + len < 46 && (s = xread(fd[0], packname+len, 46-len)) > 0; + len += s); + close(fd[0]); + if (len == 46 && packname[45] == '\n' && + memcmp(packname, "keep\t", 5) == 0) { + char path[PATH_MAX]; + packname[45] = 0; + snprintf(path, sizeof(path), "%s/pack/pack-%s.keep", + get_object_directory(), packname + 5); + pack_lockfile = xstrdup(path); + } + + /* Then wrap our index-pack process. */ + while (waitpid(pid, &status, 0) < 0) + if (errno != EINTR) + return "waitpid failed"; + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + if (code) + return "index-pack exited with error code"; + reprepare_packed_git(); + return NULL; + } + return "index-pack abnormal exit"; } } @@ -303,6 +411,8 @@ int main(int argc, char **argv) const char *unpack_status = unpack(); if (!unpack_status) execute_commands(); + if (pack_lockfile) + unlink(pack_lockfile); if (report_status) report(unpack_status); } diff --git a/sha1_file.c b/sha1_file.c index 86a06d45a6..6ea59b5588 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -663,7 +663,7 @@ void prepare_packed_git(void) prepare_packed_git_run_once = 1; } -static void reprepare_packed_git(void) +void reprepare_packed_git(void) { prepare_packed_git_run_once = 0; prepare_packed_git(); @@ -1417,8 +1417,7 @@ static int link_temp_to_file(const char *tmpfile, const char *filename) dir = strrchr(filename, '/'); if (dir) { *dir = 0; - mkdir(filename, 0777); - if (adjust_shared_perm(filename)) { + if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) { *dir = '/'; return -2; }