From: Junio C Hamano Date: Mon, 27 Mar 2006 07:44:28 +0000 (-0800) Subject: Merge branch 'lt/diffgen' into next X-Git-Tag: v1.3.0-rc1~2 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/ac93bfc3b64a8a3756263ecdadad3e7a117be597?hp=3467fec516e0549cfe7ae3ed8bd981a7e5f528f4 Merge branch 'lt/diffgen' into next * lt/diffgen: add clean and ignore rules for xdiff/ Remove dependency on a file named "-lz" --- diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index e813f84202..796d049be6 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -14,9 +14,9 @@ SYNOPSIS (-[c|d|o|i|s|u|k|m])\* [-x |--exclude=] [-X |--exclude-from=] - [--exclude-per-directory=] + [--exclude-per-directory=] [--error-unmatch] - [--full-name] [--] []\* + [--full-name] [--abbrev] [--] []\* DESCRIPTION ----------- @@ -52,6 +52,9 @@ OPTIONS If a whole directory is classified as "other", show just its name (with a trailing slash) and not its whole contents. +--no-empty-directory:: + Do not list empty directories. Has no effect without --directory. + -u|--unmerged:: Show unmerged files in the output (forces --stage) @@ -98,6 +101,11 @@ OPTIONS option forces paths to be output relative to the project top directory. +--abbrev[=]:: + Instead of showing the full 40-byte hexadecimal object + lines, show only handful hexdigits prefix. + Non default number of digits can be specified with --abbrev=. + --:: Do not interpret any more arguments as options. diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index 5bf6d8b613..018c401953 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -8,7 +8,9 @@ git-ls-tree - Lists the contents of a tree object SYNOPSIS -------- -'git-ls-tree' [-d] [-r] [-t] [-z] [--name-only] [--name-status] [paths...] +'git-ls-tree' [-d] [-r] [-t] [-z] + [--name-only] [--name-status] [--full-name] [--abbrev=[]] + [paths...] DESCRIPTION ----------- @@ -40,6 +42,11 @@ OPTIONS --name-status:: List only filenames (instead of the "long" output), one per line. +--abbrev[=]:: + Instead of showing the full 40-byte hexadecimal object + lines, show only handful hexdigits prefix. + Non default number of digits can be specified with --abbrev=. + paths:: When paths are given, show them (note that this isn't really raw pathnames, but rather a list of patterns to match). Otherwise diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index b36276c7ed..4a7e67a4d2 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -48,6 +48,18 @@ would be: / D---E---F---G master +In case of conflict, git-rebase will stop at the first problematic commit +and leave conflict markers in the tree. After resolving the conflict manually +and updating the index with the desired resolution, you can continue the +rebasing process with + + git am --resolved --3way + +Alternatively, you can undo the git-rebase with + + git reset --hard ORIG_HEAD + rm -r .dotest + OPTIONS ------- :: diff --git a/Makefile b/Makefile index 02d290d334..4edb383321 100644 --- a/Makefile +++ b/Makefile @@ -191,7 +191,7 @@ LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a LIB_H = \ - blob.h cache.h commit.h count-delta.h csum-file.h delta.h \ + blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h @@ -201,7 +201,7 @@ DIFF_OBJS = \ diffcore-delta.o LIB_OBJS = \ - blob.o commit.o connect.o count-delta.o csum-file.o \ + blob.o commit.o connect.o csum-file.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o \ quote.o read-cache.o refs.o run-command.o \ diff --git a/blame.c b/blame.c index 7e88833a37..396defccc7 100644 --- a/blame.c +++ b/blame.c @@ -752,6 +752,7 @@ int main(int argc, const char **argv) int found_rename; const char* prefix = setup_git_directory(); + git_config(git_default_config); for(i = 1; i < argc; i++) { if(options) { diff --git a/cache.h b/cache.h index 1f962809b0..255e6b5cc7 100644 --- a/cache.h +++ b/cache.h @@ -165,6 +165,7 @@ extern void rollback_index_file(struct cache_file *); extern int trust_executable_bit; extern int assume_unchanged; extern int only_use_symrefs; +extern int warn_ambiguous_refs; extern int diff_rename_limit_default; extern int shared_repository; extern const char *apply_default_whitespace; diff --git a/cat-file.c b/cat-file.c index 1a613f3ee5..761111eb0f 100644 --- a/cat-file.c +++ b/cat-file.c @@ -100,6 +100,7 @@ int main(int argc, char **argv) int opt; setup_git_directory(); + git_config(git_default_config); if (argc != 3 || get_sha1(argv[2], sha1)) usage("git-cat-file [-t|-s|-e|-p|] "); diff --git a/config.c b/config.c index 7dbdce1966..95ec34923d 100644 --- a/config.c +++ b/config.c @@ -232,6 +232,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.warnambiguousrefs")) { + warn_ambiguous_refs = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "user.name")) { strncpy(git_default_name, value, sizeof(git_default_name)); return 0; diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index f3fc3ec1a9..3e5733eed9 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -8,7 +8,7 @@ $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $REV_DIR/; $AUTHOR = 'Eric Wong '; -$VERSION = '0.10.0'; +$VERSION = '0.11.0'; $GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git"; # make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; @@ -217,9 +217,8 @@ sub fetch { push @log_args, '--stop-on-copy' unless $_no_stop_copy; my $svn_log = svn_log_raw(@log_args); - @$svn_log = sort { $a->{revision} <=> $b->{revision} } @$svn_log; - my $base = shift @$svn_log or croak "No base revision!\n"; + my $base = next_log_entry($svn_log) or croak "No base revision!\n"; my $last_commit = undef; unless (-d $SVN_WC) { svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC); @@ -234,18 +233,22 @@ sub fetch { } my @svn_up = qw(svn up); push @svn_up, '--ignore-externals' unless $_no_ignore_ext; - my $last_rev = $base->{revision}; - foreach my $log_msg (@$svn_log) { - assert_svn_wc_clean($last_rev, $last_commit); - $last_rev = $log_msg->{revision}; - sys(@svn_up,"-r$last_rev"); + my $last = $base; + while (my $log_msg = next_log_entry($svn_log)) { + assert_svn_wc_clean($last->{revision}, $last_commit); + if ($last->{revision} >= $log_msg->{revision}) { + croak "Out of order: last >= current: ", + "$last->{revision} >= $log_msg->{revision}\n"; + } + sys(@svn_up,"-r$log_msg->{revision}"); $last_commit = git_commit($log_msg, $last_commit, @parents); + $last = $log_msg; } - assert_svn_wc_clean($last_rev, $last_commit); + assert_svn_wc_clean($last->{revision}, $last_commit); unless (-e "$GIT_DIR/refs/heads/master") { sys(qw(git-update-ref refs/heads/master),$last_commit); } - return pop @$svn_log; + return $last; } sub commit { @@ -708,49 +711,61 @@ sub svn_commit_tree { return fetch("$rev_committed=$commit")->{revision}; } +# read the entire log into a temporary file (which is removed ASAP) +# and store the file handle + parser state sub svn_log_raw { my (@log_args) = @_; - my $pid = open my $log_fh,'-|'; + my $log_fh = IO::File->new_tmpfile or croak $!; + my $pid = fork; defined $pid or croak $!; - - if ($pid == 0) { + if (!$pid) { + open STDOUT, '>&', $log_fh or croak $!; exec (qw(svn log), @log_args) or croak $! } + waitpid $pid, 0; + croak if $?; + seek $log_fh, 0, 0 or croak $!; + return { state => 'sep', fh => $log_fh }; +} + +sub next_log_entry { + my $log = shift; # retval of svn_log_raw() + my $ret = undef; + my $fh = $log->{fh}; - my @svn_log; - my $state = 'sep'; - while (<$log_fh>) { + while (<$fh>) { chomp; if (/^\-{72}$/) { - if ($state eq 'msg') { - if ($svn_log[$#svn_log]->{lines}) { - $svn_log[$#svn_log]->{msg} .= $_."\n"; - unless(--$svn_log[$#svn_log]->{lines}) { - $state = 'sep'; + if ($log->{state} eq 'msg') { + if ($ret->{lines}) { + $ret->{msg} .= $_."\n"; + unless(--$ret->{lines}) { + $log->{state} = 'sep'; } } else { croak "Log parse error at: $_\n", - $svn_log[$#svn_log]->{revision}, + $ret->{revision}, "\n"; } next; } - if ($state ne 'sep') { + if ($log->{state} ne 'sep') { croak "Log parse error at: $_\n", - "state: $state\n", - $svn_log[$#svn_log]->{revision}, + "state: $log->{state}\n", + $ret->{revision}, "\n"; } - $state = 'rev'; + $log->{state} = 'rev'; # if we have an empty log message, put something there: - if (@svn_log) { - $svn_log[$#svn_log]->{msg} ||= "\n"; - delete $svn_log[$#svn_log]->{lines}; + if ($ret) { + $ret->{msg} ||= "\n"; + delete $ret->{lines}; + return $ret; } next; } - if ($state eq 'rev' && s/^r(\d+)\s*\|\s*//) { + if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) { my $rev = $1; my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3); ($lines) = ($lines =~ /(\d+)/); @@ -758,36 +773,34 @@ sub svn_log_raw { /(\d{4})\-(\d\d)\-(\d\d)\s (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) or croak "Failed to parse date: $date\n"; - my %log_msg = ( revision => $rev, + $ret = { revision => $rev, date => "$tz $Y-$m-$d $H:$M:$S", author => $author, lines => $lines, - msg => '' ); + msg => '' }; if (defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in ", "$_authors file\n"; } - push @svn_log, \%log_msg; - $state = 'msg_start'; + $log->{state} = 'msg_start'; next; } # skip the first blank line of the message: - if ($state eq 'msg_start' && /^$/) { - $state = 'msg'; - } elsif ($state eq 'msg') { - if ($svn_log[$#svn_log]->{lines}) { - $svn_log[$#svn_log]->{msg} .= $_."\n"; - unless (--$svn_log[$#svn_log]->{lines}) { - $state = 'sep'; + if ($log->{state} eq 'msg_start' && /^$/) { + $log->{state} = 'msg'; + } elsif ($log->{state} eq 'msg') { + if ($ret->{lines}) { + $ret->{msg} .= $_."\n"; + unless (--$ret->{lines}) { + $log->{state} = 'sep'; } } else { croak "Log parse error at: $_\n", - $svn_log[$#svn_log]->{revision},"\n"; + $ret->{revision},"\n"; } } } - close $log_fh or croak $?; - return \@svn_log; + return $ret; } sub svn_info { @@ -1114,9 +1127,13 @@ sub load_authors { Data structures: -@svn_log = array of log_msg hashes +$svn_log hashref (as returned by svn_log_raw) +{ + fh => file handle of the log file, + state => state of the log file parser (sep/msg/rev/msg_start...) +} -$log_msg hash +$log_msg hashref as returned by next_log_entry($svn_log) { msg => 'whitespace-formatted log entry ', # trailing newline is preserved diff --git a/count-delta.c b/count-delta.c deleted file mode 100644 index 058a2aadb1..0000000000 --- a/count-delta.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2005 Junio C Hamano - * The delta-parsing part is almost straight copy of patch-delta.c - * which is (C) 2005 Nicolas Pitre . - */ -#include -#include -#include -#include "delta.h" -#include "count-delta.h" - -/* - * NOTE. We do not _interpret_ delta fully. As an approximation, we - * just count the number of bytes that are copied from the source, and - * the number of literal data bytes that are inserted. - * - * Number of bytes that are _not_ copied from the source is deletion, - * and number of inserted literal bytes are addition, so sum of them - * is the extent of damage. - */ -int count_delta(void *delta_buf, unsigned long delta_size, - unsigned long *src_copied, unsigned long *literal_added) -{ - unsigned long copied_from_source, added_literal; - const unsigned char *data, *top; - unsigned char cmd; - unsigned long src_size, dst_size, out; - - if (delta_size < DELTA_SIZE_MIN) - return -1; - - data = delta_buf; - top = delta_buf + delta_size; - - src_size = get_delta_hdr_size(&data); - dst_size = get_delta_hdr_size(&data); - - added_literal = copied_from_source = out = 0; - while (data < top) { - cmd = *data++; - if (cmd & 0x80) { - unsigned long cp_off = 0, cp_size = 0; - if (cmd & 0x01) cp_off = *data++; - if (cmd & 0x02) cp_off |= (*data++ << 8); - if (cmd & 0x04) cp_off |= (*data++ << 16); - if (cmd & 0x08) cp_off |= (*data++ << 24); - if (cmd & 0x10) cp_size = *data++; - if (cmd & 0x20) cp_size |= (*data++ << 8); - if (cmd & 0x40) cp_size |= (*data++ << 16); - if (cp_size == 0) cp_size = 0x10000; - - copied_from_source += cp_size; - out += cp_size; - } else { - /* write literal into dst */ - added_literal += cmd; - out += cmd; - data += cmd; - } - } - - /* sanity check */ - if (data != top || out != dst_size) - return -1; - - /* delete size is what was _not_ copied from source. - * edit size is that and literal additions. - */ - *src_copied = copied_from_source; - *literal_added = added_literal; - return 0; -} diff --git a/count-delta.h b/count-delta.h deleted file mode 100644 index 7359629827..0000000000 --- a/count-delta.h +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (C) 2005 Junio C Hamano - */ -#ifndef COUNT_DELTA_H -#define COUNT_DELTA_H - -int count_delta(void *, unsigned long, - unsigned long *src_copied, unsigned long *literal_added); - -#endif diff --git a/diff.c b/diff.c index 562ca01218..5eae0947f3 100644 --- a/diff.c +++ b/diff.c @@ -484,6 +484,8 @@ void diff_free_filespec_data(struct diff_filespec *s) munmap(s->data, s->size); s->should_free = s->should_munmap = 0; s->data = NULL; + free(s->cnt_data); + s->cnt_data = NULL; } static void prep_temp_blob(struct diff_tempfile *temp, diff --git a/diffcore-break.c b/diffcore-break.c index 0fc2b860be..ed0e14c6d8 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -45,8 +45,8 @@ static int should_break(struct diff_filespec *src, * The value we return is 1 if we want the pair to be broken, * or 0 if we do not. */ - unsigned long delta_size, base_size, src_copied, literal_added; - int to_break = 0; + unsigned long delta_size, base_size, src_copied, literal_added, + src_removed; *merge_score_p = 0; /* assume no deletion --- "do not break" * is the default. @@ -68,37 +68,45 @@ static int should_break(struct diff_filespec *src, if (diffcore_count_changes(src->data, src->size, dst->data, dst->size, + NULL, NULL, 0, &src_copied, &literal_added)) return 0; + /* sanity */ + if (src->size < src_copied) + src_copied = src->size; + if (dst->size < literal_added + src_copied) { + if (src_copied < dst->size) + literal_added = dst->size - src_copied; + else + literal_added = 0; + } + src_removed = src->size - src_copied; + /* Compute merge-score, which is "how much is removed * from the source material". The clean-up stage will * merge the surviving pair together if the score is * less than the minimum, after rename/copy runs. */ - if (src->size <= src_copied) - ; /* all copied, nothing removed */ - else { - delta_size = src->size - src_copied; - *merge_score_p = delta_size * MAX_SCORE / src->size; - } - + *merge_score_p = src_removed * MAX_SCORE / src->size; + /* Extent of damage, which counts both inserts and * deletes. */ - if (src->size + literal_added <= src_copied) - delta_size = 0; /* avoid wrapping around */ - else - delta_size = (src->size - src_copied) + literal_added; - - /* We break if the edit exceeds the minimum. - * i.e. (break_score / MAX_SCORE < delta_size / base_size) + delta_size = src_removed + literal_added; + if (delta_size * MAX_SCORE / base_size < break_score) + return 0; + + /* If you removed a lot without adding new material, that is + * not really a rewrite. */ - if (break_score * base_size < delta_size * MAX_SCORE) - to_break = 1; + if ((src->size * break_score < src_removed * MAX_SCORE) && + (literal_added * 20 < src_removed) && + (literal_added * 20 < src_copied)) + return 0; - return to_break; + return 1; } void diffcore_break(int break_score) diff --git a/diffcore-delta.c b/diffcore-delta.c index 1e6a6911ec..7338a40c59 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -1,43 +1,213 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" -#include "delta.h" -#include "count-delta.h" - -static int diffcore_count_changes_1(void *src, unsigned long src_size, - void *dst, unsigned long dst_size, - unsigned long delta_limit, - unsigned long *src_copied, - unsigned long *literal_added) + +/* + * Idea here is very simple. + * + * We have total of (sz-N+1) N-byte overlapping sequences in buf whose + * size is sz. If the same N-byte sequence appears in both source and + * destination, we say the byte that starts that sequence is shared + * between them (i.e. copied from source to destination). + * + * For each possible N-byte sequence, if the source buffer has more + * instances of it than the destination buffer, that means the + * difference are the number of bytes not copied from source to + * destination. If the counts are the same, everything was copied + * from source to destination. If the destination has more, + * everything was copied, and destination added more. + * + * We are doing an approximation so we do not really have to waste + * memory by actually storing the sequence. We just hash them into + * somewhere around 2^16 hashbuckets and count the occurrences. + * + * The length of the sequence is arbitrarily set to 8 for now. + */ + +/* Wild guess at the initial hash size */ +#define INITIAL_HASH_SIZE 9 + +/* We leave more room in smaller hash but do not let it + * grow to have unused hole too much. + */ +#define INITIAL_FREE(sz_log2) ((1<<(sz_log2))*(sz_log2-3)/(sz_log2)) + +/* A prime rather carefully chosen between 2^16..2^17, so that + * HASHBASE < INITIAL_FREE(17). We want to keep the maximum hashtable + * size under the current 2<<17 maximum, which can hold this many + * different values before overflowing to hashtable of size 2<<18. + */ +#define HASHBASE 107927 + +struct spanhash { + unsigned int hashval; + unsigned int cnt; +}; +struct spanhash_top { + int alloc_log2; + int free; + struct spanhash data[FLEX_ARRAY]; +}; + +static struct spanhash *spanhash_find(struct spanhash_top *top, + unsigned int hashval) { - void *delta; - unsigned long delta_size; - - delta = diff_delta(src, src_size, - dst, dst_size, - &delta_size, delta_limit); - if (!delta) - /* If delta_limit is exceeded, we have too much differences */ - return -1; - - /* Estimate the edit size by interpreting delta. */ - if (count_delta(delta, delta_size, src_copied, literal_added)) { - free(delta); - return -1; + int sz = 1 << top->alloc_log2; + int bucket = hashval & (sz - 1); + while (1) { + struct spanhash *h = &(top->data[bucket++]); + if (!h->cnt) + return NULL; + if (h->hashval == hashval) + return h; + if (sz <= bucket) + bucket = 0; } - free(delta); - return 0; +} + +static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig) +{ + struct spanhash_top *new; + int i; + int osz = 1 << orig->alloc_log2; + int sz = osz << 1; + + new = xmalloc(sizeof(*orig) + sizeof(struct spanhash) * sz); + new->alloc_log2 = orig->alloc_log2 + 1; + new->free = INITIAL_FREE(new->alloc_log2); + memset(new->data, 0, sizeof(struct spanhash) * sz); + for (i = 0; i < osz; i++) { + struct spanhash *o = &(orig->data[i]); + int bucket; + if (!o->cnt) + continue; + bucket = o->hashval & (sz - 1); + while (1) { + struct spanhash *h = &(new->data[bucket++]); + if (!h->cnt) { + h->hashval = o->hashval; + h->cnt = o->cnt; + new->free--; + break; + } + if (sz <= bucket) + bucket = 0; + } + } + free(orig); + return new; +} + +static struct spanhash_top *add_spanhash(struct spanhash_top *top, + unsigned int hashval, int cnt) +{ + int bucket, lim; + struct spanhash *h; + + lim = (1 << top->alloc_log2); + bucket = hashval & (lim - 1); + while (1) { + h = &(top->data[bucket++]); + if (!h->cnt) { + h->hashval = hashval; + h->cnt = cnt; + top->free--; + if (top->free < 0) + return spanhash_rehash(top); + return top; + } + if (h->hashval == hashval) { + h->cnt += cnt; + return top; + } + if (lim <= bucket) + bucket = 0; + } +} + +static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz) +{ + int i, n; + unsigned int accum1, accum2, hashval; + struct spanhash_top *hash; + + i = INITIAL_HASH_SIZE; + hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<alloc_log2 = i; + hash->free = INITIAL_FREE(i); + memset(hash->data, 0, sizeof(struct spanhash) * (1<> 25); + accum2 = (accum2 << 7) ^ (old_1 >> 25); + accum1 += c; + if (++n < 64 && c != '\n') + continue; + hashval = (accum1 + accum2 * 0x61) % HASHBASE; + hash = add_spanhash(hash, hashval, n); + n = 0; + accum1 = accum2 = 0; + } + return hash; } int diffcore_count_changes(void *src, unsigned long src_size, void *dst, unsigned long dst_size, + void **src_count_p, + void **dst_count_p, unsigned long delta_limit, unsigned long *src_copied, unsigned long *literal_added) { - return diffcore_count_changes_1(src, src_size, - dst, dst_size, - delta_limit, - src_copied, - literal_added); + int i, ssz; + struct spanhash_top *src_count, *dst_count; + unsigned long sc, la; + + src_count = dst_count = NULL; + if (src_count_p) + src_count = *src_count_p; + if (!src_count) { + src_count = hash_chars(src, src_size); + if (src_count_p) + *src_count_p = src_count; + } + if (dst_count_p) + dst_count = *dst_count_p; + if (!dst_count) { + dst_count = hash_chars(dst, dst_size); + if (dst_count_p) + *dst_count_p = dst_count; + } + sc = la = 0; + + ssz = 1 << src_count->alloc_log2; + for (i = 0; i < ssz; i++) { + struct spanhash *s = &(src_count->data[i]); + struct spanhash *d; + unsigned dst_cnt, src_cnt; + if (!s->cnt) + continue; + src_cnt = s->cnt; + d = spanhash_find(dst_count, s->hashval); + dst_cnt = d ? d->cnt : 0; + if (src_cnt < dst_cnt) { + la += dst_cnt - src_cnt; + sc += src_cnt; + } + else + sc += dst_cnt; + } + + if (!src_count_p) + free(src_count); + if (!dst_count_p) + free(dst_count); + *src_copied = sc; + *literal_added = la; + return 0; } diff --git a/diffcore-rename.c b/diffcore-rename.c index 55cf1c37f3..e992698720 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -133,7 +133,7 @@ static int estimate_similarity(struct diff_filespec *src, * match than anything else; the destination does not even * call into this function in that case. */ - unsigned long delta_size, base_size, src_copied, literal_added; + unsigned long max_size, delta_size, base_size, src_copied, literal_added; unsigned long delta_limit; int score; @@ -144,9 +144,9 @@ static int estimate_similarity(struct diff_filespec *src, if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) return 0; - delta_size = ((src->size < dst->size) ? - (dst->size - src->size) : (src->size - dst->size)); + max_size = ((src->size > dst->size) ? src->size : dst->size); base_size = ((src->size < dst->size) ? src->size : dst->size); + delta_size = max_size - base_size; /* We would not consider edits that change the file size so * drastically. delta_size must be smaller than @@ -166,23 +166,18 @@ static int estimate_similarity(struct diff_filespec *src, delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE; if (diffcore_count_changes(src->data, src->size, dst->data, dst->size, + &src->cnt_data, &dst->cnt_data, delta_limit, &src_copied, &literal_added)) return 0; - /* Extent of damage */ - if (src->size + literal_added < src_copied) - delta_size = 0; - else - delta_size = (src->size - src_copied) + literal_added; - - /* - * Now we will give some score to it. 100% edit gets 0 points - * and 0% edit gets MAX_SCORE points. + /* How similar are they? + * what percentage of material in dst are from source? */ - score = MAX_SCORE - (MAX_SCORE * delta_size / base_size); - if (score < 0) return 0; - if (MAX_SCORE < score) return MAX_SCORE; + if (!dst->size) + score = 0; /* should not happen */ + else + score = src_copied * MAX_SCORE / max_size; return score; } @@ -310,6 +305,8 @@ void diffcore_rename(struct diff_options *options) m->score = estimate_similarity(one, two, minimum_score); } + /* We do not need the text anymore */ + diff_free_filespec_data(two); dst_cnt++; } /* cost matrix sorted by most to least similar pair */ diff --git a/diffcore.h b/diffcore.h index dba4f17658..73c7842cc7 100644 --- a/diffcore.h +++ b/diffcore.h @@ -17,8 +17,8 @@ */ #define MAX_SCORE 60000.0 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */ -#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%)*/ -#define DEFAULT_MERGE_SCORE 48000 /* maximum for break-merge to happen (80%)*/ +#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%) */ +#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen 60%) */ #define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */ @@ -26,6 +26,7 @@ struct diff_filespec { unsigned char sha1[20]; char *path; void *data; + void *cnt_data; unsigned long size; int xfrm_flags; /* for use by the xfrm */ unsigned short mode; /* file mode */ @@ -103,6 +104,8 @@ void diff_debug_queue(const char *, struct diff_queue_struct *); extern int diffcore_count_changes(void *src, unsigned long src_size, void *dst, unsigned long dst_size, + void **src_count_p, + void **dst_count_p, unsigned long delta_limit, unsigned long *src_copied, unsigned long *literal_added); diff --git a/environment.c b/environment.c index 16c08f0697..6df647862c 100644 --- a/environment.c +++ b/environment.c @@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME]; int trust_executable_bit = 1; int assume_unchanged = 0; int only_use_symrefs = 0; +int warn_ambiguous_refs = 1; int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = 0; diff --git a/fetch-pack.c b/fetch-pack.c index 535de10660..a3bcad016f 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -7,8 +7,9 @@ static int keep_pack; static int quiet; static int verbose; +static int fetch_all; static const char fetch_pack_usage[] = -"git-fetch-pack [-q] [-v] [-k] [--thin] [--exec=upload-pack] [host:]directory ..."; +"git-fetch-pack [--all] [-q] [-v] [-k] [--thin] [--exec=upload-pack] [host:]directory ..."; static const char *exec = "git-upload-pack"; #define COMPLETE (1U << 0) @@ -266,8 +267,9 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) for (prev = NULL, current = *refs; current; current = next) { next = current->next; if ((!memcmp(current->name, "refs/", 5) && - check_ref_format(current->name + 5)) || - !path_match(current->name, nr_match, match)) { + check_ref_format(current->name + 5)) || + (!fetch_all && + !path_match(current->name, nr_match, match))) { if (prev == NULL) *refs = next; else @@ -376,7 +378,11 @@ static int fetch_pack(int fd[2], int nr_match, char **match) goto all_done; } if (find_common(fd, sha1, ref) < 0) - fprintf(stderr, "warning: no common commits\n"); + if (!keep_pack) + /* 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); @@ -426,6 +432,10 @@ int main(int argc, char **argv) use_thin_pack = 1; continue; } + if (!strcmp("--all", arg)) { + fetch_all = 1; + continue; + } if (!strcmp("-v", arg)) { verbose = 1; continue; diff --git a/git-clone.sh b/git-clone.sh index 4ed861d576..6887321972 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -9,7 +9,7 @@ unset CDPATH usage() { - echo >&2 "Usage: $0 [--bare] [-l [-s]] [-q] [-u ] [-o ] [-n] []" + echo >&2 "Usage: $0 [--use-separate-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [-o ] [-n] []" exit 1 } @@ -40,13 +40,62 @@ Perhaps git-update-server-info needs to be run there?" do name=`expr "$refname" : 'refs/\(.*\)'` && case "$name" in - *^*) ;; - *) - git-http-fetch -v -a -w "$name" "$name" "$1/" || exit 1 + *^*) continue;; esac + if test -n "$use_separate_remote" && + branch_name=`expr "$name" : 'heads/\(.*\)'` + then + tname="remotes/$origin/$branch_name" + else + tname=$name + fi + git-http-fetch -v -a -w "$tname" "$name" "$1/" || exit 1 done <"$clone_tmp/refs" rm -fr "$clone_tmp" + http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" +} + +# Read git-fetch-pack -k output and store the remote branches. +copy_refs=' +use File::Path qw(mkpath); +use File::Basename qw(dirname); +my $git_dir = $ARGV[0]; +my $use_separate_remote = $ARGV[1]; +my $origin = $ARGV[2]; + +my $branch_top = ($use_separate_remote ? "remotes/$origin" : "heads"); +my $tag_top = "tags"; + +sub store { + my ($sha1, $name, $top) = @_; + $name = "$git_dir/refs/$top/$name"; + mkpath(dirname($name)); + open O, ">", "$name"; + print O "$sha1\n"; + close O; +} + +open FH, "<", "$git_dir/CLONE_HEAD"; +while () { + my ($sha1, $name) = /^([0-9a-f]{40})\s(.*)$/; + next if ($name =~ /\^\173/); + if ($name eq "HEAD") { + open O, ">", "$git_dir/REMOTE_HEAD"; + print O "$sha1\n"; + close O; + next; + } + if ($name =~ s/^refs\/heads\///) { + store($sha1, $name, $branch_top); + next; + } + if ($name =~ s/^refs\/tags\///) { + store($sha1, $name, $tag_top); + next; + } } +close FH; +' quiet= use_local=no @@ -54,8 +103,10 @@ local_shared=no no_checkout= upload_pack= bare= -origin=origin +reference= +origin= origin_override= +use_separate_remote= while case "$#,$1" in 0,*) break ;; @@ -68,9 +119,21 @@ while *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) local_shared=yes; use_local=yes ;; *,-q|*,--quiet) quiet=-q ;; + *,--use-separate-remote) + use_separate_remote=t ;; 1,-o) usage;; + 1,--reference) usage ;; + *,--reference) + shift; reference="$1" ;; + *,--reference=*) + reference=`expr "$1" : '--reference=\(.*\)'` ;; *,-o) - git-check-ref-format "$2" || { + case "$2" in + */*) + echo >&2 "'$2' is not suitable for an origin name" + exit 1 + esac + git-check-ref-format "heads/$2" || { echo >&2 "'$2' is not suitable for a branch name" exit 1 } @@ -100,9 +163,19 @@ then echo >&2 '--bare and -o $origin options are incompatible.' exit 1 fi + if test t = "$use_separate_remote" + then + echo >&2 '--bare and --use-separate-remote options are incompatible.' + exit 1 + fi no_checkout=yes fi +if test -z "$origin" +then + origin=origin +fi + # Turn the source into an absolute path if # it is local repo="$1" @@ -130,6 +203,28 @@ yes) GIT_DIR="$D/.git" ;; esac +if test -n "$reference" +then + if test -d "$reference" + then + if test -d "$reference/.git/objects" + then + reference="$reference/.git" + fi + reference=$(cd "$reference" && pwd) + echo "$reference/objects" >"$GIT_DIR/objects/info/alternates" + (cd "$reference" && tar cf - refs) | + (cd "$GIT_DIR/refs" && + mkdir reference-tmp && + cd reference-tmp && + tar xf -) + else + echo >&2 "$reference: not a local directory." && usage + fi +fi + +rm -f "$GIT_DIR/CLONE_HEAD" + # We do local magic only when the user tells us to. case "$local,$use_local" in yes,yes) @@ -165,24 +260,14 @@ yes,yes) } >"$GIT_DIR/objects/info/alternates" ;; esac - - # Make a duplicate of refs and HEAD pointer - HEAD= - if test -f "$repo/HEAD" - then - HEAD=HEAD - fi - (cd "$repo" && tar cf - refs $HEAD) | - (cd "$GIT_DIR" && tar xf -) || exit 1 + git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" ;; *) case "$repo" in rsync://*) rsync $quiet -av --ignore-existing \ - --exclude info "$repo/objects/" "$GIT_DIR/objects/" && - rsync $quiet -av --ignore-existing \ - --exclude info "$repo/refs/" "$GIT_DIR/refs/" || exit - + --exclude info "$repo/objects/" "$GIT_DIR/objects/" || + exit # Look at objects/info/alternates for rsync -- http will # support it natively and git native ones will do it on the # remote end. Not having that file is not a crime. @@ -205,6 +290,7 @@ yes,yes) done rm -f "$GIT_DIR/TMP_ALT" fi + git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" ;; http://*) if test -z "@@NO_CURL@@" @@ -217,38 +303,89 @@ yes,yes) ;; *) cd "$D" && case "$upload_pack" in - '') git-clone-pack $quiet "$repo" ;; - *) git-clone-pack $quiet "$upload_pack" "$repo" ;; - esac || { - echo >&2 "clone-pack from '$repo' failed." + '') git-fetch-pack --all -k $quiet "$repo" ;; + *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;; + esac >"$GIT_DIR/CLONE_HEAD" || { + echo >&2 "fetch-pack from '$repo' failed." exit 1 } ;; esac ;; esac +test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp" + +if test -f "$GIT_DIR/CLONE_HEAD" +then + # Figure out where the remote HEAD points at. + perl -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin" +fi cd "$D" || exit -if test -f "$GIT_DIR/HEAD" && test -z "$bare" +if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD" then - head_points_at=`git-symbolic-ref HEAD` + head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"` + # Figure out which remote branch HEAD points at. + case "$use_separate_remote" in + '') remote_top=refs/heads ;; + *) remote_top="refs/remotes/$origin" ;; + esac + + # What to use to track the remote primary branch + if test -n "$use_separate_remote" + then + origin_tracking="remotes/$origin/master" + else + origin_tracking="heads/$origin" + fi + + # The name under $remote_top the remote HEAD seems to point at + head_points_at=$( + ( + echo "master" + cd "$GIT_DIR/$remote_top" && + find . -type f -print | sed -e 's/^\.\///' + ) | ( + done=f + while read name + do + test t = $done && continue + branch_tip=`cat "$GIT_DIR/$remote_top/$name"` + if test "$head_sha1" = "$branch_tip" + then + echo "$name" + done=t + fi + done + ) + ) + + # Write out remotes/$origin file. case "$head_points_at" in - refs/heads/*) - head_points_at=`expr "$head_points_at" : 'refs/heads/\(.*\)'` + ?*) mkdir -p "$GIT_DIR/remotes" && - echo >"$GIT_DIR/remotes/origin" \ + echo >"$GIT_DIR/remotes/$origin" \ "URL: $repo -Pull: $head_points_at:$origin" && - git-update-ref "refs/heads/$origin" $(git-rev-parse HEAD) && - (cd "$GIT_DIR" && find "refs/heads" -type f -print) | - while read ref +Pull: refs/heads/$head_points_at:refs/$origin_tracking" && + case "$use_separate_remote" in + t) git-update-ref HEAD "$head_sha1" ;; + *) git-update-ref "refs/heads/$origin" $(git-rev-parse HEAD) ;; + esac && + (cd "$GIT_DIR/$remote_top" && find . -type f -print) | + while read dotslref do - head=`expr "$ref" : 'refs/heads/\(.*\)'` && - test "$head_points_at" = "$head" || - test "$origin" = "$head" || - echo "Pull: ${head}:${head}" - done >>"$GIT_DIR/remotes/origin" + name=`expr "$dotslref" : './\(.*\)'` && + test "$head_points_at" = "$name" || + test "$origin" = "$name" || + echo "Pull: refs/heads/${name}:$remote_top/${name}" + done >>"$GIT_DIR/remotes/$origin" && + case "$use_separate_remote" in + t) + rm -f "refs/remotes/$origin/HEAD" + git-symbolic-ref "refs/remotes/$origin/HEAD" \ + "refs/remotes/$origin/$head_points_at" + esac esac case "$no_checkout" in @@ -256,6 +393,7 @@ Pull: $head_points_at:$origin" && git-read-tree -m -u -v HEAD HEAD esac fi +rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD" trap - exit diff --git a/git-fetch.sh b/git-fetch.sh index 68356343a6..954901ddce 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -94,6 +94,9 @@ append_fetch_head () { # remote-nick is the URL given on the command line (or a shorthand) # remote-name is the $GIT_DIR relative refs/ path we computed # for this refspec. + + # the $note_ variable will be fed to git-fmt-merge-msg for further + # processing. case "$remote_name_" in HEAD) note_= ;; @@ -103,6 +106,9 @@ append_fetch_head () { refs/tags/*) note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')" note_="tag '$note_' of " ;; + refs/remotes/*) + note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')" + note_="remote branch '$note_' of " ;; *) note_="$remote_name of " ;; esac @@ -147,10 +153,10 @@ fast_forward_local () { else echo >&2 "* $1: storing $3" fi - git-update-ref "$1" "$2" + git-update-ref "$1" "$2" ;; - refs/heads/*) + refs/heads/* | refs/remotes/*) # $1 is the ref being updated. # $2 is the new value for the ref. local=$(git-rev-parse --verify "$1^0" 2>/dev/null) diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl index afe80e6321..5986e5414a 100755 --- a/git-fmt-merge-msg.perl +++ b/git-fmt-merge-msg.perl @@ -75,6 +75,7 @@ sub shortlog { $src{$src} = { BRANCH => [], TAG => [], + R_BRANCH => [], GENERIC => [], # &1 == has HEAD. # &2 == has others. @@ -91,6 +92,11 @@ sub shortlog { push @{$src{$src}{TAG}}, $1; $src{$src}{HEAD_STATUS} |= 2; } + elsif (/^remote branch (.*)$/) { + $origin = $1; + push @{$src{$src}{R_BRANCH}}, $1; + $src{$src}{HEAD_STATUS} |= 2; + } elsif (/^HEAD$/) { $origin = $src; $src{$src}{HEAD_STATUS} |= 1; @@ -123,6 +129,8 @@ sub shortlog { } push @this, andjoin("branch ", "branches ", $src{$src}{BRANCH}); + push @this, andjoin("remote branch ", "remote branches ", + $src{$src}{R_BRANCH}); push @this, andjoin("tag ", "tags ", $src{$src}{TAG}); push @this, andjoin("commit ", "commits ", diff --git a/git-merge.sh b/git-merge.sh index cc0952a97d..78ab422e4e 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -11,11 +11,15 @@ LF=' ' all_strategies='recursive octopus resolve stupid ours' -default_strategies='recursive' +default_twohead_strategies='recursive' +default_octopus_strategies='octopus' +no_trivial_merge_strategies='ours' use_strategies= + +index_merge=t if test "@@NO_PYTHON@@"; then all_strategies='resolve octopus stupid ours' - default_strategies='resolve' + default_twohead_strategies='resolve' fi dropsave() { @@ -90,8 +94,6 @@ do shift done -test "$#" -le 2 && usage ;# we need at least two heads. - merge_msg="$1" shift head_arg="$1" @@ -99,6 +101,8 @@ head=$(git-rev-parse --verify "$1"^0) || usage shift # All the rest are remote heads +test "$#" = 0 && usage ;# we need at least one remote head. + remoteheads= for remote do @@ -108,6 +112,27 @@ do done set x $remoteheads ; shift +case "$use_strategies" in +'') + case "$#" in + 1) + use_strategies="$default_twohead_strategies" ;; + *) + use_strategies="$default_octopus_strategies" ;; + esac + ;; +esac + +for s in $use_strategies +do + case " $s " in + *" $no_trivial_merge_strategies "*) + index_merge=f + break + ;; + esac +done + case "$#" in 1) common=$(git-merge-base --all $head "$@") @@ -118,18 +143,21 @@ case "$#" in esac echo "$head" >"$GIT_DIR/ORIG_HEAD" -case "$#,$common,$no_commit" in -*,'',*) +case "$index_merge,$#,$common,$no_commit" in +f,*) + # We've been told not to try anything clever. Skip to real merge. + ;; +?,*,'',*) # No common ancestors found. We need a real merge. ;; -1,"$1",*) +?,1,"$1",*) # If head can reach all the merge then we are up to date. - # but first the most common case of merging one remote + # but first the most common case of merging one remote. echo "Already up-to-date." dropsave exit 0 ;; -1,"$head",*) +?,1,"$head",*) # Again the most common case of merging one remote. echo "Updating from $head to $1" git-update-index --refresh 2>/dev/null @@ -139,11 +167,11 @@ case "$#,$common,$no_commit" in dropsave exit 0 ;; -1,?*"$LF"?*,*) +?,1,?*"$LF"?*,*) # We are not doing octopus and not fast forward. Need a # real merge. ;; -1,*,) +?,1,*,) # We are not doing octopus, not fast forward, and have only # one common. See if it is really trivial. git var GIT_COMMITTER_IDENT >/dev/null || exit @@ -188,17 +216,6 @@ esac # We are going to make a new commit. git var GIT_COMMITTER_IDENT >/dev/null || exit -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 @@ -270,11 +287,7 @@ done # auto resolved the merge cleanly. if test '' != "$result_tree" then - parents="-p $head" - for remote - do - parents="$parents -p $remote" - done + parents=$(git-show-branch --independent "$head" "$@" | sed -e 's/^/-p /') result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit finish "$result_commit" "Merge $result_commit, made by $wt_strategy." dropsave diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 5f158c613f..63f22818e6 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -86,14 +86,14 @@ canon_refs_list_for_fetch () { local=$(expr "$ref" : '[^:]*:\(.*\)') case "$remote" in '') remote=HEAD ;; - refs/heads/* | refs/tags/*) ;; - heads/* | tags/* ) remote="refs/$remote" ;; + refs/heads/* | refs/tags/* | refs/remotes/*) ;; + heads/* | tags/* | remotes/* ) remote="refs/$remote" ;; *) remote="refs/heads/$remote" ;; esac case "$local" in '') local= ;; - refs/heads/* | refs/tags/*) ;; - heads/* | tags/* ) local="refs/$local" ;; + refs/heads/* | refs/tags/* | refs/remotes/*) ;; + heads/* | tags/* | remotes/* ) local="refs/$local" ;; *) local="refs/heads/$local" ;; esac diff --git a/git-push.sh b/git-push.sh index 73dcf067cb..f10cadbf15 100755 --- a/git-push.sh +++ b/git-push.sh @@ -8,7 +8,7 @@ USAGE='[--all] [--tags] [--force] [...]' has_all= has_force= has_exec= -has_thin= +has_thin=--thin remote= do_tags= @@ -24,7 +24,9 @@ do --exec=*) has_exec="$1" ;; --thin) - has_thin="$1" ;; + ;; # noop + --no-thin) + has_thin= ;; -*) usage ;; *) diff --git a/git-send-email.perl b/git-send-email.perl index b220d11cc1..ecfa347b85 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -19,10 +19,16 @@ use strict; use warnings; use Term::ReadLine; -use Mail::Sendmail qw(sendmail %mailcfg); use Getopt::Long; use Data::Dumper; -use Email::Valid; +use Net::SMTP; + +# most mail servers generate the Date: header, but not all... +$ENV{LC_ALL} = 'C'; +use POSIX qw/strftime/; + +my $have_email_valid = eval { require Email::Valid; 1 }; +my $smtp; sub unique_email_list(@); sub cleanup_compose_files(); @@ -31,7 +37,7 @@ my $compose_filename = ".msg.$$"; # Variables we fill in automatically, or via prompting: -my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose); +my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time); # Behavior modification variables my ($chain_reply_to, $smtp_server, $quiet, $suppress_from, $no_signed_off_cc) = (1, "localhost", 0, 0, 0); @@ -244,6 +250,16 @@ sub gitvar_ident { # Variables we set as part of the loop over files our ($message_id, $cc, %mail, $subject, $reply_to, $message); +sub extract_valid_address { + my $address = shift; + if ($have_email_valid) { + return Email::Valid->address($address); + } else { + # less robust/correct than the monster regexp in Email::Valid, + # but still does a 99% job, and one less dependency + return ($address =~ /([^\"<>\s]+@[^<>\s]+)/); + } +} # Usually don't need to change anything below here. @@ -253,13 +269,12 @@ sub gitvar_ident { # 1 second since the last time we were called. # We'll setup a template for the message id, using the "from" address: -my $message_id_from = Email::Valid->address($from); +my $message_id_from = extract_valid_address($from); my $message_id_template = "<%s-git-send-email-$message_id_from>"; sub make_message_id { - my $date = `date "+\%s"`; - chomp($date); + my $date = time; my $pseudo_rand = int (rand(4200)); $message_id = sprintf $message_id_template, "$date$pseudo_rand"; #print "new message id = $message_id\n"; # Was useful for debugging @@ -268,38 +283,49 @@ sub make_message_id $cc = ""; +$time = time - scalar $#files; sub send_message { - my $to = join (", ", unique_email_list(@to)); - - %mail = ( To => $to, - From => $from, - CC => $cc, - Subject => $subject, - Message => $message, - 'Reply-to' => $from, - 'In-Reply-To' => $reply_to, - 'Message-ID' => $message_id, - 'X-Mailer' => "git-send-email", - ); - - $mail{smtp} = $smtp_server; - $mailcfg{mime} = 0; - - #print Data::Dumper->Dump([\%mail],[qw(*mail)]); - - sendmail(%mail) or die $Mail::Sendmail::error; + my @recipients = unique_email_list(@to); + my $to = join (",\n\t", @recipients); + @recipients = unique_email_list(@recipients,@cc); + my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++)); + + my $header = "From: $from +To: $to +Cc: $cc +Subject: $subject +Reply-To: $from +Date: $date +Message-Id: $message_id +X-Mailer: git-send-email @@GIT_VERSION@@ +"; + $header .= "In-Reply-To: $reply_to\n" if $reply_to; + + $smtp ||= Net::SMTP->new( $smtp_server ); + $smtp->mail( $from ) or die $smtp->message; + $smtp->to( @recipients ) or die $smtp->message; + $smtp->data or die $smtp->message; + $smtp->datasend("$header\n$message") or die $smtp->message; + $smtp->dataend() or die $smtp->message; + $smtp->ok or die "Failed to send $subject\n".$smtp->message; if ($quiet) { printf "Sent %s\n", $subject; } else { - print "OK. Log says:\n", $Mail::Sendmail::log; - print "\n\n" + print "OK. Log says: +Date: $date +Server: $smtp_server Port: 25 +From: $from +Subject: $subject +Cc: $cc +To: $to + +Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n"; } } - $reply_to = $initial_reply_to; make_message_id(); $subject = $initial_subject; @@ -390,14 +416,14 @@ () } - +$smtp->quit if $smtp; sub unique_email_list(@) { my %seen; my @emails; foreach my $entry (@_) { - my $clean = Email::Valid->address($entry); + my $clean = extract_valid_address($entry); next if $seen{$clean}++; push @emails, $entry; } diff --git a/gitk b/gitk index f4c662423c..03cd475f09 100755 --- a/gitk +++ b/gitk @@ -34,13 +34,18 @@ proc parse_args {rargs} { proc start_rev_list {rlargs} { global startmsecs nextupdate ncmupdate - global commfd leftover tclencoding + global commfd leftover tclencoding datemode set startmsecs [clock clicks -milliseconds] set nextupdate [expr {$startmsecs + 100}] set ncmupdate 1 + initlayout + set order "--topo-order" + if {$datemode} { + set order "--date-order" + } if {[catch { - set commfd [open [concat | git-rev-list --header --topo-order \ + set commfd [open [concat | git-rev-list --header $order \ --parents $rlargs] r] } err]} { puts stderr "Error executing git-rev-list: $err" @@ -57,16 +62,8 @@ proc start_rev_list {rlargs} { } proc getcommits {rargs} { - global oldcommits commits phase canv mainfont env + global phase canv mainfont - # check that we can find a .git directory somewhere... - set gitdir [gitdir] - if {![file isdirectory $gitdir]} { - error_popup "Cannot find the git directory \"$gitdir\"." - exit 1 - } - set oldcommits {} - set commits {} set phase getcommits start_rev_list [parse_args $rargs] $canv delete all @@ -75,10 +72,9 @@ proc getcommits {rargs} { } proc getcommitlines {commfd} { - global oldcommits commits parents cdate children nchildren - global commitlisted phase nextupdate - global stopped redisplaying leftover - global canv + global commitlisted nextupdate + global leftover + global displayorder commitidx commitrow commitdata set stuff [read $commfd] if {$stuff == {}} { @@ -101,16 +97,19 @@ proc getcommitlines {commfd} { exit 1 } set start 0 + set gotsome 0 while 1 { set i [string first "\0" $stuff $start] if {$i < 0} { append leftover [string range $stuff $start end] - return + break } - set cmit [string range $stuff $start [expr {$i - 1}]] if {$start == 0} { - set cmit "$leftover$cmit" + set cmit $leftover + append cmit [string range $stuff 0 [expr {$i - 1}]] set leftover {} + } else { + set cmit [string range $stuff $start [expr {$i - 1}]] } set start [expr {$i + 1}] set j [string first "\n" $cmit] @@ -119,7 +118,7 @@ proc getcommitlines {commfd} { set ids [string range $cmit 0 [expr {$j - 1}]] set ok 1 foreach id $ids { - if {![regexp {^[0-9a-f]{40}$} $id]} { + if {[string length $id] != 40} { set ok 0 break } @@ -135,28 +134,19 @@ proc getcommitlines {commfd} { } set id [lindex $ids 0] set olds [lrange $ids 1 end] - set cmit [string range $cmit [expr {$j + 1}] end] - lappend commits $id set commitlisted($id) 1 - parsecommit $id $cmit 1 [lrange $ids 1 end] - drawcommit $id 1 - if {[clock clicks -milliseconds] >= $nextupdate} { - doupdate 1 - } - while {$redisplaying} { - set redisplaying 0 - if {$stopped == 1} { - set stopped 0 - set phase "getcommits" - foreach id $commits { - drawcommit $id 1 - if {$stopped} break - if {[clock clicks -milliseconds] >= $nextupdate} { - doupdate 1 - } - } - } - } + updatechildren $id $olds + set commitdata($id) [string range $cmit [expr {$j + 1}] end] + set commitrow($id) $commitidx + incr commitidx + lappend displayorder $id + set gotsome 1 + } + if {$gotsome} { + layoutmore + } + if {[clock clicks -milliseconds] >= $nextupdate} { + doupdate 1 } } @@ -182,106 +172,31 @@ proc doupdate {reading} { proc readcommit {id} { if {[catch {set contents [exec git-cat-file commit $id]}]} return - parsecommit $id $contents 0 {} + updatechildren $id {} + parsecommit $id $contents 0 } proc updatecommits {rargs} { - global commitlisted commfd phase - global startmsecs nextupdate ncmupdate - global idtags idheads idotherrefs - global leftover - global parsed_args - global canv mainfont - global oldcommits commits - global parents nchildren children ncleft - - set old_args $parsed_args - parse_args $rargs - - if {$phase == "getcommits" || $phase == "incrdraw"} { - # havent read all the old commits, just start again from scratch - stopfindproc - set oldcommits {} - set commits {} - foreach v {children nchildren parents commitlisted commitinfo - selectedline matchinglines treediffs - mergefilelist currentid rowtextx} { - global $v - catch {unset $v} - } - readrefs - if {$phase == "incrdraw"} { - allcanvs delete all - $canv create text 3 3 -anchor nw -text "Reading commits..." \ - -font $mainfont -tags textitems - set phase getcommits - } - start_rev_list $parsed_args - return - } - - foreach id $old_args { - if {![regexp {^[0-9a-f]{40}$} $id]} continue - if {[info exists oldref($id)]} continue - set oldref($id) $id - lappend ignoreold "^$id" - } - foreach id $parsed_args { - if {![regexp {^[0-9a-f]{40}$} $id]} continue - if {[info exists ref($id)]} continue - set ref($id) $id - lappend ignorenew "^$id" - } - - foreach a $old_args { - if {![info exists ref($a)]} { - lappend ignorenew $a - } - } - - set phase updatecommits - set oldcommits $commits - set commits {} - set removed_commits [split [eval exec git-rev-list $ignorenew] "\n" ] - if {[llength $removed_commits] > 0} { - allcanvs delete all - foreach c $removed_commits { - set i [lsearch -exact $oldcommits $c] - if {$i >= 0} { - set oldcommits [lreplace $oldcommits $i $i] - unset commitlisted($c) - foreach p $parents($c) { - if {[info exists nchildren($p)]} { - set j [lsearch -exact $children($p) $c] - if {$j >= 0} { - set children($p) [lreplace $children($p) $j $j] - incr nchildren($p) -1 - } - } - } - } - } - set phase removecommits - } - - set args {} - foreach a $parsed_args { - if {![info exists oldref($a)]} { - lappend args $a - } + stopfindproc + foreach v {children nchildren parents nparents commitlisted + colormap selectedline matchinglines treediffs + mergefilelist currentid rowtextx commitrow + rowidlist rowoffsets idrowranges idrangedrawn iddrawn + linesegends crossings cornercrossings} { + global $v + catch {unset $v} } - + allcanvs delete all readrefs - start_rev_list [concat $ignoreold $args] + getcommits $rargs } proc updatechildren {id olds} { - global children nchildren parents nparents ncleft + global children nchildren parents nparents if {![info exists nchildren($id)]} { set children($id) {} set nchildren($id) 0 - set ncleft($id) 0 } set parents($id) $olds set nparents($id) [llength $olds] @@ -289,16 +204,14 @@ proc updatechildren {id olds} { if {![info exists nchildren($p)]} { set children($p) [list $id] set nchildren($p) 1 - set ncleft($p) 1 } elseif {[lsearch -exact $children($p) $id] < 0} { lappend children($p) $id incr nchildren($p) - incr ncleft($p) } } } -proc parsecommit {id contents listed olds} { +proc parsecommit {id contents listed} { global commitinfo cdate set inhdr 1 @@ -308,7 +221,6 @@ proc parsecommit {id contents listed olds} { set audate {} set comname {} set comdate {} - updatechildren $id $olds set hdrend [string first "\n\n" $contents] if {$hdrend < 0} { # should never happen... @@ -352,6 +264,21 @@ proc parsecommit {id contents listed olds} { $comname $comdate $comment] } +proc getcommit {id} { + global commitdata commitinfo nparents + + if {[info exists commitdata($id)]} { + parsecommit $id $commitdata($id) 1 + } else { + readcommit $id + if {![info exists commitinfo($id)]} { + set commitinfo($id) {"No commit information available"} + set nparents($id) 0 + } + } + return 1 +} + proc readrefs {} { global tagids idtags headids idheads tagcontents global otherrefids idotherrefs @@ -405,6 +332,7 @@ proc error_popup msg { button $w.ok -text OK -command "destroy $w" pack $w.ok -side bottom -fill x bind $w "grab $w; focus $w" + bind $w "destroy $w" tkwait window $w } @@ -457,7 +385,7 @@ proc makewindow {rargs} { set canv .ctop.top.clist.canv canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ -bg white -bd 0 \ - -yscrollincr $linespc -yscrollcommand "$cscroll set" + -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" .ctop.top.clist add $canv set canv2 .ctop.top.clist.canv2 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ @@ -631,6 +559,11 @@ proc makewindow {rargs} { $rowctxmenu add command -label "Write commit to file" -command writecommit } +proc scrollcanv {cscroll f0 f1} { + $cscroll set $f0 $f1 + drawfrac $f0 $f1 +} + # when we make a key binding for the toplevel, make sure # it doesn't get triggered when that key is pressed in the # find string entry widget. @@ -763,9 +696,9 @@ proc about {} { toplevel $w wm title $w "About gitk" message $w.m -text { -Gitk version 1.2 +Gitk - a commit viewer for git -Copyright © 2005 Paul Mackerras +Copyright © 2005-2006 Paul Mackerras Use and redistribute under the terms of the GNU General Public License} \ -justify center -aspect 400 @@ -774,14 +707,776 @@ Use and redistribute under the terms of the GNU General Public License} \ pack $w.ok -side bottom } +proc shortids {ids} { + set res {} + foreach id $ids { + if {[llength $id] > 1} { + lappend res [shortids $id] + } elseif {[regexp {^[0-9a-f]{40}$} $id]} { + lappend res [string range $id 0 7] + } else { + lappend res $id + } + } + return $res +} + +proc incrange {l x o} { + set n [llength $l] + while {$x < $n} { + set e [lindex $l $x] + if {$e ne {}} { + lset l $x [expr {$e + $o}] + } + incr x + } + return $l +} + +proc ntimes {n o} { + set ret {} + for {} {$n > 0} {incr n -1} { + lappend ret $o + } + return $ret +} + +proc usedinrange {id l1 l2} { + global children commitrow + + if {[info exists commitrow($id)]} { + set r $commitrow($id) + if {$l1 <= $r && $r <= $l2} { + return [expr {$r - $l1 + 1}] + } + } + foreach c $children($id) { + if {[info exists commitrow($c)]} { + set r $commitrow($c) + if {$l1 <= $r && $r <= $l2} { + return [expr {$r - $l1 + 1}] + } + } + } + return 0 +} + +proc sanity {row {full 0}} { + global rowidlist rowoffsets + + set col -1 + set ids [lindex $rowidlist $row] + foreach id $ids { + incr col + if {$id eq {}} continue + if {$col < [llength $ids] - 1 && + [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} { + puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}" + } + set o [lindex $rowoffsets $row $col] + set y $row + set x $col + while {$o ne {}} { + incr y -1 + incr x $o + if {[lindex $rowidlist $y $x] != $id} { + puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]" + puts " id=[shortids $id] check started at row $row" + for {set i $row} {$i >= $y} {incr i -1} { + puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}" + } + break + } + if {!$full} break + set o [lindex $rowoffsets $y $x] + } + } +} + +proc makeuparrow {oid x y z} { + global rowidlist rowoffsets uparrowlen idrowranges + + for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} { + incr y -1 + incr x $z + set off0 [lindex $rowoffsets $y] + for {set x0 $x} {1} {incr x0} { + if {$x0 >= [llength $off0]} { + set x0 [llength [lindex $rowoffsets [expr {$y-1}]]] + break + } + set z [lindex $off0 $x0] + if {$z ne {}} { + incr x0 $z + break + } + } + set z [expr {$x0 - $x}] + lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid] + lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z] + } + set tmp [lreplace [lindex $rowoffsets $y] $x $x {}] + lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1] + lappend idrowranges($oid) $y +} + +proc initlayout {} { + global rowidlist rowoffsets displayorder + global rowlaidout rowoptim + global idinlist rowchk + global commitidx numcommits + global nextcolor + + set commitidx 0 + set numcommits 0 + set displayorder {} + set nextcolor 0 + set rowidlist {{}} + set rowoffsets {{}} + catch {unset idinlist} + catch {unset rowchk} + set rowlaidout 0 + set rowoptim 0 +} + +proc visiblerows {} { + global canv numcommits linespc + + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {} || $ymax == 0} return + set f [$canv yview] + set y0 [expr {int([lindex $f 0] * $ymax)}] + set r0 [expr {int(($y0 - 3) / $linespc) - 1}] + if {$r0 < 0} { + set r0 0 + } + set y1 [expr {int([lindex $f 1] * $ymax)}] + set r1 [expr {int(($y1 - 3) / $linespc) + 1}] + if {$r1 >= $numcommits} { + set r1 [expr {$numcommits - 1}] + } + return [list $r0 $r1] +} + +proc layoutmore {} { + global rowlaidout rowoptim commitidx numcommits optim_delay + global uparrowlen + + set row $rowlaidout + set rowlaidout [layoutrows $row $commitidx 0] + set orow [expr {$rowlaidout - $uparrowlen - 1}] + if {$orow > $rowoptim} { + checkcrossings $rowoptim $orow + optimize_rows $rowoptim 0 $orow + set rowoptim $orow + } + set canshow [expr {$rowoptim - $optim_delay}] + if {$canshow > $numcommits} { + showstuff $canshow + } +} + +proc showstuff {canshow} { + global numcommits + global canvy0 linespc + global linesegends idrowranges idrangedrawn + + if {$numcommits == 0} { + global phase + set phase "incrdraw" + allcanvs delete all + } + set row $numcommits + set numcommits $canshow + allcanvs conf -scrollregion \ + [list 0 0 0 [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]] + set rows [visiblerows] + set r0 [lindex $rows 0] + set r1 [lindex $rows 1] + for {set r $row} {$r < $canshow} {incr r} { + if {[info exists linesegends($r)]} { + foreach id $linesegends($r) { + set i -1 + foreach {s e} $idrowranges($id) { + incr i + if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0 + && ![info exists idrangedrawn($id,$i)]} { + drawlineseg $id $i + set idrangedrawn($id,$i) 1 + } + } + } + } + } + if {$canshow > $r1} { + set canshow $r1 + } + while {$row < $canshow} { + drawcmitrow $row + incr row + } +} + +proc layoutrows {row endrow last} { + global rowidlist rowoffsets displayorder + global uparrowlen downarrowlen maxwidth mingaplen + global nchildren parents nparents + global idrowranges linesegends + global commitidx + global idinlist rowchk + + set idlist [lindex $rowidlist $row] + set offs [lindex $rowoffsets $row] + while {$row < $endrow} { + set id [lindex $displayorder $row] + set oldolds {} + set newolds {} + foreach p $parents($id) { + if {![info exists idinlist($p)]} { + lappend newolds $p + } elseif {!$idinlist($p)} { + lappend oldolds $p + } + } + set nev [expr {[llength $idlist] + [llength $newolds] + + [llength $oldolds] - $maxwidth + 1}] + if {$nev > 0} { + if {!$last && $row + $uparrowlen + $mingaplen >= $commitidx} break + for {set x [llength $idlist]} {[incr x -1] >= 0} {} { + set i [lindex $idlist $x] + if {![info exists rowchk($i)] || $row >= $rowchk($i)} { + set r [usedinrange $i [expr {$row - $downarrowlen}] \ + [expr {$row + $uparrowlen + $mingaplen}]] + if {$r == 0} { + set idlist [lreplace $idlist $x $x] + set offs [lreplace $offs $x $x] + set offs [incrange $offs $x 1] + set idinlist($i) 0 + set rm1 [expr {$row - 1}] + lappend linesegends($rm1) $i + lappend idrowranges($i) $rm1 + if {[incr nev -1] <= 0} break + continue + } + set rowchk($id) [expr {$row + $r}] + } + } + lset rowidlist $row $idlist + lset rowoffsets $row $offs + } + set col [lsearch -exact $idlist $id] + if {$col < 0} { + set col [llength $idlist] + lappend idlist $id + lset rowidlist $row $idlist + set z {} + if {$nchildren($id) > 0} { + set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}] + unset idinlist($id) + } + lappend offs $z + lset rowoffsets $row $offs + if {$z ne {}} { + makeuparrow $id $col $row $z + } + } else { + unset idinlist($id) + } + if {[info exists idrowranges($id)]} { + lappend idrowranges($id) $row + } + incr row + set offs [ntimes [llength $idlist] 0] + set l [llength $newolds] + set idlist [eval lreplace \$idlist $col $col $newolds] + set o 0 + if {$l != 1} { + set offs [lrange $offs 0 [expr {$col - 1}]] + foreach x $newolds { + lappend offs {} + incr o -1 + } + incr o + set tmp [expr {[llength $idlist] - [llength $offs]}] + if {$tmp > 0} { + set offs [concat $offs [ntimes $tmp $o]] + } + } else { + lset offs $col {} + } + foreach i $newolds { + set idinlist($i) 1 + set idrowranges($i) $row + } + incr col $l + foreach oid $oldolds { + set idinlist($oid) 1 + set idlist [linsert $idlist $col $oid] + set offs [linsert $offs $col $o] + makeuparrow $oid $col $row $o + incr col + } + lappend rowidlist $idlist + lappend rowoffsets $offs + } + return $row +} + +proc addextraid {id row} { + global displayorder commitrow commitinfo nparents + global commitidx + + incr commitidx + lappend displayorder $id + set commitrow($id) $row + readcommit $id + if {![info exists commitinfo($id)]} { + set commitinfo($id) {"No commit information available"} + set nparents($id) 0 + } +} + +proc layouttail {} { + global rowidlist rowoffsets idinlist commitidx + global idrowranges + + set row $commitidx + set idlist [lindex $rowidlist $row] + while {$idlist ne {}} { + set col [expr {[llength $idlist] - 1}] + set id [lindex $idlist $col] + addextraid $id $row + unset idinlist($id) + lappend idrowranges($id) $row + incr row + set offs [ntimes $col 0] + set idlist [lreplace $idlist $col $col] + lappend rowidlist $idlist + lappend rowoffsets $offs + } + + foreach id [array names idinlist] { + addextraid $id $row + lset rowidlist $row [list $id] + lset rowoffsets $row 0 + makeuparrow $id 0 $row 0 + lappend idrowranges($id) $row + incr row + lappend rowidlist {} + lappend rowoffsets {} + } +} + +proc insert_pad {row col npad} { + global rowidlist rowoffsets + + set pad [ntimes $npad {}] + lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad] + set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad] + lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]] +} + +proc optimize_rows {row col endrow} { + global rowidlist rowoffsets idrowranges linesegends displayorder + + for {} {$row < $endrow} {incr row} { + set idlist [lindex $rowidlist $row] + set offs [lindex $rowoffsets $row] + set haspad 0 + set downarrowcols {} + if {[info exists linesegends($row)]} { + set downarrowcols $linesegends($row) + if {$col > 0} { + while {$downarrowcols ne {}} { + set i [lsearch -exact $idlist [lindex $downarrowcols 0]] + if {$i < 0 || $i >= $col} break + set downarrowcols [lrange $downarrowcols 1 end] + } + } + } + for {} {$col < [llength $offs]} {incr col} { + if {[lindex $idlist $col] eq {}} { + set haspad 1 + continue + } + set z [lindex $offs $col] + if {$z eq {}} continue + set isarrow 0 + set x0 [expr {$col + $z}] + set y0 [expr {$row - 1}] + set z0 [lindex $rowoffsets $y0 $x0] + if {$z0 eq {}} { + set id [lindex $idlist $col] + if {[info exists idrowranges($id)] && + $y0 > [lindex $idrowranges($id) 0]} { + set isarrow 1 + } + } elseif {$downarrowcols ne {} && + [lindex $idlist $col] eq [lindex $downarrowcols 0]} { + set downarrowcols [lrange $downarrowcols 1 end] + set isarrow 1 + } + if {$z < -1 || ($z < 0 && $isarrow)} { + set npad [expr {-1 - $z + $isarrow}] + set offs [incrange $offs $col $npad] + insert_pad $y0 $x0 $npad + if {$y0 > 0} { + optimize_rows $y0 $x0 $row + } + set z [lindex $offs $col] + set x0 [expr {$col + $z}] + set z0 [lindex $rowoffsets $y0 $x0] + } elseif {$z > 1 || ($z > 0 && $isarrow)} { + set npad [expr {$z - 1 + $isarrow}] + set y1 [expr {$row + 1}] + set offs2 [lindex $rowoffsets $y1] + set x1 -1 + foreach z $offs2 { + incr x1 + if {$z eq {} || $x1 + $z < $col} continue + if {$x1 + $z > $col} { + incr npad + } + lset rowoffsets $y1 [incrange $offs2 $x1 $npad] + break + } + set pad [ntimes $npad {}] + set idlist [eval linsert \$idlist $col $pad] + set tmp [eval linsert \$offs $col $pad] + incr col $npad + set offs [incrange $tmp $col [expr {-$npad}]] + set z [lindex $offs $col] + set haspad 1 + } + if {$z0 eq {} && !$isarrow} { + # this line links to its first child on row $row-2 + set rm2 [expr {$row - 2}] + set id [lindex $displayorder $rm2] + set xc [lsearch -exact [lindex $rowidlist $rm2] $id] + if {$xc >= 0} { + set z0 [expr {$xc - $x0}] + } + } + if {$z0 ne {} && $z < 0 && $z0 > 0} { + insert_pad $y0 $x0 1 + set offs [incrange $offs $col 1] + optimize_rows $y0 [expr {$x0 + 1}] $row + } + } + if {!$haspad} { + set o {} + for {set col [llength $idlist]} {[incr col -1] >= 0} {} { + set o [lindex $offs $col] + if {$o eq {}} { + # check if this is the link to the first child + set id [lindex $idlist $col] + if {[info exists idrowranges($id)] && + $row == [lindex $idrowranges($id) 0]} { + # it is, work out offset to child + set y0 [expr {$row - 1}] + set id [lindex $displayorder $y0] + set x0 [lsearch -exact [lindex $rowidlist $y0] $id] + if {$x0 >= 0} { + set o [expr {$x0 - $col}] + } + } + } + if {$o eq {} || $o <= 0} break + } + if {$o ne {} && [incr col] < [llength $idlist]} { + set y1 [expr {$row + 1}] + set offs2 [lindex $rowoffsets $y1] + set x1 -1 + foreach z $offs2 { + incr x1 + if {$z eq {} || $x1 + $z < $col} continue + lset rowoffsets $y1 [incrange $offs2 $x1 1] + break + } + set idlist [linsert $idlist $col {}] + set tmp [linsert $offs $col {}] + incr col + set offs [incrange $tmp $col -1] + } + } + lset rowidlist $row $idlist + lset rowoffsets $row $offs + set col 0 + } +} + +proc xc {row col} { + global canvx0 linespc + return [expr {$canvx0 + $col * $linespc}] +} + +proc yc {row} { + global canvy0 linespc + return [expr {$canvy0 + $row * $linespc}] +} + +proc linewidth {id} { + global thickerline lthickness + + set wid $lthickness + if {[info exists thickerline] && $id eq $thickerline} { + set wid [expr {2 * $lthickness}] + } + return $wid +} + +proc drawlineseg {id i} { + global rowoffsets rowidlist idrowranges + global displayorder + global canv colormap + + set startrow [lindex $idrowranges($id) [expr {2 * $i}]] + set row [lindex $idrowranges($id) [expr {2 * $i + 1}]] + if {$startrow == $row} return + assigncolor $id + set coords {} + set col [lsearch -exact [lindex $rowidlist $row] $id] + if {$col < 0} { + puts "oops: drawline: id $id not on row $row" + return + } + set lasto {} + set ns 0 + while {1} { + set o [lindex $rowoffsets $row $col] + if {$o eq {}} break + if {$o ne $lasto} { + # changing direction + set x [xc $row $col] + set y [yc $row] + lappend coords $x $y + set lasto $o + } + incr col $o + incr row -1 + } + set x [xc $row $col] + set y [yc $row] + lappend coords $x $y + if {$i == 0} { + # draw the link to the first child as part of this line + incr row -1 + set child [lindex $displayorder $row] + set ccol [lsearch -exact [lindex $rowidlist $row] $child] + if {$ccol >= 0} { + set x [xc $row $ccol] + set y [yc $row] + if {$ccol < $col - 1} { + lappend coords [xc $row [expr {$col - 1}]] [yc $row] + } elseif {$ccol > $col + 1} { + lappend coords [xc $row [expr {$col + 1}]] [yc $row] + } + lappend coords $x $y + } + } + if {[llength $coords] < 4} return + set last [expr {[llength $idrowranges($id)] / 2 - 1}] + set arrow [expr {2 * ($i > 0) + ($i < $last)}] + set arrow [lindex {none first last both} $arrow] + set t [$canv create line $coords -width [linewidth $id] \ + -fill $colormap($id) -tags lines.$id -arrow $arrow] + $canv lower $t + bindline $t $id +} + +proc drawparentlinks {id row col olds} { + global rowidlist canv colormap idrowranges + + set row2 [expr {$row + 1}] + set x [xc $row $col] + set y [yc $row] + set y2 [yc $row2] + set ids [lindex $rowidlist $row2] + # rmx = right-most X coord used + set rmx 0 + foreach p $olds { + if {[info exists idrowranges($p)] && + $row2 == [lindex $idrowranges($p) 0] && + $row2 < [lindex $idrowranges($p) 1]} { + # drawlineseg will do this one for us + continue + } + set i [lsearch -exact $ids $p] + if {$i < 0} { + puts "oops, parent $p of $id not in list" + continue + } + assigncolor $p + # should handle duplicated parents here... + set coords [list $x $y] + if {$i < $col - 1} { + lappend coords [xc $row [expr {$i + 1}]] $y + } elseif {$i > $col + 1} { + lappend coords [xc $row [expr {$i - 1}]] $y + } + set x2 [xc $row2 $i] + if {$x2 > $rmx} { + set rmx $x2 + } + lappend coords $x2 $y2 + set t [$canv create line $coords -width [linewidth $p] \ + -fill $colormap($p) -tags lines.$p] + $canv lower $t + bindline $t $p + } + return $rmx +} + +proc drawlines {id} { + global colormap canv + global idrowranges idrangedrawn + global children iddrawn commitrow rowidlist + + $canv delete lines.$id + set nr [expr {[llength $idrowranges($id)] / 2}] + for {set i 0} {$i < $nr} {incr i} { + if {[info exists idrangedrawn($id,$i)]} { + drawlineseg $id $i + } + } + if {[info exists children($id)]} { + foreach child $children($id) { + if {[info exists iddrawn($child)]} { + set row $commitrow($child) + set col [lsearch -exact [lindex $rowidlist $row] $child] + if {$col >= 0} { + drawparentlinks $child $row $col [list $id] + } + } + } + } +} + +proc drawcmittext {id row col rmx} { + global linespc canv canv2 canv3 canvy0 + global commitlisted commitinfo rowidlist + global rowtextx idpos idtags idheads idotherrefs + global linehtag linentag linedtag + global mainfont namefont + + set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}] + set x [xc $row $col] + set y [yc $row] + set orad [expr {$linespc / 3}] + set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \ + [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \ + -fill $ofill -outline black -width 1] + $canv raise $t + $canv bind $t <1> {selcanvline {} %x %y} + set xt [xc $row [llength [lindex $rowidlist $row]]] + if {$xt < $rmx} { + set xt $rmx + } + set rowtextx($row) $xt + set idpos($id) [list $x $xt $y] + if {[info exists idtags($id)] || [info exists idheads($id)] + || [info exists idotherrefs($id)]} { + set xt [drawtags $id $x $xt $y] + } + set headline [lindex $commitinfo($id) 0] + set name [lindex $commitinfo($id) 1] + set date [lindex $commitinfo($id) 2] + set date [formatdate $date] + set linehtag($row) [$canv create text $xt $y -anchor w \ + -text $headline -font $mainfont ] + $canv bind $linehtag($row) "rowmenu %X %Y $id" + set linentag($row) [$canv2 create text 3 $y -anchor w \ + -text $name -font $namefont] + set linedtag($row) [$canv3 create text 3 $y -anchor w \ + -text $date -font $mainfont] +} + +proc drawcmitrow {row} { + global displayorder rowidlist + global idrowranges idrangedrawn iddrawn + global commitinfo commitlisted parents numcommits + + if {$row >= $numcommits} return + foreach id [lindex $rowidlist $row] { + if {![info exists idrowranges($id)]} continue + set i -1 + foreach {s e} $idrowranges($id) { + incr i + if {$row < $s} continue + if {$e eq {}} break + if {$row <= $e} { + if {$e < $numcommits && ![info exists idrangedrawn($id,$i)]} { + drawlineseg $id $i + set idrangedrawn($id,$i) 1 + } + break + } + } + } + + set id [lindex $displayorder $row] + if {[info exists iddrawn($id)]} return + set col [lsearch -exact [lindex $rowidlist $row] $id] + if {$col < 0} { + puts "oops, row $row id $id not in list" + return + } + if {![info exists commitinfo($id)]} { + getcommit $id + } + assigncolor $id + if {[info exists commitlisted($id)] && [info exists parents($id)] + && $parents($id) ne {}} { + set rmx [drawparentlinks $id $row $col $parents($id)] + } else { + set rmx 0 + } + drawcmittext $id $row $col $rmx + set iddrawn($id) 1 +} + +proc drawfrac {f0 f1} { + global numcommits canv + global linespc + + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {} || $ymax == 0} return + set y0 [expr {int($f0 * $ymax)}] + set row [expr {int(($y0 - 3) / $linespc) - 1}] + if {$row < 0} { + set row 0 + } + set y1 [expr {int($f1 * $ymax)}] + set endrow [expr {int(($y1 - 3) / $linespc) + 1}] + if {$endrow >= $numcommits} { + set endrow [expr {$numcommits - 1}] + } + for {} {$row <= $endrow} {incr row} { + drawcmitrow $row + } +} + +proc drawvisible {} { + global canv + eval drawfrac [$canv yview] +} + +proc clear_display {} { + global iddrawn idrangedrawn + + allcanvs delete all + catch {unset iddrawn} + catch {unset idrangedrawn} +} + proc assigncolor {id} { - global colormap commcolors colors nextcolor + global colormap colors nextcolor global parents nparents children nchildren global cornercrossings crossings if {[info exists colormap($id)]} return set ncolors [llength $colors] - if {$nparents($id) <= 1 && $nchildren($id) == 1} { + if {$nchildren($id) == 1} { set child [lindex $children($id) 0] if {[info exists colormap($child)] && $nparents($child) == 1} { @@ -845,28 +1540,6 @@ proc assigncolor {id} { set colormap($id) $c } -proc initgraph {} { - global canvy canvy0 lineno numcommits nextcolor linespc - global nchildren ncleft - global displist nhyperspace - - allcanvs delete all - set nextcolor 0 - set canvy $canvy0 - set lineno -1 - set numcommits 0 - foreach v {mainline mainlinearrow sidelines colormap cornercrossings - crossings idline lineid} { - global $v - catch {unset $v} - } - foreach id [array names nchildren] { - set ncleft($id) $nchildren($id) - } - set displist {} - set nhyperspace 0 -} - proc bindline {t id} { global canv @@ -876,121 +1549,10 @@ proc bindline {t id} { $canv bind $t "lineclick %x %y $id 1" } -proc drawlines {id xtra delold} { - global mainline mainlinearrow sidelines lthickness colormap canv - - if {$delold} { - $canv delete lines.$id - } - if {[info exists mainline($id)]} { - set t [$canv create line $mainline($id) \ - -width [expr {($xtra + 1) * $lthickness}] \ - -fill $colormap($id) -tags lines.$id \ - -arrow $mainlinearrow($id)] - $canv lower $t - bindline $t $id - } - if {[info exists sidelines($id)]} { - foreach ls $sidelines($id) { - set coords [lindex $ls 0] - set thick [lindex $ls 1] - set arrow [lindex $ls 2] - set t [$canv create line $coords -fill $colormap($id) \ - -width [expr {($thick + $xtra) * $lthickness}] \ - -arrow $arrow -tags lines.$id] - $canv lower $t - bindline $t $id - } - } -} - -# level here is an index in displist -proc drawcommitline {level} { - global parents children nparents displist - global canv canv2 canv3 mainfont namefont canvy linespc - global lineid linehtag linentag linedtag commitinfo - global colormap numcommits currentparents dupparents - global idtags idline idheads idotherrefs - global lineno lthickness mainline mainlinearrow sidelines - global commitlisted rowtextx idpos lastuse displist - global oldnlines olddlevel olddisplist - - incr numcommits - incr lineno - set id [lindex $displist $level] - set lastuse($id) $lineno - set lineid($lineno) $id - set idline($id) $lineno - set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}] - if {![info exists commitinfo($id)]} { - readcommit $id - if {![info exists commitinfo($id)]} { - set commitinfo($id) {"No commit information available"} - set nparents($id) 0 - } - } - assigncolor $id - set currentparents {} - set dupparents {} - if {[info exists commitlisted($id)] && [info exists parents($id)]} { - foreach p $parents($id) { - if {[lsearch -exact $currentparents $p] < 0} { - lappend currentparents $p - } else { - # remember that this parent was listed twice - lappend dupparents $p - } - } - } - set x [xcoord $level $level $lineno] - set y1 $canvy - set canvy [expr {$canvy + $linespc}] - allcanvs conf -scrollregion \ - [list 0 0 0 [expr {$y1 + 0.5 * $linespc + 2}]] - if {[info exists mainline($id)]} { - lappend mainline($id) $x $y1 - if {$mainlinearrow($id) ne "none"} { - set mainline($id) [trimdiagstart $mainline($id)] - } - } - 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}] \ - -fill $ofill -outline black -width 1] - $canv raise $t - $canv bind $t <1> {selcanvline {} %x %y} - set xt [xcoord [llength $displist] $level $lineno] - if {[llength $currentparents] > 2} { - set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}] - } - set rowtextx($lineno) $xt - set idpos($id) [list $x $xt $y1] - if {[info exists idtags($id)] || [info exists idheads($id)] - || [info exists idotherrefs($id)]} { - set xt [drawtags $id $x $xt $y1] - } - 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" - set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \ - -text $name -font $namefont] - set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \ - -text $date -font $mainfont] - - set olddlevel $level - set olddisplist $displist - set oldnlines [llength $displist] -} - proc drawtags {id x xt y1} { global idtags idheads idotherrefs global linespc lthickness - global canv mainfont idline rowtextx + global canv mainfont commitrow rowtextx set marks {} set ntags 0 @@ -1033,7 +1595,7 @@ proc drawtags {id x xt y1} { $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \ -width 1 -outline black -fill yellow -tags tag.$id] $canv bind $t <1> [list showtag $tag 1] - set rowtextx($idline($id)) [expr {$xr + $linespc}] + set rowtextx($commitrow($id)) [expr {$xr + $linespc}] } else { # draw a head or other ref if {[incr nheads -1] >= 0} { @@ -1054,11 +1616,32 @@ proc drawtags {id x xt y1} { return $xt } -proc notecrossings {id lo hi corner} { - global olddisplist crossings cornercrossings +proc checkcrossings {row endrow} { + global displayorder parents rowidlist + + for {} {$row < $endrow} {incr row} { + set id [lindex $displayorder $row] + set i [lsearch -exact [lindex $rowidlist $row] $id] + if {$i < 0} continue + set idlist [lindex $rowidlist [expr {$row+1}]] + foreach p $parents($id) { + set j [lsearch -exact $idlist $p] + if {$j > 0} { + if {$j < $i - 1} { + notecrossings $row $p $j $i [expr {$j+1}] + } elseif {$j > $i + 1} { + notecrossings $row $p $i $j [expr {$j-1}] + } + } + } + } +} + +proc notecrossings {row id lo hi corner} { + global rowidlist crossings cornercrossings for {set i $lo} {[incr i] < $hi} {} { - set p [lindex $olddisplist $i] + set p [lindex [lindex $rowidlist $row] $i] if {$p == {}} continue if {$i == $corner} { if {![info exists cornercrossings($id)] @@ -1094,512 +1677,23 @@ proc xcoord {i level ln} { return $x } -# it seems Tk can't draw arrows on the end of diagonal line segments... -proc trimdiagend {line} { - while {[llength $line] > 4} { - set x1 [lindex $line end-3] - set y1 [lindex $line end-2] - set x2 [lindex $line end-1] - set y2 [lindex $line end] - if {($x1 == $x2) != ($y1 == $y2)} break - set line [lreplace $line end-1 end] - } - return $line -} - -proc trimdiagstart {line} { - while {[llength $line] > 4} { - set x1 [lindex $line 0] - set y1 [lindex $line 1] - set x2 [lindex $line 2] - set y2 [lindex $line 3] - if {($x1 == $x2) != ($y1 == $y2)} break - set line [lreplace $line 0 1] - } - return $line -} - -proc drawslants {id needonscreen nohs} { - global canv mainline mainlinearrow sidelines - global canvx0 canvy xspc1 xspc2 lthickness - global currentparents dupparents - global lthickness linespc canvy colormap lineno geometry - global maxgraphpct maxwidth - global displist onscreen lastuse - global parents commitlisted - global oldnlines olddlevel olddisplist - global nhyperspace numcommits nnewparents - - if {$lineno < 0} { - lappend displist $id - set onscreen($id) 1 - return 0 - } - - set y1 [expr {$canvy - $linespc}] - set y2 $canvy - - # work out what we need to get back on screen - set reins {} - if {$onscreen($id) < 0} { - # next to do isn't displayed, better get it on screen... - lappend reins [list $id 0] - } - # make sure all the previous commits's parents are on the screen - foreach p $currentparents { - if {$onscreen($p) < 0} { - lappend reins [list $p 0] - } - } - # bring back anything requested by caller - if {$needonscreen ne {}} { - lappend reins $needonscreen - } - - # try the shortcut - if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} { - set dlevel $olddlevel - set x [xcoord $dlevel $dlevel $lineno] - set mainline($id) [list $x $y1] - set mainlinearrow($id) none - set lastuse($id) $lineno - set displist [lreplace $displist $dlevel $dlevel $id] - set onscreen($id) 1 - set xspc1([expr {$lineno + 1}]) $xspc1($lineno) - return $dlevel - } - - # update displist - set displist [lreplace $displist $olddlevel $olddlevel] - set j $olddlevel - foreach p $currentparents { - set lastuse($p) $lineno - if {$onscreen($p) == 0} { - set displist [linsert $displist $j $p] - set onscreen($p) 1 - incr j - } - } - if {$onscreen($id) == 0} { - lappend displist $id - set onscreen($id) 1 - } - - # remove the null entry if present - set nullentry [lsearch -exact $displist {}] - if {$nullentry >= 0} { - set displist [lreplace $displist $nullentry $nullentry] - } - - # bring back the ones we need now (if we did it earlier - # it would change displist and invalidate olddlevel) - foreach pi $reins { - # test again in case of duplicates in reins - set p [lindex $pi 0] - if {$onscreen($p) < 0} { - set onscreen($p) 1 - set lastuse($p) $lineno - set displist [linsert $displist [lindex $pi 1] $p] - incr nhyperspace -1 - } - } - - set lastuse($id) $lineno - - # see if we need to make any lines jump off into hyperspace - set displ [llength $displist] - if {$displ > $maxwidth} { - set ages {} - foreach x $displist { - lappend ages [list $lastuse($x) $x] - } - set ages [lsort -integer -index 0 $ages] - set k 0 - while {$displ > $maxwidth} { - set use [lindex $ages $k 0] - set victim [lindex $ages $k 1] - if {$use >= $lineno - 5} break - incr k - if {[lsearch -exact $nohs $victim] >= 0} continue - set i [lsearch -exact $displist $victim] - set displist [lreplace $displist $i $i] - set onscreen($victim) -1 - incr nhyperspace - incr displ -1 - if {$i < $nullentry} { - incr nullentry -1 - } - set x [lindex $mainline($victim) end-1] - lappend mainline($victim) $x $y1 - set line [trimdiagend $mainline($victim)] - set arrow "last" - if {$mainlinearrow($victim) ne "none"} { - set line [trimdiagstart $line] - set arrow "both" - } - lappend sidelines($victim) [list $line 1 $arrow] - unset mainline($victim) - } - } - - set dlevel [lsearch -exact $displist $id] - - # If we are reducing, put in a null entry - if {$displ < $oldnlines} { - # does the next line look like a merge? - # i.e. does it have > 1 new parent? - if {$nnewparents($id) > 1} { - set i [expr {$dlevel + 1}] - } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} { - set i $olddlevel - if {$nullentry >= 0 && $nullentry < $i} { - incr i -1 - } - } elseif {$nullentry >= 0} { - set i $nullentry - while {$i < $displ - && [lindex $olddisplist $i] == [lindex $displist $i]} { - incr i - } - } else { - set i $olddlevel - if {$dlevel >= $i} { - incr i - } - } - if {$i < $displ} { - set displist [linsert $displist $i {}] - incr displ - if {$dlevel >= $i} { - incr dlevel - } - } - } - - # decide on the line spacing for the next line - set lj [expr {$lineno + 1}] - set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}] - if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} { - set xspc1($lj) $xspc2 - } else { - set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}] - if {$xspc1($lj) < $lthickness} { - set xspc1($lj) $lthickness - } - } - - foreach idi $reins { - set id [lindex $idi 0] - set j [lsearch -exact $displist $id] - set xj [xcoord $j $dlevel $lj] - set mainline($id) [list $xj $y2] - set mainlinearrow($id) first - } - - set i -1 - foreach id $olddisplist { - incr i - if {$id == {}} continue - if {$onscreen($id) <= 0} continue - set xi [xcoord $i $olddlevel $lineno] - if {$i == $olddlevel} { - foreach p $currentparents { - set j [lsearch -exact $displist $p] - set coords [list $xi $y1] - set xj [xcoord $j $dlevel $lj] - if {$xj < $xi - $linespc} { - lappend coords [expr {$xj + $linespc}] $y1 - notecrossings $p $j $i [expr {$j + 1}] - } elseif {$xj > $xi + $linespc} { - lappend coords [expr {$xj - $linespc}] $y1 - notecrossings $p $i $j [expr {$j - 1}] - } - if {[lsearch -exact $dupparents $p] >= 0} { - # draw a double-width line to indicate the doubled parent - lappend coords $xj $y2 - lappend sidelines($p) [list $coords 2 none] - if {![info exists mainline($p)]} { - set mainline($p) [list $xj $y2] - set mainlinearrow($p) none - } - } else { - # normal case, no parent duplicated - set yb $y2 - set dx [expr {abs($xi - $xj)}] - if {0 && $dx < $linespc} { - set yb [expr {$y1 + $dx}] - } - if {![info exists mainline($p)]} { - if {$xi != $xj} { - lappend coords $xj $yb - } - set mainline($p) $coords - set mainlinearrow($p) none - } else { - lappend coords $xj $yb - if {$yb < $y2} { - lappend coords $xj $y2 - } - lappend sidelines($p) [list $coords 1 none] - } - } - } - } else { - set j $i - if {[lindex $displist $i] != $id} { - set j [lsearch -exact $displist $id] - } - if {$j != $i || $xspc1($lineno) != $xspc1($lj) - || ($olddlevel < $i && $i < $dlevel) - || ($dlevel < $i && $i < $olddlevel)} { - set xj [xcoord $j $dlevel $lj] - lappend mainline($id) $xi $y1 $xj $y2 - } - } - } - return $dlevel -} - -# search for x in a list of lists -proc llsearch {llist x} { - set i 0 - foreach l $llist { - if {$l == $x || [lsearch -exact $l $x] >= 0} { - return $i - } - incr i - } - return -1 -} - -proc drawmore {reading} { - global displayorder numcommits ncmupdate nextupdate - global stopped nhyperspace parents commitlisted - global maxwidth onscreen displist currentparents olddlevel - - set n [llength $displayorder] - while {$numcommits < $n} { - set id [lindex $displayorder $numcommits] - set ctxend [expr {$numcommits + 10}] - if {!$reading && $ctxend > $n} { - set ctxend $n - } - set dlist {} - if {$numcommits > 0} { - set dlist [lreplace $displist $olddlevel $olddlevel] - set i $olddlevel - foreach p $currentparents { - if {$onscreen($p) == 0} { - set dlist [linsert $dlist $i $p] - incr i - } - } - } - set nohs {} - set reins {} - set isfat [expr {[llength $dlist] > $maxwidth}] - if {$nhyperspace > 0 || $isfat} { - if {$ctxend > $n} break - # work out what to bring back and - # what we want to don't want to send into hyperspace - set room 1 - for {set k $numcommits} {$k < $ctxend} {incr k} { - set x [lindex $displayorder $k] - set i [llsearch $dlist $x] - if {$i < 0} { - set i [llength $dlist] - lappend dlist $x - } - if {[lsearch -exact $nohs $x] < 0} { - lappend nohs $x - } - if {$reins eq {} && $onscreen($x) < 0 && $room} { - set reins [list $x $i] - } - set newp {} - if {[info exists commitlisted($x)]} { - set right 0 - foreach p $parents($x) { - if {[llsearch $dlist $p] < 0} { - lappend newp $p - if {[lsearch -exact $nohs $p] < 0} { - lappend nohs $p - } - if {$reins eq {} && $onscreen($p) < 0 && $room} { - set reins [list $p [expr {$i + $right}]] - } - } - set right 1 - } - } - set l [lindex $dlist $i] - if {[llength $l] == 1} { - set l $newp - } else { - set j [lsearch -exact $l $x] - set l [concat [lreplace $l $j $j] $newp] - } - set dlist [lreplace $dlist $i $i $l] - if {$room && $isfat && [llength $newp] <= 1} { - set room 0 - } - } - } - - set dlevel [drawslants $id $reins $nohs] - drawcommitline $dlevel - if {[clock clicks -milliseconds] >= $nextupdate - && $numcommits >= $ncmupdate} { - doupdate $reading - if {$stopped} break - } - } -} - -# level here is an index in todo -proc updatetodo {level noshortcut} { - global ncleft todo nnewparents - global commitlisted parents onscreen - - set id [lindex $todo $level] - set olds {} - if {[info exists commitlisted($id)]} { - foreach p $parents($id) { - if {[lsearch -exact $olds $p] < 0} { - lappend olds $p - } - } - } - if {!$noshortcut && [llength $olds] == 1} { - set p [lindex $olds 0] - if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} { - set ncleft($p) 0 - set todo [lreplace $todo $level $level $p] - set onscreen($p) 0 - set nnewparents($id) 1 - return 0 - } - } - - set todo [lreplace $todo $level $level] - set i $level - set n 0 - foreach p $olds { - incr ncleft($p) -1 - set k [lsearch -exact $todo $p] - if {$k < 0} { - set todo [linsert $todo $i $p] - set onscreen($p) 0 - incr i - incr n - } - } - set nnewparents($id) $n - - return 1 -} - -proc decidenext {{noread 0}} { - global ncleft todo - global datemode cdate - global commitinfo - - # choose which one to do next time around - set todol [llength $todo] - set level -1 - set latest {} - for {set k $todol} {[incr k -1] >= 0} {} { - set p [lindex $todo $k] - if {$ncleft($p) == 0} { - if {$datemode} { - if {![info exists commitinfo($p)]} { - if {$noread} { - return {} - } - readcommit $p - } - if {$latest == {} || $cdate($p) > $latest} { - set level $k - set latest $cdate($p) - } - } else { - set level $k - break - } - } - } - - return $level -} - -proc drawcommit {id reading} { - global phase todo nchildren datemode nextupdate revlistorder ncleft - global numcommits ncmupdate displayorder todo onscreen parents - global commitlisted commitordered - - if {$phase != "incrdraw"} { - set phase incrdraw - set displayorder {} - set todo {} - initgraph - catch {unset commitordered} - } - set commitordered($id) 1 - if {$nchildren($id) == 0} { - lappend todo $id - set onscreen($id) 0 - } - 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 == {} || $level < 0} return - while 1 { - set id [lindex $todo $level] - if {![info exists commitordered($id)]} { - break - } - lappend displayorder [lindex $todo $level] - if {[updatetodo $level $datemode]} { - set level [decidenext 1] - if {$level == {} || $level < 0} break - } - } - } - drawmore $reading -} - proc finishcommits {} { - global phase oldcommits commits + global commitidx phase global canv mainfont ctext maincursor textcursor - global parents displayorder todo + global findinprogress - if {$phase == "incrdraw" || $phase == "removecommits"} { - foreach id $oldcommits { - lappend commits $id - drawcommit $id 0 - } - set oldcommits {} + if {$commitidx > 0} { drawrest - } elseif {$phase == "updatecommits"} { - # there were no new commits, in fact - set commits $oldcommits - set oldcommits {} - set phase {} } else { $canv delete all $canv create text 3 3 -anchor nw -text "No commits selected" \ -font $mainfont -tags textitems - set phase {} } - . config -cursor $maincursor - settextcursor $textcursor + if {![info exists findinprogress]} { + . config -cursor $maincursor + settextcursor $textcursor + } + set phase {} } # Don't change the text pane cursor if it is currently the hand cursor, @@ -1613,61 +1707,20 @@ proc settextcursor {c} { set curtextcursor $c } -proc drawgraph {} { - global nextupdate startmsecs ncmupdate - global displayorder onscreen - - if {$displayorder == {}} return - set startmsecs [clock clicks -milliseconds] - set nextupdate [expr {$startmsecs + 100}] - set ncmupdate 1 - initgraph - foreach id $displayorder { - set onscreen($id) 0 - } - drawmore 0 -} - proc drawrest {} { - global phase stopped redisplaying selectedline - global datemode todo displayorder ncleft - global numcommits ncmupdate - global nextupdate startmsecs revlistorder + global numcommits + global startmsecs + global canvy0 numcommits linespc + global rowlaidout commitidx - set level [decidenext] - if {$level >= 0} { - set phase drawgraph - while 1 { - lappend displayorder [lindex $todo $level] - set hard [updatetodo $level $datemode] - if {$hard} { - set level [decidenext] - if {$level < 0} break - } - } - } - if {$todo != {}} { - puts "ERROR: none of the pending commits can be done yet:" - foreach p $todo { - puts " $p ($ncleft($p))" - } - } + set row $rowlaidout + layoutrows $rowlaidout $commitidx 1 + layouttail + optimize_rows $row 0 $commitidx + showstuff $commitidx - drawmore 0 - set phase {} set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}] #puts "overall $drawmsecs ms for $numcommits commits" - if {$redisplaying} { - if {$stopped == 0 && [info exists selectedline]} { - selectline $selectedline 0 - } - if {$stopped == 1} { - set stopped 0 - after idle drawgraph - } else { - set redisplaying 0 - } - } } proc findmatches {f} { @@ -1692,9 +1745,10 @@ proc findmatches {f} { proc dofind {} { global findtype findloc findstring markedmatches commitinfo - global numcommits lineid linehtag linentag linedtag + global numcommits displayorder linehtag linentag linedtag global mainfont namefont canv canv2 canv3 selectedline - global matchinglines foundstring foundstrlen + global matchinglines foundstring foundstrlen matchstring + global commitdata stopfindproc unmarkmatches @@ -1711,6 +1765,8 @@ proc dofind {} { } set foundstrlen [string length $findstring] if {$foundstrlen == 0} return + regsub -all {[*?\[\\]} $foundstring {\\&} matchstring + set matchstring "*$matchstring*" if {$findloc == "Files"} { findfiles return @@ -1722,8 +1778,21 @@ proc dofind {} { } set didsel 0 set fldtypes {Headline Author Date Committer CDate Comment} - for {set l 0} {$l < $numcommits} {incr l} { - set id $lineid($l) + set l -1 + foreach id $displayorder { + set d $commitdata($id) + incr l + if {$findtype == "Regexp"} { + set doesmatch [regexp $foundstring $d] + } elseif {$findtype == "IgnCase"} { + set doesmatch [string match -nocase $matchstring $d] + } else { + set doesmatch [string match $matchstring $d] + } + if {!$doesmatch} continue + if {![info exists commitinfo($id)]} { + getcommit $id + } set info $commitinfo($id) set doesmatch 0 foreach f $info ty $fldtypes { @@ -1734,10 +1803,13 @@ proc dofind {} { if {$matches == {}} continue set doesmatch 1 if {$ty == "Headline"} { + drawcmitrow $l markmatches $canv $l $f $linehtag($l) $matches $mainfont } elseif {$ty == "Author"} { + drawcmitrow $l markmatches $canv2 $l $f $linentag($l) $matches $namefont } elseif {$ty == "Date"} { + drawcmitrow $l markmatches $canv3 $l $f $linedtag($l) $matches $mainfont } } @@ -1845,7 +1917,7 @@ proc stopfindproc {{done 0}} { proc findpatches {} { global findstring selectedline numcommits global findprocpid findprocfile - global finddidsel ctext lineid findinprogress + global finddidsel ctext displayorder findinprogress global findinsertpos if {$numcommits == 0} return @@ -1862,7 +1934,7 @@ proc findpatches {} { if {[incr l] >= $numcommits} { set l 0 } - append inputids $lineid($l) "\n" + append inputids [lindex $displayorder $l] "\n" } if {[catch { @@ -1886,7 +1958,7 @@ proc findpatches {} { proc readfindproc {} { global findprocfile finddidsel - global idline matchinglines findinsertpos + global commitrow matchinglines findinsertpos set n [gets $findprocfile line] if {$n < 0} { @@ -1903,11 +1975,11 @@ proc readfindproc {} { stopfindproc return } - if {![info exists idline($id)]} { + if {![info exists commitrow($id)]} { puts stderr "spurious id: $id" return } - set l $idline($id) + set l $commitrow($id) insertmatch $l $id } @@ -1933,7 +2005,7 @@ proc insertmatch {l id} { } proc findfiles {} { - global selectedline numcommits lineid ctext + global selectedline numcommits displayorder ctext global ffileline finddidsel parents nparents global findinprogress findstartline findinsertpos global treediffs fdiffid fdiffsneeded fdiffpos @@ -1951,7 +2023,7 @@ proc findfiles {} { set diffsneeded {} set fdiffsneeded {} while 1 { - set id $lineid($l) + set id [lindex $displayorder $l] if {$findmergefiles || $nparents($id) == 1} { if {![info exists treediffs($id)]} { append diffsneeded "$id\n" @@ -1980,7 +2052,7 @@ proc findfiles {} { set finddidsel 0 set findinsertpos end - set id $lineid($l) + set id [lindex $displayorder $l] . config -cursor watch settextcursor watch set findinprogress 1 @@ -2050,7 +2122,7 @@ proc donefilediff {} { proc findcont {id} { global findid treediffs parents nparents global ffileline findstartline finddidsel - global lineid numcommits matchinglines findinprogress + global displayorder numcommits matchinglines findinprogress global findmergefiles set l $ffileline @@ -2077,7 +2149,7 @@ proc findcont {id} { set l 0 } if {$l == $findstartline} break - set id $lineid($l) + set id [lindex $displayorder $l] } stopfindproc if {!$finddidsel} { @@ -2088,8 +2160,9 @@ proc findcont {id} { # mark a commit as matching by putting a yellow background # behind the headline proc markheadline {l id} { - global canv mainfont linehtag commitinfo + global canv mainfont linehtag + drawcmitrow $l set bbox [$canv bbox $linehtag($l)] set t [$canv create rect $bbox -outline {} -tags matches -fill yellow] $canv lower $t @@ -2123,7 +2196,7 @@ proc unmarkmatches {} { proc selcanvline {w x y} { global canv canvy0 ctext linespc - global lineid linehtag linentag linedtag rowtextx + global rowtextx set ymax [lindex [$canv cget -scrollregion] 3] if {$ymax == {}} return set yfrac [lindex [$canv yview] 0] @@ -2151,7 +2224,7 @@ proc commit_descriptor {p} { # append some text to the ctext widget, and make any SHA1 ID # that we know about be a clickable link. proc appendwithlinks {text} { - global ctext idline linknum + global ctext commitrow linknum set start [$ctext index "end - 1c"] $ctext insert end $text @@ -2161,11 +2234,11 @@ proc appendwithlinks {text} { set s [lindex $l 0] set e [lindex $l 1] set linkid [string range $text $s $e] - if {![info exists idline($linkid)]} continue + if {![info exists commitrow($linkid)]} continue incr e $ctext tag add link "$start + $s c" "$start + $e c" $ctext tag add link$linknum "$start + $s c" "$start + $e c" - $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1] + $ctext tag bind link$linknum <1> [list selectline $commitrow($linkid) 1] incr linknum } $ctext tag conf link -foreground blue -underline 1 @@ -2175,27 +2248,15 @@ proc appendwithlinks {text} { proc selectline {l isnew} { global canv canv2 canv3 ctext commitinfo selectedline - global lineid linehtag linentag linedtag + global displayorder linehtag linentag linedtag global canvy0 linespc parents nparents children global cflist currentid sha1entry - global commentend idtags idline linknum - global mergemax + global commentend idtags linknum + global mergemax numcommits $canv delete hover normalline - if {![info exists lineid($l)] || ![info exists linehtag($l)]} return - $canv delete secsel - set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ - -tags secsel -fill [$canv cget -selectbackground]] - $canv lower $t - $canv2 delete secsel - set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ - -tags secsel -fill [$canv2 cget -selectbackground]] - $canv2 lower $t - $canv3 delete secsel - set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ - -tags secsel -fill [$canv3 cget -selectbackground]] - $canv3 lower $t + if {$l < 0 || $l >= $numcommits} return set y [expr {$canvy0 + $l * $linespc}] set ymax [lindex [$canv cget -scrollregion] 3] set ytop [expr {$y - $linespc - 1}] @@ -2229,15 +2290,30 @@ proc selectline {l isnew} { set newtop 0 } allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}] + drawvisible } + if {![info exists linehtag($l)]} return + $canv delete secsel + set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ + -tags secsel -fill [$canv cget -selectbackground]] + $canv lower $t + $canv2 delete secsel + set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ + -tags secsel -fill [$canv2 cget -selectbackground]] + $canv2 lower $t + $canv3 delete secsel + set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ + -tags secsel -fill [$canv3 cget -selectbackground]] + $canv3 lower $t + if {$isnew} { addtohistory [list selectline $l 0] } set selectedline $l - set id $lineid($l) + set id [lindex $displayorder $l] set currentid $id $sha1entry delete 0 end $sha1entry insert 0 $id @@ -2375,9 +2451,10 @@ proc goforw {} { proc mergediff {id} { global parents diffmergeid diffopts mdifffd - global difffilestart + global difffilestart diffids set diffmergeid $id + set diffids $id catch {unset difffilestart} # this doesn't seem to actually affect anything... set env(GIT_DIFF_OPTS) $diffopts @@ -2394,7 +2471,7 @@ proc mergediff {id} { proc getmergediffline {mdf id} { global diffmergeid ctext cflist nextupdate nparents mergemax - global difffilestart + global difffilestart mdifffd set n [gets $mdf line] if {$n < 0} { @@ -2403,7 +2480,8 @@ proc getmergediffline {mdf id} { } return } - if {![info exists diffmergeid] || $id != $diffmergeid} { + if {![info exists diffmergeid] || $id != $diffmergeid + || $mdf != $mdifffd($id)} { return } $ctext conf -state normal @@ -2513,13 +2591,11 @@ proc gettreediffline {gdtf ids} { set treediffs($ids) $treediff unset treepending if {$ids != $diffids} { - gettreediffs $diffids - } else { - if {[info exists diffmergeid]} { - contmergediff $ids - } else { - addtocflist $ids + if {![info exists diffmergeid]} { + gettreediffs $diffids } + } else { + addtocflist $ids } return } @@ -2595,7 +2671,9 @@ proc getblobdiffline {bdf ids} { set pad [string range "----------------------------------------" 1 $l] $ctext insert end "$pad $header $pad\n" filesep set diffinhdr 1 - } elseif {[regexp {^(---|\+\+\+)} $line]} { + } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} { + # do nothing + } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} { set diffinhdr 0 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \ $line match f1l f1c f2l f2c rest]} { @@ -2662,22 +2740,27 @@ proc setcoords {} { set linespc [font metrics $mainfont -linespace] set charspc [font measure $mainfont "m"] - set canvy0 [expr {3 + 0.5 * $linespc}] - set canvx0 [expr {3 + 0.5 * $linespc}] + set canvy0 [expr {int(3 + 0.5 * $linespc)}] + set canvx0 [expr {int(3 + 0.5 * $linespc)}] set lthickness [expr {int($linespc / 9) + 1}] set xspc1(0) $linespc set xspc2 $linespc } proc redisplay {} { - global stopped redisplaying phase - if {$stopped > 1} return - if {$phase == "getcommits"} return - set redisplaying 1 - if {$phase == "drawgraph" || $phase == "incrdraw"} { - set stopped 1 - } else { - drawgraph + global canv canvy0 linespc numcommits + global selectedline + + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {} || $ymax == 0} return + set span [$canv yview] + clear_display + allcanvs conf -scrollregion \ + [list 0 0 0 [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]] + allcanvs yview moveto [lindex $span 0] + drawvisible + if {[info exists selectedline]} { + selectline $selectedline 0 } } @@ -2724,8 +2807,8 @@ proc sha1change {n1 n2 op} { } proc gotocommit {} { - global sha1string currentid idline tagids - global lineid numcommits + global sha1string currentid commitrow tagids + global displayorder numcommits if {$sha1string == {} || ([info exists currentid] && $sha1string == $currentid)} return @@ -2735,9 +2818,9 @@ proc gotocommit {} { set id [string tolower $sha1string] if {[regexp {^[0-9a-f]{4,39}$} $id]} { set matches {} - for {set l 0} {$l < $numcommits} {incr l} { - if {[string match $id* $lineid($l)]} { - lappend matches $lineid($l) + foreach i $displayorder { + if {[string match $id* $i]} { + lappend matches $i } } if {$matches ne {}} { @@ -2749,8 +2832,8 @@ proc gotocommit {} { } } } - if {[info exists idline($id)]} { - selectline $idline($id) 1 + if {[info exists commitrow($id)]} { + selectline $commitrow($id) 1 return } if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} { @@ -2765,7 +2848,7 @@ proc lineenter {x y id} { global hoverx hovery hoverid hovertimer global commitinfo canv - if {![info exists commitinfo($id)]} return + if {![info exists commitinfo($id)] && ![getcommit $id]} return set hoverx $x set hovery $y set hoverid $id @@ -2825,65 +2908,26 @@ proc linehover {} { } proc clickisonarrow {id y} { - global mainline mainlinearrow sidelines lthickness + global lthickness idrowranges set thresh [expr {2 * $lthickness + 6}] - if {[info exists mainline($id)]} { - if {$mainlinearrow($id) ne "none"} { - if {abs([lindex $mainline($id) 1] - $y) < $thresh} { - return "up" - } - } - } - if {[info exists sidelines($id)]} { - foreach ls $sidelines($id) { - set coords [lindex $ls 0] - set arrow [lindex $ls 2] - if {$arrow eq "first" || $arrow eq "both"} { - if {abs([lindex $coords 1] - $y) < $thresh} { - return "up" - } - } - if {$arrow eq "last" || $arrow eq "both"} { - if {abs([lindex $coords end] - $y) < $thresh} { - return "down" - } - } + set n [expr {[llength $idrowranges($id)] - 1}] + for {set i 1} {$i < $n} {incr i} { + set row [lindex $idrowranges($id) $i] + if {abs([yc $row] - $y) < $thresh} { + return $i } } return {} } -proc arrowjump {id dirn y} { - global mainline sidelines canv canv2 canv3 +proc arrowjump {id n y} { + global idrowranges canv - set yt {} - if {$dirn eq "down"} { - if {[info exists mainline($id)]} { - set y1 [lindex $mainline($id) 1] - if {$y1 > $y} { - set yt $y1 - } - } - if {[info exists sidelines($id)]} { - foreach ls $sidelines($id) { - set y1 [lindex $ls 0 1] - if {$y1 > $y && ($yt eq {} || $y1 < $yt)} { - set yt $y1 - } - } - } - } else { - if {[info exists sidelines($id)]} { - foreach ls $sidelines($id) { - set y1 [lindex $ls 0 end] - if {$y1 < $y && ($yt eq {} || $y1 > $yt)} { - set yt $y1 - } - } - } - } - if {$yt eq {}} return + # 1 <-> 2, 3 <-> 4, etc... + set n [expr {(($n - 1) ^ 1) + 1}] + set row [lindex $idrowranges($id) $n] + set yt [yc $row] set ymax [lindex [$canv cget -scrollregion] 3] if {$ymax eq {} || $ymax <= 0} return set view [$canv yview] @@ -2892,21 +2936,20 @@ proc arrowjump {id dirn y} { if {$yfrac < 0} { set yfrac 0 } - $canv yview moveto $yfrac - $canv2 yview moveto $yfrac - $canv3 yview moveto $yfrac + allcanvs yview moveto $yfrac } proc lineclick {x y id isnew} { global ctext commitinfo children cflist canv thickerline + if {![info exists commitinfo($id)] && ![getcommit $id]} return unmarkmatches unselectline normalline $canv delete hover # draw this line thicker than normal - drawlines $id 1 1 set thickerline $id + drawlines $id if {$isnew} { set ymax [lindex [$canv cget -scrollregion] 3] if {$ymax eq {}} return @@ -2941,6 +2984,7 @@ proc lineclick {x y id isnew} { set i 0 foreach child $children($id) { incr i + if {![info exists commitinfo($child)] && ![getcommit $child]} continue set info $commitinfo($child) $ctext insert end "\n\t" $ctext insert end $child [list link link$i] @@ -2959,15 +3003,16 @@ proc lineclick {x y id isnew} { proc normalline {} { global thickerline if {[info exists thickerline]} { - drawlines $thickerline 0 1 + set id $thickerline unset thickerline + drawlines $id } } proc selbyid {id} { - global idline - if {[info exists idline($id)]} { - selectline $idline($id) 1 + global commitrow + if {[info exists commitrow($id)]} { + selectline $commitrow($id) 1 } } @@ -2980,9 +3025,9 @@ proc mstime {} { } proc rowmenu {x y id} { - global rowctxmenu idline selectedline rowmenuid + global rowctxmenu commitrow selectedline rowmenuid - if {![info exists selectedline] || $idline($id) eq $selectedline} { + if {![info exists selectedline] || $commitrow($id) eq $selectedline} { set state disabled } else { set state normal @@ -2995,15 +3040,15 @@ proc rowmenu {x y id} { } proc diffvssel {dirn} { - global rowmenuid selectedline lineid + global rowmenuid selectedline displayorder if {![info exists selectedline]} return if {$dirn} { - set oldid $lineid($selectedline) + set oldid [lindex $displayorder $selectedline] set newid $rowmenuid } else { set oldid $rowmenuid - set newid $lineid($selectedline) + set newid [lindex $displayorder $selectedline] } addtohistory [list doseldiff $oldid $newid] doseldiff $oldid $newid @@ -3185,13 +3230,14 @@ proc domktag {} { } proc redrawtags {id} { - global canv linehtag idline idpos selectedline + global canv linehtag commitrow idpos selectedline - if {![info exists idline($id)]} return + if {![info exists commitrow($id)]} return + drawcmitrow $commitrow($id) $canv delete tag.$id set xt [eval drawtags $id $idpos($id)] - $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2] - if {[info exists selectedline] && $selectedline == $idline($id)} { + $canv coords $linehtag($commitrow($id)) $xt [lindex $idpos($id) 2] + if {[info exists selectedline] && $selectedline == $commitrow($id)} { selectline $selectedline 0 } } @@ -3697,6 +3743,9 @@ set maxgraphpct 50 set maxwidth 16 set revlistorder 0 set fastdate 0 +set uparrowlen 7 +set downarrowlen 7 +set mingaplen 30 set colors {green red blue magenta darkgrey brown orange} @@ -3711,18 +3760,25 @@ foreach arg $argv { switch -regexp -- $arg { "^$" { } "^-d" { set datemode 1 } - "^-r" { set revlistorder 1 } default { lappend revtreeargs $arg } } } +# check that we can find a .git directory somewhere... +set gitdir [gitdir] +if {![file isdirectory $gitdir]} { + error_popup "Cannot find the git directory \"$gitdir\"." + exit 1 +} + set history {} set historyindex 0 +set optim_delay 16 + set stopped 0 -set redisplaying 0 set stuffsaved 0 set patchnum 0 setcoords diff --git a/ls-files.c b/ls-files.c index e42119c5ee..4a4af1ca3b 100644 --- a/ls-files.c +++ b/ls-files.c @@ -11,6 +11,7 @@ #include "cache.h" #include "quote.h" +static int abbrev = 0; static int show_deleted = 0; static int show_cached = 0; static int show_others = 0; @@ -20,6 +21,7 @@ static int show_unmerged = 0; static int show_modified = 0; static int show_killed = 0; static int show_other_directories = 0; +static int hide_empty_directories = 0; static int show_valid_bit = 0; static int line_terminator = '\n'; @@ -258,11 +260,12 @@ static int dir_exists(const char *dirname, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static void read_directory(const char *path, const char *base, int baselen) +static int read_directory(const char *path, const char *base, int baselen) { - DIR *dir = opendir(path); + DIR *fdir = opendir(path); + int contents = 0; - if (dir) { + if (fdir) { int exclude_stk; struct dirent *de; char fullname[MAXPATHLEN + 1]; @@ -270,7 +273,7 @@ static void read_directory(const char *path, const char *base, int baselen) exclude_stk = push_exclude_per_directory(base, baselen); - while ((de = readdir(dir)) != NULL) { + while ((de = readdir(fdir)) != NULL) { int len; if ((de->d_name[0] == '.') && @@ -288,6 +291,7 @@ static void read_directory(const char *path, const char *base, int baselen) switch (DTYPE(de)) { struct stat st; + int subdir, rewind_base; default: continue; case DT_UNKNOWN: @@ -301,22 +305,32 @@ static void read_directory(const char *path, const char *base, int baselen) case DT_DIR: memcpy(fullname + baselen + len, "/", 2); len++; + rewind_base = nr_dir; + subdir = read_directory(fullname, fullname, + baselen + len); if (show_other_directories && - !dir_exists(fullname, baselen + len)) + (subdir || !hide_empty_directories) && + !dir_exists(fullname, baselen + len)) { + // Rewind the read subdirectory + while (nr_dir > rewind_base) + free(dir[--nr_dir]); break; - read_directory(fullname, fullname, - baselen + len); + } + contents += subdir; continue; case DT_REG: case DT_LNK: break; } add_name(fullname, baselen + len); + contents++; } - closedir(dir); + closedir(fdir); pop_exclude_per_directory(exclude_stk); } + + return contents; } static int cmp_name(const void *p1, const void *p2) @@ -489,7 +503,8 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) printf("%s%06o %s %d\t", tag, ntohl(ce->ce_mode), - sha1_to_hex(ce->sha1), + abbrev ? find_unique_abbrev(ce->sha1,abbrev) + : sha1_to_hex(ce->sha1), ce_stage(ce)); write_name_quoted("", 0, ce->name + offset, line_terminator, stdout); @@ -630,7 +645,8 @@ static void verify_pathspec(void) static const char ls_files_usage[] = "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " "[ --ignored ] [--exclude=] [--exclude-from=] " - "[ --exclude-per-directory= ] [--full-name] [--] []*"; + "[ --exclude-per-directory= ] [--full-name] [--abbrev] " + "[--] []*"; int main(int argc, const char **argv) { @@ -696,6 +712,10 @@ int main(int argc, const char **argv) show_other_directories = 1; continue; } + if (!strcmp(arg, "--no-empty-directory")) { + hide_empty_directories = 1; + continue; + } if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { /* There's no point in showing unmerged unless * you also show the stage information. @@ -737,6 +757,18 @@ int main(int argc, const char **argv) error_unmatch = 1; continue; } + if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = strtoul(arg+9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (abbrev > 40) + abbrev = 40; + continue; + } + if (!strcmp(arg, "--abbrev")) { + abbrev = DEFAULT_ABBREV; + continue; + } if (*arg == '-') usage(ls_files_usage); break; diff --git a/ls-tree.c b/ls-tree.c index d005643ee0..26258c3cf9 100644 --- a/ls-tree.c +++ b/ls-tree.c @@ -13,13 +13,14 @@ static int line_termination = '\n'; #define LS_TREE_ONLY 2 #define LS_SHOW_TREES 4 #define LS_NAME_ONLY 8 +static int abbrev = 0; static int ls_options = 0; const char **pathspec; static int chomp_prefix = 0; static const char *prefix; static const char ls_tree_usage[] = - "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [path...]"; + "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=]] [path...]"; static int show_recursive(const char *base, int baselen, const char *pathname) { @@ -73,7 +74,9 @@ static int show_tree(unsigned char *sha1, const char *base, int baselen, return 0; if (!(ls_options & LS_NAME_ONLY)) - printf("%06o %s %s\t", mode, type, sha1_to_hex(sha1)); + printf("%06o %s %s\t", mode, type, + abbrev ? find_unique_abbrev(sha1,abbrev) + : sha1_to_hex(sha1)); write_name_quoted(base + chomp_prefix, baselen - chomp_prefix, pathname, line_termination, stdout); @@ -87,6 +90,7 @@ int main(int argc, const char **argv) struct tree *tree; prefix = setup_git_directory(); + git_config(git_default_config); if (prefix && *prefix) chomp_prefix = strlen(prefix); while (1 < argc && argv[1][0] == '-') { @@ -113,6 +117,18 @@ int main(int argc, const char **argv) chomp_prefix = 0; break; } + if (!strncmp(argv[1]+2, "abbrev=",7)) { + abbrev = strtoul(argv[1]+9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (abbrev > 40) + abbrev = 40; + break; + } + if (!strcmp(argv[1]+2, "abbrev")) { + abbrev = DEFAULT_ABBREV; + break; + } /* otherwise fallthru */ default: usage(ls_tree_usage); diff --git a/merge-base.c b/merge-base.c index e73fca7453..07f5ab4d1c 100644 --- a/merge-base.c +++ b/merge-base.c @@ -237,6 +237,7 @@ int main(int argc, char **argv) unsigned char rev1key[20], rev2key[20]; setup_git_directory(); + git_config(git_default_config); while (1 < argc && argv[1][0] == '-') { char *arg = argv[1]; diff --git a/name-rev.c b/name-rev.c index 0c3f547622..bad8a53777 100644 --- a/name-rev.c +++ b/name-rev.c @@ -127,6 +127,7 @@ int main(int argc, char **argv) int as_is = 0, all = 0, transform_stdin = 0; setup_git_directory(); + git_config(git_default_config); if (argc < 2) usage(name_rev_usage); diff --git a/read-tree.c b/read-tree.c index 1c3b09beff..eaff444196 100644 --- a/read-tree.c +++ b/read-tree.c @@ -717,6 +717,7 @@ int main(int argc, char **argv) merge_fn_t fn = NULL; setup_git_directory(); + git_config(git_default_config); newfd = hold_index_file_for_update(&cache_file, get_index_file()); if (newfd < 0) diff --git a/rev-list.c b/rev-list.c index 812d237f47..441c437855 100644 --- a/rev-list.c +++ b/rev-list.c @@ -40,13 +40,18 @@ static int bisect_list = 0; static int verbose_header = 0; static int abbrev = DEFAULT_ABBREV; static int show_parents = 0; +static int show_timestamp = 0; static int hdr_termination = 0; static const char *commit_prefix = ""; static enum cmit_fmt commit_format = CMIT_FMT_RAW; static void show_commit(struct commit *commit) { - printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1)); + if (show_timestamp) + printf("%lu ", commit->date); + if (commit_prefix[0]) + fputs(commit_prefix, stdout); + fputs(sha1_to_hex(commit->object.sha1), stdout); if (show_parents) { struct commit_list *parents = commit->parents; while (parents) { @@ -335,6 +340,10 @@ int main(int argc, const char **argv) show_parents = 1; continue; } + if (!strcmp(arg, "--timestamp")) { + show_timestamp = 1; + continue; + } if (!strcmp(arg, "--bisect")) { bisect_list = 1; continue; diff --git a/rev-parse.c b/rev-parse.c index f90e999e60..f176c56f7f 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -166,13 +166,17 @@ int main(int argc, char **argv) unsigned char sha1[20]; const char *prefix = setup_git_directory(); + git_config(git_default_config); + for (i = 1; i < argc; i++) { struct stat st; char *arg = argv[i]; char *dotdot; - + if (as_is) { - show_file(arg); + if (show_file(arg) && as_is < 2) + if (lstat(arg, &st) < 0) + die("'%s': %s", arg, strerror(errno)); continue; } if (!strcmp(arg,"-n")) { @@ -192,7 +196,7 @@ int main(int argc, char **argv) if (*arg == '-') { if (!strcmp(arg, "--")) { - as_is = 1; + as_is = 2; /* Pass on the "--" if we show anything but files.. */ if (filter & (DO_FLAGS | DO_REVS)) show_file(arg); diff --git a/revision.c b/revision.c index 12cd0529a5..d67718c75b 100644 --- a/revision.c +++ b/revision.c @@ -649,7 +649,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch /* If we didn't have a "--", all filenames must exist */ for (j = i; j < argc; j++) { if (lstat(argv[j], &st) < 0) - die("'%s': %s", arg, strerror(errno)); + die("'%s': %s", argv[j], strerror(errno)); } revs->prune_data = get_pathspec(revs->prefix, argv + i); break; diff --git a/send-pack.c b/send-pack.c index c8ffc8d537..409f188503 100644 --- a/send-pack.c +++ b/send-pack.c @@ -362,6 +362,8 @@ int main(int argc, char **argv) pid_t pid; setup_git_directory(); + git_config(git_default_config); + argv++; for (i = 1; i < argc; i++, argv++) { char *arg = *argv; diff --git a/sha1_name.c b/sha1_name.c index d67de18ba5..4f92e12a8d 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -235,14 +235,21 @@ static int ambiguous_path(const char *path, int len) static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { - static const char *prefix[] = { - "", - "refs", - "refs/tags", - "refs/heads", + static const char *fmt[] = { + "%.*s", + "refs/%.*s", + "refs/tags/%.*s", + "refs/heads/%.*s", + "refs/remotes/%.*s", + "refs/remotes/%.*s/HEAD", NULL }; const char **p; + const char *warning = "warning: refname '%.*s' is ambiguous.\n"; + char *pathname; + int already_found = 0; + unsigned char *this_result; + unsigned char sha1_from_ref[20]; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; @@ -251,11 +258,21 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (ambiguous_path(str, len)) return -1; - for (p = prefix; *p; p++) { - char *pathname = git_path("%s/%.*s", *p, len, str); - if (!read_ref(pathname, sha1)) - return 0; + for (p = fmt; *p; p++) { + this_result = already_found ? sha1_from_ref : sha1; + pathname = git_path(*p, len, str); + if (!read_ref(pathname, this_result)) { + if (warn_ambiguous_refs) { + if (already_found) + fprintf(stderr, warning, len, str); + already_found++; + } + else + return 0; + } } + if (already_found) + return 0; return -1; } diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index adc5e937de..278eb66701 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -34,6 +34,9 @@ test_expect_success \ mkdir a/bin && cp /bin/sh a/bin && ln -s a a/l1 && + (p=long_path_to_a_file && cd a && + for depth in 1 2 3 4 5; do mkdir $p && cd $p; done && + echo text >file_with_long_path) && (cd a && find .) | sort >a.lst' test_expect_success \ diff --git a/tar-tree.c b/tar-tree.c index e478e13e28..8d9e31c206 100644 --- a/tar-tree.c +++ b/tar-tree.c @@ -1,37 +1,23 @@ /* - * Copyright (c) 2005 Rene Scharfe + * Copyright (c) 2005, 2006 Rene Scharfe */ #include #include "cache.h" #include "diff.h" #include "commit.h" +#include "strbuf.h" +#include "tar.h" #define RECORDSIZE (512) #define BLOCKSIZE (RECORDSIZE * 20) -#define TYPEFLAG_AUTO '\0' -#define TYPEFLAG_REG '0' -#define TYPEFLAG_LNK '2' -#define TYPEFLAG_DIR '5' -#define TYPEFLAG_GLOBAL_HEADER 'g' -#define TYPEFLAG_EXT_HEADER 'x' - -#define EXT_HEADER_PATH 1 -#define EXT_HEADER_LINKPATH 2 - static const char tar_tree_usage[] = "git-tar-tree [basedir]"; static char block[BLOCKSIZE]; static unsigned long offset; -static const char *basedir; static time_t archive_time; -struct path_prefix { - struct path_prefix *prev; - const char *name; -}; - /* tries hard to write, either succeeds or dies in the attempt */ static void reliable_write(void *buf, unsigned long size) { @@ -119,230 +105,170 @@ static void write_blocked(void *buf, unsigned long size) write_if_needed(); } -static void append_string(char **p, const char *s) -{ - unsigned int len = strlen(s); - memcpy(*p, s, len); - *p += len; -} - -static void append_char(char **p, char c) -{ - **p = c; - *p += 1; -} - -static void append_path_prefix(char **buffer, struct path_prefix *prefix) +static void strbuf_append_string(struct strbuf *sb, const char *s) { - if (!prefix) - return; - append_path_prefix(buffer, prefix->prev); - append_string(buffer, prefix->name); - append_char(buffer, '/'); -} - -static unsigned int path_prefix_len(struct path_prefix *prefix) -{ - if (!prefix) - return 0; - return path_prefix_len(prefix->prev) + strlen(prefix->name) + 1; -} - -static void append_path(char **p, int is_dir, const char *basepath, - struct path_prefix *prefix, const char *path) -{ - if (basepath) { - append_string(p, basepath); - append_char(p, '/'); + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; } - append_path_prefix(p, prefix); - append_string(p, path); - if (is_dir) - append_char(p, '/'); + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; } -static unsigned int path_len(int is_dir, const char *basepath, - struct path_prefix *prefix, const char *path) -{ - unsigned int len = 0; - if (basepath) - len += strlen(basepath) + 1; - len += path_prefix_len(prefix) + strlen(path); - if (is_dir) - len++; - return len; -} - -static void append_extended_header_prefix(char **p, unsigned int size, - const char *keyword) +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) { - int len = sprintf(*p, "%u %s=", size, keyword); - *p += len; -} + char *p; + int len, total, tmp; -static unsigned int extended_header_len(const char *keyword, - unsigned int valuelen) -{ /* "%u %s=%s\n" */ - unsigned int len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - if (len > 9) - len++; - if (len > 99) + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) len++; - return len; -} -static void append_extended_header(char **p, const char *keyword, - const char *value, unsigned int len) -{ - unsigned int size = extended_header_len(keyword, len); - append_extended_header_prefix(p, size, keyword); - memcpy(*p, value, len); - *p += len; - append_char(p, '\n'); -} + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } -static void write_header(const unsigned char *, char, const char *, struct path_prefix *, - const char *, unsigned int, void *, unsigned long); + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} -/* stores a pax extended header directly in the block buffer */ -static void write_extended_header(const char *headerfilename, int is_dir, - unsigned int flags, const char *basepath, - struct path_prefix *prefix, - const char *path, unsigned int namelen, - void *content, unsigned int contentsize) +static unsigned int ustar_header_chksum(const struct ustar_header *header) { - char *buffer, *p; - unsigned int pathlen, size, linkpathlen = 0; - - size = pathlen = extended_header_len("path", namelen); - if (flags & EXT_HEADER_LINKPATH) { - linkpathlen = extended_header_len("linkpath", contentsize); - size += linkpathlen; - } - write_header(NULL, TYPEFLAG_EXT_HEADER, NULL, NULL, headerfilename, - 0100600, NULL, size); - - buffer = p = malloc(size); - if (!buffer) - die("git-tar-tree: %s", strerror(errno)); - append_extended_header_prefix(&p, pathlen, "path"); - append_path(&p, is_dir, basepath, prefix, path); - append_char(&p, '\n'); - if (flags & EXT_HEADER_LINKPATH) - append_extended_header(&p, "linkpath", content, contentsize); - write_blocked(buffer, size); - free(buffer); + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; } -static void write_global_extended_header(const unsigned char *sha1) +static int get_path_prefix(const struct strbuf *path, int maxlen) { - char *p; - unsigned int size; - - size = extended_header_len("comment", 40); - write_header(NULL, TYPEFLAG_GLOBAL_HEADER, NULL, NULL, - "pax_global_header", 0100600, NULL, size); - - p = get_record(); - append_extended_header(&p, "comment", sha1_to_hex(sha1), 40); - write_if_needed(); + int i = path->len; + if (i > maxlen) + i = maxlen; + while (i > 0 && path->buf[i] != '/') + i--; + return i; } -/* stores a ustar header directly in the block buffer */ -static void write_header(const unsigned char *sha1, char typeflag, const char *basepath, - struct path_prefix *prefix, const char *path, - unsigned int mode, void *buffer, unsigned long size) +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) { - unsigned int namelen; - char *header = NULL; - unsigned int checksum = 0; - int i; - unsigned int ext_header = 0; - - if (typeflag == TYPEFLAG_AUTO) { - if (S_ISDIR(mode)) - typeflag = TYPEFLAG_DIR; - else if (S_ISLNK(mode)) - typeflag = TYPEFLAG_LNK; - else - typeflag = TYPEFLAG_REG; - } - - namelen = path_len(S_ISDIR(mode), basepath, prefix, path); - if (namelen > 100) - ext_header |= EXT_HEADER_PATH; - if (typeflag == TYPEFLAG_LNK && size > 100) - ext_header |= EXT_HEADER_LINKPATH; - - /* the extended header must be written before the normal one */ - if (ext_header) { - char headerfilename[51]; - sprintf(headerfilename, "%s.paxheader", sha1_to_hex(sha1)); - write_extended_header(headerfilename, S_ISDIR(mode), - ext_header, basepath, prefix, path, - namelen, buffer, size); - } - - header = get_record(); - - if (ext_header) { - sprintf(header, "%s.data", sha1_to_hex(sha1)); + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); } else { - char *p = header; - append_path(&p, S_ISDIR(mode), basepath, prefix, path); + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode |= 0777; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode |= (mode & 0100) ? 0777 : 0666; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); } - if (typeflag == TYPEFLAG_LNK) { - if (ext_header & EXT_HEADER_LINKPATH) { - sprintf(&header[157], "see %s.paxheader", + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", sha1_to_hex(sha1)); - } else { - if (buffer) - strncpy(&header[157], buffer, size); - } + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); } - if (S_ISDIR(mode)) - mode |= 0777; - else if (S_ISREG(mode)) - mode |= (mode & 0100) ? 0777 : 0666; - else if (S_ISLNK(mode)) - mode |= 0777; - sprintf(&header[100], "%07o", mode & 07777); + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); /* XXX: should we provide more meaningful info here? */ - sprintf(&header[108], "%07o", 0); /* uid */ - sprintf(&header[116], "%07o", 0); /* gid */ - strncpy(&header[265], "git", 31); /* uname */ - strncpy(&header[297], "git", 31); /* gname */ - - if (S_ISDIR(mode) || S_ISLNK(mode)) - size = 0; - sprintf(&header[124], "%011lo", size); - sprintf(&header[136], "%011lo", archive_time); + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strncpy(header.uname, "git", 31); + strncpy(header.gname, "git", 31); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); - header[156] = typeflag; + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); - memcpy(&header[257], "ustar", 6); - memcpy(&header[263], "00", 2); + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - sprintf(&header[329], "%07o", 0); /* devmajor */ - sprintf(&header[337], "%07o", 0); /* devminor */ - - memset(&header[148], ' ', 8); - for (i = 0; i < RECORDSIZE; i++) - checksum += header[i]; - sprintf(&header[148], "%07o", checksum & 0x1fffff); + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} - write_if_needed(); +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); } -static void traverse_tree(struct tree_desc *tree, - struct path_prefix *prefix) +static void traverse_tree(struct tree_desc *tree, struct strbuf *path) { - struct path_prefix this_prefix; - this_prefix.prev = prefix; + int pathlen = path->len; while (tree->size) { const char *name; @@ -358,16 +284,19 @@ static void traverse_tree(struct tree_desc *tree, eltbuf = read_sha1_file(sha1, elttype, &eltsize); if (!eltbuf) die("cannot read %s", sha1_to_hex(sha1)); - write_header(sha1, TYPEFLAG_AUTO, basedir, - prefix, name, mode, eltbuf, eltsize); + + path->len = pathlen; + strbuf_append_string(path, name); + if (S_ISDIR(mode)) + strbuf_append_string(path, "/"); + + write_entry(sha1, path, mode, eltbuf, eltsize); + if (S_ISDIR(mode)) { struct tree_desc subtree; subtree.buf = eltbuf; subtree.size = eltsize; - this_prefix.name = name; - traverse_tree(&subtree, &this_prefix); - } else if (!S_ISLNK(mode)) { - write_blocked(eltbuf, eltsize); + traverse_tree(&subtree, path); } free(eltbuf); } @@ -375,15 +304,22 @@ static void traverse_tree(struct tree_desc *tree, int main(int argc, char **argv) { - unsigned char sha1[20]; + unsigned char sha1[20], tree_sha1[20]; struct commit *commit; struct tree_desc tree; + struct strbuf current_path; + + current_path.buf = xmalloc(PATH_MAX); + current_path.alloc = PATH_MAX; + current_path.len = current_path.eof = 0; setup_git_directory(); + git_config(git_default_config); switch (argc) { case 3: - basedir = argv[2]; + strbuf_append_string(¤t_path, argv[2]); + strbuf_append_string(¤t_path, "/"); /* FALLTHROUGH */ case 2: if (get_sha1(argv[1], sha1) < 0) @@ -397,17 +333,19 @@ int main(int argc, char **argv) if (commit) { write_global_extended_header(commit->object.sha1); archive_time = commit->date; - } - tree.buf = read_object_with_reference(sha1, "tree", &tree.size, NULL); + } else + archive_time = time(NULL); + + tree.buf = read_object_with_reference(sha1, "tree", &tree.size, + tree_sha1); if (!tree.buf) die("not a reference to a tag, commit or tree object: %s", sha1_to_hex(sha1)); - if (!archive_time) - archive_time = time(NULL); - if (basedir) - write_header((unsigned char *)"0", TYPEFLAG_DIR, NULL, NULL, - basedir, 040777, NULL, 0); - traverse_tree(&tree, NULL); + + if (current_path.len > 0) + write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); + traverse_tree(&tree, ¤t_path); write_trailer(); + free(current_path.buf); return 0; } diff --git a/tar.h b/tar.h new file mode 100644 index 0000000000..3467705e9b --- /dev/null +++ b/tar.h @@ -0,0 +1,25 @@ +#define TYPEFLAG_AUTO '\0' +#define TYPEFLAG_REG '0' +#define TYPEFLAG_LNK '2' +#define TYPEFLAG_DIR '5' +#define TYPEFLAG_GLOBAL_HEADER 'g' +#define TYPEFLAG_EXT_HEADER 'x' + +struct ustar_header { + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag[1]; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ +}; diff --git a/unpack-file.c b/unpack-file.c index 07303f8bb3..3accb974dd 100644 --- a/unpack-file.c +++ b/unpack-file.c @@ -30,6 +30,7 @@ int main(int argc, char **argv) usage("git-unpack-file "); setup_git_directory(); + git_config(git_default_config); puts(create_temp_file(sha1)); return 0; diff --git a/update-ref.c b/update-ref.c index e6fbddbab6..ba4bf5153e 100644 --- a/update-ref.c +++ b/update-ref.c @@ -25,6 +25,7 @@ int main(int argc, char **argv) int fd, written; setup_git_directory(); + git_config(git_default_config); if (argc < 3 || argc > 4) usage(git_update_ref_usage);