diff options
-rw-r--r-- | Documentation/git-checkout-index.txt | 20 | ||||
-rw-r--r-- | Documentation/git-cvsserver.txt | 24 | ||||
-rw-r--r-- | Documentation/git-svnimport.txt | 30 | ||||
-rw-r--r-- | Makefile | 17 | ||||
-rw-r--r-- | apply.c | 158 | ||||
-rw-r--r-- | blame.c | 442 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rw-r--r-- | checkout-index.c | 41 | ||||
-rw-r--r-- | combine-diff.c | 14 | ||||
-rw-r--r-- | contrib/git-svn/git-svn.txt | 11 | ||||
-rwxr-xr-x | contrib/gitview/gitview | 91 | ||||
-rw-r--r-- | count-delta.c | 77 | ||||
-rw-r--r-- | diff-delta.c | 291 | ||||
-rw-r--r-- | diff.c | 138 | ||||
-rw-r--r-- | diffcore-break.c | 25 | ||||
-rw-r--r-- | diffcore-delta.c | 43 | ||||
-rw-r--r-- | diffcore-rename.c | 28 | ||||
-rw-r--r-- | diffcore.h | 8 | ||||
-rw-r--r-- | environment.c | 1 | ||||
-rwxr-xr-x | git-am.sh | 13 | ||||
-rwxr-xr-x | git-cvsserver.perl | 44 | ||||
-rwxr-xr-x | git-format-patch.sh | 7 | ||||
-rwxr-xr-x | git-svnimport.perl | 112 | ||||
-rw-r--r-- | refs.c | 9 |
24 files changed, 1295 insertions, 351 deletions
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index 2a1e526c6a..b0b65889ac 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -10,7 +10,9 @@ SYNOPSIS -------- [verse] 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] - [--stage=<number>] [--] <file>... + [--stage=<number>] + [-z] [--stdin] + [--] [<file>]\* DESCRIPTION ----------- @@ -45,6 +47,15 @@ OPTIONS Instead of checking out unmerged entries, copy out the files from named stage. <number> must be between 1 and 3. +--stdin:: + Instead of taking list of paths from the command line, + read list of paths from the standard input. Paths are + separated by LF (i.e. one path per line) by default. + +-z:: + Only meaningful with `--stdin`; paths are separated with + NUL character instead of LF. + --:: Do not interpret any more arguments as options. @@ -64,7 +75,12 @@ $ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f -- which will force all existing `*.h` files to be replaced with their cached copies. If an empty command line implied "all", then this would -force-refresh everything in the index, which was not the point. +force-refresh everything in the index, which was not the point. But +since git-checkout-index accepts --stdin it would be faster to use: + +---------------- +$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin +---------------- The `--` is just a good idea when you know the rest will be filenames; it will prevent problems with a filename of, for example, `-a`. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 88f07ff15d..19c9c51cff 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -54,6 +54,30 @@ INSTALLATION of branches in git). $ cvs co -d mylocaldir master +Eclipse CVS Client Notes +------------------------ + +To get a checkout with the Eclipse CVS client: + +1. Create a new project from CVS checkout, giving it repository and module +2. Context Menu->Team->Share Project... +3. Enter the repository and module information again and click Finish +4. The Synchronize view appears. Untick "launch commit wizard" to avoid +committing the .project file, and select HEAD as the tag to synchronize to. +Update all incoming changes. + +Note that most versions of Eclipse ignore CVS_SERVER (which you can set in +the Preferences->Team->CVS->ExtConnection pane), so you may have to +rename, alias or symlink git-cvsserver to 'cvs' on the server. + +Clients known to work +--------------------- + +CVS 1.12.9 on Debian +CVS 1.11.17 on MacOSX (from Fink package) +Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) +TortoiseCVS + Operations supported -------------------- diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index 5c543d5d1b..a1588138ea 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -10,10 +10,11 @@ git-svnimport - Import a SVN repository into git SYNOPSIS -------- 'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ] - [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev] - [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] - [ -s start_chg ] [ -m ] [ -M regex ] - <SVN_repository_URL> [ <path> ] + [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev] + [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] + [ -s start_chg ] [ -m ] [ -r ] [ -M regex ] + [ -I <ignorefile_name> ] [ -A <author_file> ] + <SVN_repository_URL> [ <path> ] DESCRIPTION @@ -65,6 +66,27 @@ When importing incrementally, you might need to edit the .git/svn2git file. Prepend 'rX: ' to commit messages, where X is the imported subversion revision. +-I <ignorefile_name>:: + Import the svn:ignore directory property to files with this + name in each directory. (The Subversion and GIT ignore + syntaxes are similar enough that using the Subversion patterns + directly with "-I .gitignore" will almost always just work.) + +-A <author_file>:: + Read a file with lines on the form + + username = User's Full Name <email@addr.es> + + and use "User's Full Name <email@addr.es>" as the GIT + author and committer for Subversion commits made by + "username". If encountering a commit made by a user not in the + list, abort. + + For convenience, this data is saved to $GIT_DIR/svn-authors + each time the -A option is provided, and read from that same + file each time git-svnimport is run with an existing GIT + repository without -A. + -m:: Attempt to detect merges based on the commit message. This option will enable default regexes that try to capture the name source @@ -165,7 +165,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ - git-describe$X git-merge-tree$X + git-describe$X git-merge-tree$X git-blame$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -196,7 +196,8 @@ LIB_H = \ DIFF_OBJS = \ diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \ - diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o + diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ + diffcore-delta.o LIB_OBJS = \ blob.o commit.o connect.o count-delta.o csum-file.o \ @@ -223,11 +224,15 @@ ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease ## fink - ALL_CFLAGS += -I/sw/include - ALL_LDFLAGS += -L/sw/lib + ifeq ($(shell test -d /sw/lib && echo y),y) + ALL_CFLAGS += -I/sw/include + ALL_LDFLAGS += -L/sw/lib + endif ## darwinports - ALL_CFLAGS += -I/opt/local/include - ALL_LDFLAGS += -L/opt/local/lib + ifeq ($(shell test -d /opt/local/lib && echo y),y) + ALL_CFLAGS += -I/opt/local/include + ALL_LDFLAGS += -L/opt/local/lib + endif endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -34,6 +34,56 @@ static int line_termination = '\n'; static const char apply_usage[] = "git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>..."; +static enum whitespace_eol { + nowarn_whitespace, + warn_on_whitespace, + error_on_whitespace, + strip_whitespace, +} new_whitespace = warn_on_whitespace; +static int whitespace_error = 0; +static int squelch_whitespace_errors = 5; +static int applied_after_stripping = 0; +static const char *patch_input_file = NULL; + +static void parse_whitespace_option(const char *option) +{ + if (!option) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "warn")) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "nowarn")) { + new_whitespace = nowarn_whitespace; + return; + } + if (!strcmp(option, "error")) { + new_whitespace = error_on_whitespace; + return; + } + if (!strcmp(option, "error-all")) { + new_whitespace = error_on_whitespace; + squelch_whitespace_errors = 0; + return; + } + if (!strcmp(option, "strip")) { + new_whitespace = strip_whitespace; + return; + } + die("unrecognized whitespace option '%s'", option); +} + +static void set_default_whitespace_mode(const char *whitespace_option) +{ + if (!whitespace_option && !apply_default_whitespace) { + new_whitespace = (apply + ? warn_on_whitespace + : nowarn_whitespace); + } +} + /* * For "diff-stat" like behaviour, we keep track of the biggest change * we've seen, and the longest filename. That allows us to do simple @@ -815,6 +865,25 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s oldlines--; break; case '+': + /* + * We know len is at least two, since we have a '+' and + * we checked that the last character was a '\n' above. + * That is, an addition of an empty line would check + * the '+' here. Sneaky... + */ + if ((new_whitespace != nowarn_whitespace) && + isspace(line[len-2])) { + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < + whitespace_error) + ; + else { + fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", + patch_input_file, + linenr, len-2, line+1); + } + } added++; newlines--; break; @@ -1092,6 +1161,28 @@ struct buffer_desc { unsigned long alloc; }; +static int apply_line(char *output, const char *patch, int plen) +{ + /* plen is number of bytes to be copied from patch, + * starting at patch+1 (patch[0] is '+'). Typically + * patch[plen] is '\n'. + */ + int add_nl_to_tail = 0; + if ((new_whitespace == strip_whitespace) && + 1 < plen && isspace(patch[plen-1])) { + if (patch[plen] == '\n') + add_nl_to_tail = 1; + plen--; + while (0 < plen && isspace(patch[plen])) + plen--; + applied_after_stripping++; + } + memcpy(output, patch + 1, plen); + if (add_nl_to_tail) + output[plen++] = '\n'; + return plen; +} + static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) { char *buf = desc->buffer; @@ -1127,10 +1218,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) break; /* Fall-through for ' ' */ case '+': - if (*patch != '+' || !no_add) { - memcpy(new + newsize, patch + 1, plen); - newsize += plen; - } + if (*patch != '+' || !no_add) + newsize += apply_line(new + newsize, patch, + plen); break; case '@': case '\\': /* Ignore it, we already handled it */ @@ -1699,7 +1789,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd) +static int apply_patch(int fd, const char *filename) { int newfd; unsigned long offset, size; @@ -1707,6 +1797,7 @@ static int apply_patch(int fd) struct patch *list = NULL, **listp = &list; int skipped_patch = 0; + patch_input_file = filename; if (!buffer) return -1; offset = 0; @@ -1733,6 +1824,9 @@ static int apply_patch(int fd) } newfd = -1; + if (whitespace_error && (new_whitespace == error_on_whitespace)) + apply = 0; + write_index = check_index && apply; if (write_index) newfd = hold_index_file_for_update(&cache_file, get_index_file()); @@ -1769,17 +1863,28 @@ static int apply_patch(int fd) return 0; } +static int git_apply_config(const char *var, const char *value) +{ + if (!strcmp(var, "apply.whitespace")) { + apply_default_whitespace = strdup(value); + return 0; + } + return git_default_config(var, value); +} + + int main(int argc, char **argv) { int i; int read_stdin = 1; + const char *whitespace_option = NULL; for (i = 1; i < argc; i++) { const char *arg = argv[i]; int fd; if (!strcmp(arg, "-")) { - apply_patch(0); + apply_patch(0, "<stdin>"); read_stdin = 0; continue; } @@ -1839,11 +1944,18 @@ int main(int argc, char **argv) line_termination = 0; continue; } + if (!strncmp(arg, "--whitespace=", 13)) { + whitespace_option = arg + 13; + parse_whitespace_option(arg + 13); + continue; + } if (check_index && prefix_length < 0) { prefix = setup_git_directory(); prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_default_config); + git_config(git_apply_config); + if (!whitespace_option && apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); } if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); @@ -1852,10 +1964,38 @@ int main(int argc, char **argv) if (fd < 0) usage(apply_usage); read_stdin = 0; - apply_patch(fd); + set_default_whitespace_mode(whitespace_option); + apply_patch(fd, arg); close(fd); } + set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0); + apply_patch(0, "<stdin>"); + if (whitespace_error) { + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) { + int squelched = + whitespace_error - squelch_whitespace_errors; + fprintf(stderr, "warning: squelched %d whitespace error%s\n", + squelched, + squelched == 1 ? "" : "s"); + } + if (new_whitespace == error_on_whitespace) + die("%d line%s add%s trailing whitespaces.", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + if (applied_after_stripping) + fprintf(stderr, "warning: %d line%s applied after" + " stripping trailing whitespaces.\n", + applied_after_stripping, + applied_after_stripping == 1 ? "" : "s"); + else if (whitespace_error) + fprintf(stderr, "warning: %d line%s add%s trailing" + " whitespaces.\n", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + } return 0; } diff --git a/blame.c b/blame.c new file mode 100644 index 0000000000..dbce7e2c55 --- /dev/null +++ b/blame.c @@ -0,0 +1,442 @@ +#include <assert.h> + +#include "cache.h" +#include "refs.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "diff.h" + +#define DEBUG 0 + +struct commit** blame_lines; +int num_blame_lines; + +struct util_info +{ + int* line_map; + int num_lines; + unsigned char sha1[20]; /* blob sha, not commit! */ + char* buf; + unsigned long size; +// const char* path; +}; + +struct chunk +{ + int off1, len1; // --- + int off2, len2; // +++ +}; + +struct patch +{ + struct chunk* chunks; + int num; +}; + +static void get_blob(struct commit* commit); + +int num_get_patch = 0; +int num_commits = 0; + +struct patch* get_patch(struct commit* commit, struct commit* other) +{ + struct patch* ret = xmalloc(sizeof(struct patch)); + ret->chunks = NULL; + ret->num = 0; + + struct util_info* info_c = (struct util_info*) commit->object.util; + struct util_info* info_o = (struct util_info*) other->object.util; + + if(!memcmp(info_c->sha1, info_o->sha1, 20)) + return ret; + + get_blob(commit); + get_blob(other); + + FILE* fout = fopen("/tmp/git-blame-tmp1", "w"); + if(!fout) + die("fopen tmp1 failed: %s", strerror(errno)); + + if(fwrite(info_c->buf, info_c->size, 1, fout) != 1) + die("fwrite 1 failed: %s", strerror(errno)); + fclose(fout); + + fout = fopen("/tmp/git-blame-tmp2", "w"); + if(!fout) + die("fopen tmp2 failed: %s", strerror(errno)); + + if(fwrite(info_o->buf, info_o->size, 1, fout) != 1) + die("fwrite 2 failed: %s", strerror(errno)); + fclose(fout); + + FILE* fin = popen("diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2", "r"); + if(!fin) + die("popen failed: %s", strerror(errno)); + + char buf[1024]; + while(fgets(buf, sizeof(buf), fin)) { + if(buf[0] != '@' || buf[1] != '@') + continue; + + if(DEBUG) + printf("chunk line: %s", buf); + ret->num++; + ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num); + struct chunk* chunk = &ret->chunks[ret->num-1]; + + assert(!strncmp(buf, "@@ -", 4)); + + char* start = buf+4; + char* sp = index(start, ' '); + *sp = '\0'; + if(index(start, ',')) { + int ret = sscanf(start, "%d,%d", &chunk->off1, &chunk->len1); + assert(ret == 2); + } else { + int ret = sscanf(start, "%d", &chunk->off1); + assert(ret == 1); + chunk->len1 = 1; + } + *sp = ' '; + + start = sp+1; + sp = index(start, ' '); + *sp = '\0'; + if(index(start, ',')) { + int ret = sscanf(start, "%d,%d", &chunk->off2, &chunk->len2); + assert(ret == 2); + } else { + int ret = sscanf(start, "%d", &chunk->off2); + assert(ret == 1); + chunk->len2 = 1; + } + *sp = ' '; + + if(chunk->off1 > 0) + chunk->off1 -= 1; + if(chunk->off2 > 0) + chunk->off2 -= 1; + + assert(chunk->off1 >= 0); + assert(chunk->off2 >= 0); + } + fclose(fin); + + num_get_patch++; + return ret; +} + +void free_patch(struct patch* p) +{ + free(p->chunks); + free(p); +} + +static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen, + const char *pathname, unsigned mode, int stage); + + +static unsigned char blob_sha1[20]; +static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1) +{ + const char *pathspec[2]; + pathspec[0] = pathname; + pathspec[1] = NULL; + memset(blob_sha1, 0, sizeof(blob_sha1)); + read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); + + int i; + for(i = 0; i < 20; i++) { + if(blob_sha1[i] != 0) + break; + } + + if(i == 20) + return -1; + + memcpy(sha1, blob_sha1, 20); + return 0; +} + +static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen, + const char *pathname, unsigned mode, int stage) +{ +// printf("Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\n", +// sha1_to_hex(sha1), base, baselen, pathname, mode, stage); + + if(S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + memcpy(blob_sha1, sha1, 20); + return -1; +} + +static void get_blob(struct commit* commit) +{ + struct util_info* info = commit->object.util; + char type[20]; + + if(info->buf) + return; + + info->buf = read_sha1_file(info->sha1, type, &info->size); + assert(!strcmp(type, "blob")); +} + +void print_patch(struct patch* p) +{ + printf("Num chunks: %d\n", p->num); + int i; + for(i = 0; i < p->num; i++) { + printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2); + } +} + + +// p is a patch from commit to other. +void fill_line_map(struct commit* commit, struct commit* other, struct patch* p) +{ + int num_lines = ((struct util_info*) commit->object.util)->num_lines; + int* line_map = ((struct util_info*) commit->object.util)->line_map; + int num_lines2 = ((struct util_info*) other->object.util)->num_lines; + int* line_map2 = ((struct util_info*) other->object.util)->line_map; + int cur_chunk = 0; + int i1, i2; + + if(p->num && DEBUG) + print_patch(p); + + for(i1 = 0; i1 < num_lines; i1++) + line_map[i1] = -1; + + if(DEBUG) + printf("num lines 1: %d num lines 2: %d\n", num_lines, num_lines2); + + for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) { + if(DEBUG > 1) + printf("%d %d\n", i1, i2); + + if(i2 >= num_lines2) + break; + + line_map[i1] = line_map2[i2]; + + struct chunk* chunk = NULL; + if(cur_chunk < p->num) + chunk = &p->chunks[cur_chunk]; + + if(chunk && chunk->off1 == i1) { + i2 = chunk->off2; + + if(chunk->len1 > 0) + i1 += chunk->len1-1; + if(chunk->len2 > 0) + i2 += chunk->len2-1; + cur_chunk++; + } + } +} + +int map_line(struct commit* commit, int line) +{ + struct util_info* info = commit->object.util; + assert(line >= 0 && line < info->num_lines); + return info->line_map[line]; +} + +int fill_util_info(struct commit* commit, const char* path) +{ + if(commit->object.util) + return 0; + + struct util_info* util = xmalloc(sizeof(struct util_info)); + util->buf = NULL; + util->size = 0; + util->num_lines = -1; + util->line_map = NULL; + + commit->object.util = util; + + if(get_blob_sha1(commit->tree, path, util->sha1)) + return -1; + + return 0; +} + +void alloc_line_map(struct commit* commit) +{ + struct util_info* util = commit->object.util; + + if(util->line_map) + return; + + get_blob(commit); + + int i; + util->num_lines = 0; + for(i = 0; i < util->size; i++) { + if(util->buf[i] == '\n') + util->num_lines++; + } + util->line_map = xmalloc(sizeof(int)*util->num_lines); +} + +void copy_line_map(struct commit* dst, struct commit* src) +{ + struct util_info* u_dst = dst->object.util; + struct util_info* u_src = src->object.util; + + u_dst->line_map = u_src->line_map; + u_dst->num_lines = u_src->num_lines; + u_dst->buf = u_src->buf; + u_dst->size = u_src->size; +} + +void process_commits(struct commit_list* list, const char* path) +{ + int i; + + while(list) { + struct commit* commit = pop_commit(&list); + struct commit_list* parents; + struct util_info* info; + + info = commit->object.util; + num_commits++; + if(DEBUG) + printf("\nProcessing commit: %d %s\n", num_commits, sha1_to_hex(commit->object.sha1)); + for(parents = commit->parents; + parents != NULL; parents = parents->next) { + struct commit* parent = parents->item; + + if(parse_commit(parent) < 0) + die("parse_commit error"); + + if(DEBUG) + printf("parent: %s\n", sha1_to_hex(parent->object.sha1)); + + if(fill_util_info(parent, path)) + continue; + + // Temporarily assign everything to the parent. + int num_blame = 0; + for(i = 0; i < num_blame_lines; i++) { + if(blame_lines[i] == commit) { + num_blame++; + blame_lines[i] = parent; + } + } + + if(num_blame == 0) + continue; + + struct patch* patch = get_patch(parent, commit); + if(patch->num == 0) { + copy_line_map(parent, commit); + } else { + alloc_line_map(parent); + fill_line_map(parent, commit, patch); + } + + for(i = 0; i < patch->num; i++) { + int l; + for(l = 0; l < patch->chunks[i].len2; l++) { + int mapped_line = map_line(commit, patch->chunks[i].off2 + l); + if(mapped_line != -1 && blame_lines[mapped_line] == parent) + blame_lines[mapped_line] = commit; + } + } + free_patch(patch); + } + } +} + +#define SEEN 1 +struct commit_list* get_commit_list(struct commit* commit, const char* pathname) +{ + struct commit_list* ret = NULL; + struct commit_list* process = NULL; + unsigned char sha1[20]; + + commit_list_insert(commit, &process); + + while(process) { + struct commit* com = pop_commit(&process); + if(com->object.flags & SEEN) + continue; + + com->object.flags |= SEEN; + commit_list_insert(com, &ret); + struct commit_list* parents; + + parse_commit(com); + + for(parents = com->parents; + parents != NULL; parents = parents->next) { + struct commit* parent = parents->item; + + parse_commit(parent); + + if(!get_blob_sha1(parent->tree, pathname, sha1)) + commit_list_insert(parent, &process); + } + } + + return ret; +} + +int main(int argc, const char **argv) +{ + unsigned char sha1[20]; + struct commit *commit; + const char* filename; + int i; + + setup_git_directory(); + + if (argc != 3) + die("Usage: blame commit-ish file"); + + if (get_sha1(argv[1], sha1)) + die("get_sha1 failed"); + + commit = lookup_commit_reference(sha1); + + filename = argv[2]; + + struct commit_list* list = get_commit_list(commit, filename); + sort_in_topological_order(&list, 1); + + if(fill_util_info(commit, filename)) { + printf("%s not found in %s\n", filename, argv[1]); + return 0; + } + alloc_line_map(commit); + + struct util_info* util = commit->object.util; + num_blame_lines = util->num_lines; + blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines); + + + for(i = 0; i < num_blame_lines; i++) { + blame_lines[i] = commit; + + ((struct util_info*) commit->object.util)->line_map[i] = i; + } + + process_commits(list, filename); + + for(i = 0; i < num_blame_lines; i++) { + printf("%d %s\n", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1)); +// printf("%d %s\n", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6)); + } + + if(DEBUG) { + printf("num get patch: %d\n", num_get_patch); + printf("num commits: %d\n", num_commits); + } + + return 0; +} @@ -161,11 +161,13 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path); extern int commit_index_file(struct cache_file *); extern void rollback_index_file(struct cache_file *); +/* Environment bits from configuration mechanism */ extern int trust_executable_bit; extern int assume_unchanged; extern int only_use_symrefs; extern int diff_rename_limit_default; extern int shared_repository; +extern const char *apply_default_whitespace; #define GIT_REPO_VERSION 0 extern int repository_format_version; diff --git a/checkout-index.c b/checkout-index.c index 957b4a86b0..f54c606414 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -22,6 +22,10 @@ * * find . -name '*.h' -print0 | xargs -0 git-checkout-index -f -- * + * or: + * + * find . -name '*.h' -print0 | git-checkout-index -f -z --stdin + * * which will force all existing *.h files to be replaced with * their cached copies. If an empty command line implied "all", * then this would force-refresh everything in the cache, which @@ -33,6 +37,8 @@ * but get used to it in scripting!). */ #include "cache.h" +#include "strbuf.h" +#include "quote.h" static const char *prefix; static int prefix_length; @@ -114,6 +120,8 @@ int main(int argc, char **argv) int i; int newfd = -1; int all = 0; + int read_from_stdin = 0; + int line_termination = '\n'; prefix = setup_git_directory(); git_config(git_default_config); @@ -156,6 +164,17 @@ int main(int argc, char **argv) die("cannot open index.lock file."); continue; } + if (!strcmp(arg, "-z")) { + line_termination = 0; + continue; + } + if (!strcmp(arg, "--stdin")) { + if (i != argc - 1) + die("--stdin must be at the end"); + read_from_stdin = 1; + i++; /* do not consider arg as a file name */ + break; + } if (!strncmp(arg, "--prefix=", 9)) { state.base_dir = arg+9; state.base_dir_len = strlen(state.base_dir); @@ -191,9 +210,31 @@ int main(int argc, char **argv) if (all) die("git-checkout-index: don't mix '--all' and explicit filenames"); + if (read_from_stdin) + die("git-checkout-index: don't mix '--stdin' and explicit filenames"); checkout_file(prefix_path(prefix, prefix_length, arg)); } + if (read_from_stdin) { + struct strbuf buf; + if (all) + die("git-checkout-index: don't mix '--all' and '--stdin'"); + strbuf_init(&buf); + while (1) { + char *path_name; + read_line(&buf, stdin, line_termination); + if (buf.eof) + break; + if (line_termination && buf.buf[0] == '"') + path_name = unquote_c_style(buf.buf, NULL); + else + path_name = buf.buf; + checkout_file(prefix_path(prefix, prefix_length, path_name)); + if (path_name != buf.buf) + free(path_name); + } + } + if (all) checkout_all(); diff --git a/combine-diff.c b/combine-diff.c index d812600d11..a23894d869 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -621,7 +621,8 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, } static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, const char *header) + int dense, const char *header, + struct diff_options *opt) { unsigned long size, cnt, lno; char *result, *cp, *ep; @@ -631,6 +632,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, char ourtmp_buf[TMPPATHLEN]; char *ourtmp = ourtmp_buf; int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); + int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; /* Read the result of merge first */ if (!working_tree_file) { @@ -724,7 +726,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (header) { shown_header++; - puts(header); + printf("%s%c", header, opt->line_termination); } printf("diff --%s ", dense ? "cc" : "combined"); if (quote_c_style(elem->path, NULL, NULL, 0)) @@ -735,10 +737,10 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, printf("index "); for (i = 0; i < num_parent; i++) { abb = find_unique_abbrev(elem->parent[i].sha1, - DEFAULT_ABBREV); + abbrev); printf("%s%s", i ? "," : "", abb); } - abb = find_unique_abbrev(elem->sha1, DEFAULT_ABBREV); + abb = find_unique_abbrev(elem->sha1, abbrev); printf("..%s\n", abb); if (mode_differs) { @@ -797,7 +799,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha inter_name_termination = 0; if (header) - puts(header); + printf("%s%c", header, line_termination); for (i = 0; i < num_parent; i++) { if (p->parent[i].mode) @@ -862,7 +864,7 @@ int show_combined_diff(struct combine_diff_path *p, default: case DIFF_FORMAT_PATCH: - return show_patch_diff(p, num_parent, dense, header); + return show_patch_diff(p, num_parent, dense, header, opt); } } diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt index b4b7789dee..b29073997c 100644 --- a/contrib/git-svn/git-svn.txt +++ b/contrib/git-svn/git-svn.txt @@ -43,6 +43,11 @@ fetch:: Fetch unfetched revisions from the SVN_URL we are tracking. refs/heads/git-svn-HEAD will be updated to the latest revision. + Note: You should never attempt to modify the git-svn-HEAD branch + outside of git-svn. Instead, create a branch from git-svn-HEAD + and work on that branch. Use the 'commit' command (see below) + to write git commits back to git-svn-HEAD. + commit:: Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes @@ -154,7 +159,7 @@ Tracking and contributing to an Subversion managed-project: # Commit only the git commits you want to SVN:: git-svn commit <tree-ish> [<tree-ish_2> ...] # Commit all the git commits from my-branch that don't exist in SVN:: - git commit git-svn-HEAD..my-branch + git-svn commit git-svn-HEAD..my-branch # Something is committed to SVN, pull the latest into your branch:: git-svn fetch && git pull . git-svn-HEAD # Append svn:ignore settings to the default git exclude file: @@ -179,7 +184,9 @@ SVN repositories via one git repository. Simply set the GIT_SVN_ID environment variable to a name other other than "git-svn" (the default) and git-svn will ignore the contents of the $GIT_DIR/git-svn directory and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that -invocation. +invocation. The interface branch will be $GIT_SVN_ID-HEAD, instead of +git-svn-HEAD. Any $GIT_SVN_ID-HEAD branch should never be modified +by the user outside of git-svn commands. ADDITIONAL FETCH ARGUMENTS -------------------------- diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview index 4e3847d8bf..ea05cd4240 100755 --- a/contrib/gitview/gitview +++ b/contrib/gitview/gitview @@ -162,7 +162,7 @@ class CellRendererGraph(gtk.GenericCellRenderer): for item in names: names_len += len(item) - width = box_size * (cols + 1 ) + names_len + width = box_size * (cols + 1 ) + names_len height = box_size # FIXME I have no idea how to use cell_area properly @@ -239,20 +239,23 @@ class CellRendererGraph(gtk.GenericCellRenderer): box_size / 4, 0, 2 * math.pi) + self.set_colour(ctx, colour, 0.0, 0.5) + ctx.stroke_preserve() + + self.set_colour(ctx, colour, 0.5, 1.0) + ctx.fill_preserve() + if (len(names) != 0): name = " " for item in names: name = name + item + " " - ctx.select_font_face("Monospace") ctx.set_font_size(13) - ctx.text_path(name) - - self.set_colour(ctx, colour, 0.0, 0.5) - ctx.stroke_preserve() - - self.set_colour(ctx, colour, 0.5, 1.0) - ctx.fill() + if (flags & 1): + self.set_colour(ctx, colour, 0.5, 1.0) + else: + self.set_colour(ctx, colour, 0.0, 0.5) + ctx.show_text(name) class Commit: """ This represent a commit object obtained after parsing the git-rev-list @@ -261,11 +264,11 @@ class Commit: children_sha1 = {} def __init__(self, commit_lines): - self.message = "" + self.message = "" self.author = "" - self.date = "" - self.committer = "" - self.commit_date = "" + self.date = "" + self.committer = "" + self.commit_date = "" self.commit_sha1 = "" self.parent_sha1 = [ ] self.parse_commit(commit_lines) @@ -365,7 +368,7 @@ class DiffWindow: save_menu.connect("activate", self.save_menu_response, "save") save_menu.show() menu_bar.append(save_menu) - vbox.pack_start(menu_bar, False, False, 2) + vbox.pack_start(menu_bar, expand=False, fill=True) menu_bar.show() scrollwin = gtk.ScrolledWindow() @@ -391,7 +394,7 @@ class DiffWindow: sourceview.show() - def set_diff(self, commit_sha1, parent_sha1): + def set_diff(self, commit_sha1, parent_sha1, encoding): """Set the differences showed by this window. Compares the two trees and populates the window with the differences. @@ -401,7 +404,7 @@ class DiffWindow: return fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1) - self.buffer.set_text(fp.read()) + self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8')) fp.close() self.window.show() @@ -426,10 +429,11 @@ class GitView: def __init__(self, with_diff=0): self.with_diff = with_diff - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_border_width(0) self.window.set_title("Git repository browser") + self.get_encoding() self.get_bt_sha1() # Use three-quarters of the screen by default @@ -468,22 +472,20 @@ class GitView: self.bt_sha1[sha1].append(name) fp.close() + def get_encoding(self): + fp = os.popen("git repo-config --get i18n.commitencoding") + self.encoding=string.strip(fp.readline()) + fp.close() + if (self.encoding == ""): + self.encoding = "utf-8" + def construct(self): """Construct the window contents.""" + vbox = gtk.VBox() paned = gtk.VPaned() paned.pack1(self.construct_top(), resize=False, shrink=True) paned.pack2(self.construct_bottom(), resize=False, shrink=True) - self.window.add(paned) - paned.show() - - - def construct_top(self): - """Construct the top-half of the window.""" - vbox = gtk.VBox(spacing=6) - vbox.set_border_width(12) - vbox.show() - menu_bar = gtk.MenuBar() menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL) help_menu = gtk.MenuItem("Help") @@ -495,8 +497,20 @@ class GitView: help_menu.set_submenu(menu) help_menu.show() menu_bar.append(help_menu) - vbox.pack_start(menu_bar, False, False, 2) menu_bar.show() + vbox.pack_start(menu_bar, expand=False, fill=True) + vbox.pack_start(paned, expand=True, fill=True) + self.window.add(vbox) + paned.show() + vbox.show() + + + def construct_top(self): + """Construct the top-half of the window.""" + vbox = gtk.VBox(spacing=6) + vbox.set_border_width(12) + vbox.show() + scrollwin = gtk.ScrolledWindow() scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) @@ -512,6 +526,9 @@ class GitView: self.treeview.show() cell = CellRendererGraph() + # Set the default width to 265 + # This make sure that we have nice display with large tag names + cell.set_property("width", 265) column = gtk.TreeViewColumn() column.set_resizable(True) column.pack_start(cell, expand=True) @@ -683,7 +700,7 @@ class GitView: self.revid_label.set_text(revid_label) self.committer_label.set_text(committer) self.timestamp_label.set_text(timestamp) - self.message_buffer.set_text(message) + self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8')) for widget in self.parents_widgets: self.table.remove(widget) @@ -728,7 +745,7 @@ class GitView: button.set_relief(gtk.RELIEF_NONE) button.set_sensitive(True) button.connect("clicked", self._show_clicked_cb, - commit.commit_sha1, parent_id) + commit.commit_sha1, parent_id, self.encoding) hbox.pack_start(button, expand=False, fill=True) button.show() @@ -870,15 +887,15 @@ class GitView: # Reset nodepostion if (last_nodepos > 5): - last_nodepos = -1 + last_nodepos = -1 # Add the incomplete lines of the last cell in this try: colour = self.colours[commit.commit_sha1] except KeyError: self.colours[commit.commit_sha1] = last_colour+1 - last_colour = self.colours[commit.commit_sha1] - colour = self.colours[commit.commit_sha1] + last_colour = self.colours[commit.commit_sha1] + colour = self.colours[commit.commit_sha1] try: node_pos = self.nodepos[commit.commit_sha1] @@ -910,7 +927,7 @@ class GitView: self.colours[parent_id] = last_colour+1 last_colour = self.colours[parent_id] self.nodepos[parent_id] = last_nodepos+1 - last_nodepos = self.nodepos[parent_id] + last_nodepos = self.nodepos[parent_id] in_line.append((node_pos, self.nodepos[parent_id], self.colours[parent_id])) @@ -946,7 +963,7 @@ class GitView: try: next_commit = self.commits[index+1] if (next_commit.commit_sha1 == sha1 and pos != int(pos)): - # join the line back to the node point + # join the line back to the node point # This need to be done only if we modified it in_line.append((pos, pos-0.5, self.colours[sha1])) continue; @@ -967,10 +984,10 @@ class GitView: self.treeview.grab_focus() - def _show_clicked_cb(self, widget, commit_sha1, parent_sha1): + def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding): """Callback for when the show button for a parent is clicked.""" window = DiffWindow() - window.set_diff(commit_sha1, parent_sha1) + window.set_diff(commit_sha1, parent_sha1, encoding) self.treeview.grab_focus() if __name__ == "__main__": diff --git a/count-delta.c b/count-delta.c index 058a2aadb1..3ee3a0ccf1 100644 --- a/count-delta.c +++ b/count-delta.c @@ -3,11 +3,74 @@ * The delta-parsing part is almost straight copy of patch-delta.c * which is (C) 2005 Nicolas Pitre <nico@cam.org>. */ +#include "cache.h" +#include "delta.h" +#include "count-delta.h" #include <stdlib.h> #include <string.h> #include <limits.h> -#include "delta.h" -#include "count-delta.h" + +struct span { + struct span *next; + unsigned long ofs; + unsigned long end; +}; + +static void touch_range(struct span **span, + unsigned long ofs, unsigned long end) +{ + struct span *e = *span; + struct span *p = NULL; + + while (e && e->ofs <= ofs) { + again: + if (ofs < e->end) { + while (e->end < end) { + if (e->next && e->next->ofs <= end) { + e->end = e->next->ofs; + e = e->next; + } + else { + e->end = end; + return; + } + } + return; + } + p = e; + e = e->next; + } + if (e && e->ofs <= end) { + e->ofs = ofs; + goto again; + } + else { + e = xmalloc(sizeof(*e)); + e->ofs = ofs; + e->end = end; + if (p) { + e->next = p->next; + p->next = e; + } + else { + e->next = *span; + *span = e; + } + } +} + +static unsigned long count_range(struct span *s) +{ + struct span *t; + unsigned long sz = 0; + while (s) { + t = s; + sz += s->end - s->ofs; + s = s->next; + free(t); + } + return sz; +} /* * NOTE. We do not _interpret_ delta fully. As an approximation, we @@ -21,10 +84,11 @@ 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; + unsigned long added_literal; const unsigned char *data, *top; unsigned char cmd; unsigned long src_size, dst_size, out; + struct span *span = NULL; if (delta_size < DELTA_SIZE_MIN) return -1; @@ -35,7 +99,7 @@ int count_delta(void *delta_buf, unsigned long delta_size, src_size = get_delta_hdr_size(&data); dst_size = get_delta_hdr_size(&data); - added_literal = copied_from_source = out = 0; + added_literal = out = 0; while (data < top) { cmd = *data++; if (cmd & 0x80) { @@ -49,7 +113,7 @@ int count_delta(void *delta_buf, unsigned long delta_size, if (cmd & 0x40) cp_size |= (*data++ << 16); if (cp_size == 0) cp_size = 0x10000; - copied_from_source += cp_size; + touch_range(&span, cp_off, cp_off+cp_size); out += cp_size; } else { /* write literal into dst */ @@ -59,6 +123,8 @@ int count_delta(void *delta_buf, unsigned long delta_size, } } + *src_copied = count_range(span); + /* sanity check */ if (data != top || out != dst_size) return -1; @@ -66,7 +132,6 @@ int count_delta(void *delta_buf, unsigned long delta_size, /* 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/diff-delta.c b/diff-delta.c index c2f656ae39..0730b24df8 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -19,159 +19,128 @@ */ #include <stdlib.h> +#include <string.h> #include "delta.h" -#include "zlib.h" -/* block size: min = 16, max = 64k, power of 2 */ -#define BLK_SIZE 16 - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -#define GR_PRIME 0x9e370001 -#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b))) - -static unsigned int hashbits(unsigned int size) -{ - unsigned int val = 1, bits = 0; - while (val < size && bits < 32) { - val <<= 1; - bits++; - } - return bits ? bits: 1; -} - -typedef struct s_chanode { - struct s_chanode *next; - int icurr; -} chanode_t; - -typedef struct s_chastore { - int isize, nsize; - chanode_t *ancur; -} chastore_t; - -static void cha_init(chastore_t *cha, int isize, int icount) -{ - cha->isize = isize; - cha->nsize = icount * isize; - cha->ancur = NULL; -} - -static void *cha_alloc(chastore_t *cha) -{ - chanode_t *ancur; - void *data; - - ancur = cha->ancur; - if (!ancur || ancur->icurr == cha->nsize) { - ancur = malloc(sizeof(chanode_t) + cha->nsize); - if (!ancur) - return NULL; - ancur->icurr = 0; - ancur->next = cha->ancur; - cha->ancur = ancur; - } - - data = (void *)ancur + sizeof(chanode_t) + ancur->icurr; - ancur->icurr += cha->isize; - return data; -} - -static void cha_free(chastore_t *cha) -{ - chanode_t *cur = cha->ancur; - while (cur) { - chanode_t *tmp = cur; - cur = cur->next; - free(tmp); - } -} - -typedef struct s_bdrecord { - struct s_bdrecord *next; - unsigned int fp; +struct index { const unsigned char *ptr; -} bdrecord_t; - -typedef struct s_bdfile { - chastore_t cha; - unsigned int fphbits; - bdrecord_t **fphash; -} bdfile_t; + struct index *next; +}; -static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf) +static struct index ** delta_index(const unsigned char *buf, + unsigned long bufsize, + unsigned long trg_bufsize, + unsigned int *hash_shift) { - unsigned int fphbits; - int i, hsize; - const unsigned char *data, *top; - bdrecord_t *brec; - bdrecord_t **fphash; - - fphbits = hashbits(bufsize / BLK_SIZE + 1); - hsize = 1 << fphbits; - fphash = malloc(hsize * sizeof(bdrecord_t *)); - if (!fphash) - return -1; - for (i = 0; i < hsize; i++) - fphash[i] = NULL; - cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1); - - top = buf + bufsize; - data = buf + (bufsize / BLK_SIZE) * BLK_SIZE; - if (data == top) - data -= BLK_SIZE; - - for ( ; data >= buf; data -= BLK_SIZE) { - brec = cha_alloc(&bdf->cha); - if (!brec) { - cha_free(&bdf->cha); - free(fphash); - return -1; - } - brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data)); - brec->ptr = data; - i = HASH(brec->fp, fphbits); - brec->next = fphash[i]; - fphash[i] = brec; + unsigned long hsize; + unsigned int i, hshift, hlimit, *hash_count; + const unsigned char *data; + struct index *entry, **hash; + void *mem; + + /* determine index hash size */ + hsize = bufsize / 4; + for (i = 8; (1 << i) < hsize && i < 24; i += 2); + hsize = 1 << i; + hshift = (i - 8) / 2; + *hash_shift = hshift; + + /* allocate lookup index */ + mem = malloc(hsize * sizeof(*hash) + bufsize * sizeof(*entry)); + if (!mem) + return NULL; + hash = mem; + entry = mem + hsize * sizeof(*hash); + memset(hash, 0, hsize * sizeof(*hash)); + + /* allocate an array to count hash entries */ + hash_count = calloc(hsize, sizeof(*hash_count)); + if (!hash_count) { + free(hash); + return NULL; } - bdf->fphbits = fphbits; - bdf->fphash = fphash; - - return 0; -} + /* then populate the index */ + data = buf + bufsize - 2; + while (data > buf) { + entry->ptr = --data; + i = data[0] ^ ((data[1] ^ (data[2] << hshift)) << hshift); + entry->next = hash[i]; + hash[i] = entry++; + hash_count[i]++; + } + + /* + * Determine a limit on the number of entries in the same hash + * bucket. This guard us against patological data sets causing + * really bad hash distribution with most entries in the same hash + * bucket that would bring us to O(m*n) computing costs (m and n + * corresponding to reference and target buffer sizes). + * + * The more the target buffer is large, the more it is important to + * have small entry lists for each hash buckets. With such a limit + * the cost is bounded to something more like O(m+n). + */ + hlimit = (1 << 26) / trg_bufsize; + if (hlimit < 16) + hlimit = 16; + + /* + * Now make sure none of the hash buckets has more entries than + * we're willing to test. Otherwise we short-circuit the entry + * list uniformly to still preserve a good repartition across + * the reference buffer. + */ + for (i = 0; i < hsize; i++) { + if (hash_count[i] < hlimit) + continue; + entry = hash[i]; + do { + struct index *keep = entry; + int skip = hash_count[i] / hlimit / 2; + do { + entry = entry->next; + } while(--skip && entry); + keep->next = entry; + } while(entry); + } + free(hash_count); -static void delta_cleanup(bdfile_t *bdf) -{ - free(bdf->fphash); - cha_free(&bdf->cha); + return hash; } +/* provide the size of the copy opcode given the block offset and size */ #define COPYOP_SIZE(o, s) \ (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \ !!(s & 0xff) + !!(s & 0xff00) + 1) +/* the maximum size for any opcode */ +#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff) + void *diff_delta(void *from_buf, unsigned long from_size, void *to_buf, unsigned long to_size, unsigned long *delta_size, unsigned long max_size) { - int i, outpos, outsize, inscnt, csize, msize, moff; - unsigned int fp; - const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2; - unsigned char *out, *orig; - bdrecord_t *brec; - bdfile_t bdf; + unsigned int i, outpos, outsize, inscnt, hash_shift; + const unsigned char *ref_data, *ref_top, *data, *top; + unsigned char *out; + struct index *entry, **hash; - if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf)) + if (!from_size || !to_size) return NULL; - + hash = delta_index(from_buf, from_size, to_size, &hash_shift); + if (!hash) + return NULL; + outpos = 0; outsize = 8192; + if (max_size && outsize >= max_size) + outsize = max_size + MAX_OP_SIZE + 1; out = malloc(outsize); if (!out) { - delta_cleanup(&bdf); + free(hash); return NULL; } @@ -199,28 +168,28 @@ void *diff_delta(void *from_buf, unsigned long from_size, } inscnt = 0; - moff = 0; - while (data < top) { - msize = 0; - fp = adler32(0, data, MIN(top - data, BLK_SIZE)); - i = HASH(fp, bdf.fphbits); - for (brec = bdf.fphash[i]; brec; brec = brec->next) { - if (brec->fp == fp) { - csize = ref_top - brec->ptr; - if (csize > top - data) - csize = top - data; - for (ptr1 = brec->ptr, ptr2 = data; - csize && *ptr1 == *ptr2; - csize--, ptr1++, ptr2++); - csize = ptr1 - brec->ptr; - if (csize > msize) { - moff = brec->ptr - ref_data; - msize = csize; - if (msize >= 0x10000) { - msize = 0x10000; - break; - } + while (data < top) { + unsigned int moff = 0, msize = 0; + if (data + 3 <= top) { + i = data[0] ^ ((data[1] ^ (data[2] << hash_shift)) << hash_shift); + for (entry = hash[i]; entry; entry = entry->next) { + const unsigned char *ref = entry->ptr; + const unsigned char *src = data; + unsigned int ref_size = ref_top - ref; + if (ref_size > top - src) + ref_size = top - src; + if (ref_size > 0x10000) + ref_size = 0x10000; + if (ref_size <= msize) + break; + if (*ref != *src) + continue; + while (ref_size-- && *++src == *++ref); + if (msize < ref - entry->ptr) { + /* this is our best match so far */ + msize = ref - entry->ptr; + moff = entry->ptr - ref_data; } } } @@ -235,13 +204,15 @@ void *diff_delta(void *from_buf, unsigned long from_size, inscnt = 0; } } else { + unsigned char *op; + if (inscnt) { out[outpos - inscnt - 1] = inscnt; inscnt = 0; } data += msize; - orig = out + outpos++; + op = out + outpos++; i = 0x80; if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; } @@ -256,23 +227,21 @@ void *diff_delta(void *from_buf, unsigned long from_size, msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } - *orig = i; - } - - if (max_size && outpos > max_size) { - free(out); - delta_cleanup(&bdf); - return NULL; + *op = i; } - /* next time around the largest possible output is 1 + 4 + 3 */ - if (outpos > outsize - 8) { + if (outpos >= outsize - MAX_OP_SIZE) { void *tmp = out; outsize = outsize * 3 / 2; - out = realloc(out, outsize); + if (max_size && outsize >= max_size) + outsize = max_size + MAX_OP_SIZE + 1; + if (max_size && outpos > max_size) + out = NULL; + else + out = realloc(out, outsize); if (!out) { free(tmp); - delta_cleanup(&bdf); + free(hash); return NULL; } } @@ -281,7 +250,7 @@ void *diff_delta(void *from_buf, unsigned long from_size, if (inscnt) out[outpos - inscnt - 1] = inscnt; - delta_cleanup(&bdf); + free(hash); *delta_size = outpos; return out; } @@ -178,11 +178,12 @@ static void emit_rewrite_diff(const char *name_a, copy_file('+', temp[1].name); } -static void builtin_diff(const char *name_a, +static const char *builtin_diff(const char *name_a, const char *name_b, struct diff_tempfile *temp, const char *xfrm_msg, - int complete_rewrite) + int complete_rewrite, + const char **args) { int i, next_at, cmd_size; const char *const diff_cmd = "diff -L%s -L%s"; @@ -242,19 +243,24 @@ static void builtin_diff(const char *name_a, } if (xfrm_msg && xfrm_msg[0]) puts(xfrm_msg); + /* + * we do not run diff between different kind + * of objects. + */ if (strncmp(temp[0].mode, temp[1].mode, 3)) - /* we do not run diff between different kind - * of objects. - */ - exit(0); + return NULL; if (complete_rewrite) { - fflush(NULL); emit_rewrite_diff(name_a, name_b, temp); - exit(0); + return NULL; } } - fflush(NULL); - execlp("/bin/sh","sh", "-c", cmd, NULL); + + /* This is disgusting */ + *args++ = "sh"; + *args++ = "-c"; + *args++ = cmd; + *args = NULL; + return "/bin/sh"; } struct diff_filespec *alloc_filespec(const char *path) @@ -559,6 +565,40 @@ static void remove_tempfile_on_signal(int signo) raise(signo); } +static int spawn_prog(const char *pgm, const char **arg) +{ + pid_t pid; + int status; + + fflush(NULL); + pid = fork(); + if (pid < 0) + die("unable to fork"); + if (!pid) { + execvp(pgm, (char *const*) arg); + exit(255); + } + + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + return -1; + } + + /* Earlier we did not check the exit status because + * diff exits non-zero if files are different, and + * we are not interested in knowing that. It was a + * mistake which made it harder to quit a diff-* + * session that uses the git-apply-patch-script as + * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF + * should also exit non-zero only when it wants to + * abort the entire diff-* session. + */ + if (WIFEXITED(status) && !WEXITSTATUS(status)) + return 0; + return -1; +} + /* An external diff command takes: * * diff-cmd name infile1 infile1-sha1 infile1-mode \ @@ -573,9 +613,9 @@ static void run_external_diff(const char *pgm, const char *xfrm_msg, int complete_rewrite) { + const char *spawn_arg[10]; struct diff_tempfile *temp = diff_temp; - pid_t pid; - int status; + int retval; static int atexit_asked = 0; const char *othername; @@ -592,59 +632,41 @@ static void run_external_diff(const char *pgm, signal(SIGINT, remove_tempfile_on_signal); } - fflush(NULL); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - if (pgm) { - if (one && two) { - const char *exec_arg[10]; - const char **arg = &exec_arg[0]; - *arg++ = pgm; - *arg++ = name; - *arg++ = temp[0].name; - *arg++ = temp[0].hex; - *arg++ = temp[0].mode; - *arg++ = temp[1].name; - *arg++ = temp[1].hex; - *arg++ = temp[1].mode; - if (other) { - *arg++ = other; - *arg++ = xfrm_msg; - } - *arg = NULL; - execvp(pgm, (char *const*) exec_arg); + if (pgm) { + const char **arg = &spawn_arg[0]; + if (one && two) { + *arg++ = pgm; + *arg++ = name; + *arg++ = temp[0].name; + *arg++ = temp[0].hex; + *arg++ = temp[0].mode; + *arg++ = temp[1].name; + *arg++ = temp[1].hex; + *arg++ = temp[1].mode; + if (other) { + *arg++ = other; + *arg++ = xfrm_msg; } - else - execlp(pgm, pgm, name, NULL); + } else { + *arg++ = pgm; + *arg++ = name; } - /* - * otherwise we use the built-in one. - */ - if (one && two) - builtin_diff(name, othername, temp, xfrm_msg, - complete_rewrite); - else + *arg = NULL; + } else { + if (one && two) { + pgm = builtin_diff(name, othername, temp, xfrm_msg, complete_rewrite, spawn_arg); + } else printf("* Unmerged path %s\n", name); - exit(0); } - if (waitpid(pid, &status, 0) < 0 || - !WIFEXITED(status) || WEXITSTATUS(status)) { - /* Earlier we did not check the exit status because - * diff exits non-zero if files are different, and - * we are not interested in knowing that. It was a - * mistake which made it harder to quit a diff-* - * session that uses the git-apply-patch-script as - * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF - * should also exit non-zero only when it wants to - * abort the entire diff-* session. - */ - remove_tempfile(); + + retval = 0; + if (pgm) + retval = spawn_prog(pgm, spawn_arg); + remove_tempfile(); + if (retval) { fprintf(stderr, "external diff died, stopping at %s.\n", name); exit(1); } - remove_tempfile(); } static void diff_fill_sha1_info(struct diff_filespec *one) diff --git a/diffcore-break.c b/diffcore-break.c index c57513a4fa..0fc2b860be 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -4,8 +4,6 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" -#include "delta.h" -#include "count-delta.h" static int should_break(struct diff_filespec *src, struct diff_filespec *dst, @@ -47,7 +45,6 @@ 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. */ - void *delta; unsigned long delta_size, base_size, src_copied, literal_added; int to_break = 0; @@ -58,6 +55,10 @@ static int should_break(struct diff_filespec *src, if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) return 0; /* leave symlink rename alone */ + if (src->sha1_valid && dst->sha1_valid && + !memcmp(src->sha1, dst->sha1, 20)) + return 0; /* they are the same */ + if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; /* error but caught downstream */ @@ -65,19 +66,11 @@ static int should_break(struct diff_filespec *src, if (base_size < MINIMUM_BREAK_SIZE) return 0; /* we do not break too small filepair */ - delta = diff_delta(src->data, src->size, - dst->data, dst->size, - &delta_size, 0); - if (!delta) - return 0; /* error but caught downstream */ - - /* Estimate the edit size by interpreting delta. */ - if (count_delta(delta, delta_size, - &src_copied, &literal_added)) { - free(delta); - return 0; /* we cannot tell */ - } - free(delta); + if (diffcore_count_changes(src->data, src->size, + dst->data, dst->size, + 0, + &src_copied, &literal_added)) + return 0; /* Compute merge-score, which is "how much is removed * from the source material". The clean-up stage will diff --git a/diffcore-delta.c b/diffcore-delta.c new file mode 100644 index 0000000000..1e6a6911ec --- /dev/null +++ b/diffcore-delta.c @@ -0,0 +1,43 @@ +#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) +{ + 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; + } + free(delta); + return 0; +} + +int diffcore_count_changes(void *src, unsigned long src_size, + void *dst, unsigned long dst_size, + 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); +} diff --git a/diffcore-rename.c b/diffcore-rename.c index ffd126af0d..55cf1c37f3 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -4,8 +4,6 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" -#include "delta.h" -#include "count-delta.h" /* Table of rename/copy destinations */ @@ -135,7 +133,6 @@ static int estimate_similarity(struct diff_filespec *src, * match than anything else; the destination does not even * call into this function in that case. */ - void *delta; unsigned long delta_size, base_size, src_copied, literal_added; unsigned long delta_limit; int score; @@ -165,28 +162,13 @@ static int estimate_similarity(struct diff_filespec *src, if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; /* error but caught downstream */ - delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE; - delta = diff_delta(src->data, src->size, - dst->data, dst->size, - &delta_size, delta_limit); - if (!delta) - /* If delta_limit is exceeded, we have too much differences */ - return 0; - - /* A delta that has a lot of literal additions would have - * big delta_size no matter what else it does. - */ - if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) { - free(delta); - return 0; - } - /* Estimate the edit size by interpreting delta. */ - if (count_delta(delta, delta_size, &src_copied, &literal_added)) { - free(delta); + delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE; + if (diffcore_count_changes(src->data, src->size, + dst->data, dst->size, + delta_limit, + &src_copied, &literal_added)) return 0; - } - free(delta); /* Extent of damage */ if (src->size + literal_added < src_copied) diff --git a/diffcore.h b/diffcore.h index 12cd816591..7f9889daf3 100644 --- a/diffcore.h +++ b/diffcore.h @@ -18,7 +18,7 @@ #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_MERGE_SCORE 45000 /* maximum for break-merge to happen (75%)*/ #define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */ @@ -101,4 +101,10 @@ void diff_debug_queue(const char *, struct diff_queue_struct *); #define diff_debug_queue(a,b) do {} while(0) #endif +extern int diffcore_count_changes(void *src, unsigned long src_size, + void *dst, unsigned long dst_size, + unsigned long delta_limit, + unsigned long *src_copied, + unsigned long *literal_added); + #endif diff --git a/environment.c b/environment.c index 251e53ca09..16c08f0697 100644 --- a/environment.c +++ b/environment.c @@ -17,6 +17,7 @@ int only_use_symrefs = 0; int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = 0; +const char *apply_default_whitespace = NULL; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; @@ -100,7 +100,7 @@ fall_back_3way () { } prec=4 -dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= +dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= while case "$#" in 0) break;; esac do @@ -133,6 +133,9 @@ do --sk|--ski|--skip) skip=t; shift ;; + --whitespace=*) + ws=$1; shift ;; + --) shift; break ;; -*) @@ -171,10 +174,11 @@ else exit 1 } - # -b, -s, -u and -k flags are kept for the resuming session after - # a patch failure. + # -b, -s, -u, -k and --whitespace flags are kept for the + # resuming session after a patch failure. # -3 and -i can and must be given when resuming. echo "$binary" >"$dotest/binary" + echo " $ws" >"$dotest/whitespace" echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" @@ -202,6 +206,7 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi +ws=`cat "$dotest/whitespace"` if test "$(cat "$dotest/sign")" = t then SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e ' @@ -355,7 +360,7 @@ do case "$resolved" in '') - git-apply $binary --index "$dotest/patch" + git-apply $binary --index $ws "$dotest/patch" apply_status=$? ;; t) diff --git a/git-cvsserver.perl b/git-cvsserver.perl index d20d1a8c4b..3c588c9d64 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -53,6 +53,7 @@ my $methods = { 'Entry' => \&req_Entry, 'Modified' => \&req_Modified, 'Unchanged' => \&req_Unchanged, + 'Questionable' => \&req_Questionable, 'Argument' => \&req_Argument, 'Argumentx' => \&req_Argument, 'expand-modules' => \&req_expandmodules, @@ -63,6 +64,7 @@ my $methods = { 'ci' => \&req_ci, 'diff' => \&req_diff, 'log' => \&req_log, + 'rlog' => \&req_log, 'tag' => \&req_CATCHALL, 'status' => \&req_status, 'admin' => \&req_CATCHALL, @@ -459,6 +461,22 @@ sub req_Unchanged #$log->debug("req_Unchanged : $data"); } +# Questionable filename \n +# Response expected: no. Additional data: no. +# Tell the server to check whether filename should be ignored, +# and if not, next time the server sends responses, send (in +# a M response) `?' followed by the directory and filename. +# filename must not contain `/'; it needs to be a file in the +# directory named by the most recent Directory request. +sub req_Questionable +{ + my ( $cmd, $data ) = @_; + + $state->{entries}{$state->{directory}.$data}{questionable} = 1; + + #$log->debug("req_Questionable : $data"); +} + # Argument text \n # Response expected: no. Save argument for use in a subsequent command. # Arguments accumulate until an argument-using command is given, at which @@ -568,7 +586,7 @@ sub req_co # print some information to the client print "MT +updated\n"; - print "MT text U\n"; + print "MT text U \n"; if ( defined ( $git->{dir} ) and $git->{dir} ne "./" ) { print "MT fname $checkout_path/$git->{dir}$git->{name}\n"; @@ -579,9 +597,9 @@ sub req_co print "MT -updated\n"; # instruct client we're sending a file to put in this path - print "Created $checkout_path/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "\n"; + print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n"; - print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; + print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; # this is an "entries" line print "/$git->{name}/1.$git->{revision}///\n"; @@ -612,6 +630,26 @@ sub req_update argsplit("update"); + # + # It may just be a client exploring the available heads/modukles + # in that case, list them as top level directories and leave it + # at that. Eclipse uses this technique to offer you a list of + # projects (heads in this case) to checkout. + # + if ($state->{module} eq '') { + print "E cvs update: Updating .\n"; + opendir HEADS, $state->{CVSROOT} . '/refs/heads'; + while (my $head = readdir(HEADS)) { + if (-f $state->{CVSROOT} . '/refs/heads/' . $head) { + print "E cvs update: New directory `$head'\n"; + } + } + closedir HEADS; + print "ok\n"; + return 1; + } + + # Grab a handle to the SQLite db and do any necessary updates my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); diff --git a/git-format-patch.sh b/git-format-patch.sh index eb75de4601..2bd26395ec 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -174,7 +174,7 @@ titleScript=' process_one () { perl -w -e ' my ($keep_subject, $num, $signoff, $commsg) = @ARGV; -my ($signoff_pattern, $done_header, $done_subject, $signoff_seen, +my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen, $last_was_signoff); if ($signoff) { @@ -228,6 +228,11 @@ while (<FH>) { $done_subject = 1; next; } + unless ($done_separator) { + print "\n"; + $done_separator = 1; + next if (/^$/); + } $last_was_signoff = 0; if (/Signed-off-by:/i) { diff --git a/git-svnimport.perl b/git-svnimport.perl index ee2940f480..639aa41861 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -13,6 +13,7 @@ use strict; use warnings; use Getopt::Std; +use File::Copy; use File::Spec; use File::Temp qw(tempfile); use File::Path qw(mkpath); @@ -29,19 +30,21 @@ die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1"; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_r,$opt_s,$opt_l,$opt_d,$opt_D); +our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D); sub usage() { print STDERR <<END; Usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] - [-d|-D] [-i] [-u] [-r] [-s start_chg] [-m] [-M regex] [SVN_URL] + [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] + [-m] [-M regex] [-A author_file] [SVN_URL] END exit(1); } -getopts("b:C:dDhil:mM:o:rs:t:T:uv") or usage(); +getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; @@ -66,6 +69,25 @@ if ($opt_M) { push (@mergerx, qr/$opt_M/); } +# Absolutize filename now, since we will have chdir'ed by the time we +# get around to opening it. +$opt_A = File::Spec->rel2abs($opt_A) if $opt_A; + +our %users = (); +our $users_file = undef; +sub read_users($) { + $users_file = File::Spec->rel2abs(@_); + die "Cannot open $users_file\n" unless -f $users_file; + open(my $authors,$users_file); + while(<$authors>) { + chomp; + next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + (my $user,my $name,my $email) = ($1,$2,$3); + $users{$user} = [$name,$email]; + } + close($authors); +} + select(STDERR); $|=1; select(STDOUT); @@ -112,16 +134,40 @@ sub file { DIR => File::Spec->tmpdir(), UNLINK => 1); print "... $rev $path ...\n" if $opt_v; - my $pool = SVN::Pool->new(); - eval { $self->{'svn'}->get_file($path,$rev,$fh,$pool); }; - $pool->clear; + my (undef, $properties); + eval { (undef, $properties) + = $self->{'svn'}->get_file($path,$rev,$fh); }; if($@) { return undef if $@ =~ /Attempted to get checksum/; die $@; } + my $mode; + if (exists $properties->{'svn:executable'}) { + $mode = '0755'; + } else { + $mode = '0644'; + } close ($fh); - return $name; + return ($name, $mode); +} + +sub ignore { + my($self,$path,$rev) = @_; + + print "... $rev $path ...\n" if $opt_v; + my (undef,undef,$properties) + = $self->{'svn'}->get_dir($path,$rev,undef); + if (exists $properties->{'svn:ignore'}) { + my ($fh, $name) = tempfile('gitsvn.XXXXXX', + DIR => File::Spec->tmpdir(), + UNLINK => 1); + print $fh $properties->{'svn:ignore'}; + close($fh); + return $name; + } else { + return undef; + } } package main; @@ -263,6 +309,14 @@ EOM -d $git_dir or die "Could not create git subdir ($git_dir).\n"; +my $default_authors = "$git_dir/svn-authors"; +if ($opt_A) { + read_users($opt_A); + copy($opt_A,$default_authors) or die "Copy failed: $!"; +} else { + read_users($default_authors) if -f $default_authors; +} + open BRANCHES,">>", "$git_dir/svn2git"; sub node_kind($$$) { @@ -296,7 +350,7 @@ sub get_file($$$) { my $svnpath = revert_split_path($branch,$path); # now get it - my $name; + my ($name,$mode); if($opt_d) { my($req,$res); @@ -316,8 +370,9 @@ sub get_file($$$) { return undef if $res->code == 301; # directory? die $res->status_line." at $url\n"; } + $mode = '0644'; # can't obtain mode via direct http request? } else { - $name = $svn->file("$svnpath",$rev); + ($name,$mode) = $svn->file("$svnpath",$rev); return undef unless defined $name; } @@ -331,10 +386,37 @@ sub get_file($$$) { chomp $sha; close $F; unlink $name; - my $mode = "0644"; # SV does not seem to store any file modes return [$mode, $sha, $path]; } +sub get_ignore($$$$$) { + my($new,$old,$rev,$branch,$path) = @_; + + return unless $opt_I; + my $svnpath = revert_split_path($branch,$path); + my $name = $svn->ignore("$svnpath",$rev); + if ($path eq '/') { + $path = $opt_I; + } else { + $path = File::Spec->catfile($path,$opt_I); + } + if (defined $name) { + my $pid = open(my $F, '-|'); + die $! unless defined $pid; + if (!$pid) { + exec("git-hash-object", "-w", $name) + or die "Cannot create object: $!\n"; + } + my $sha = <$F>; + chomp $sha; + close $F; + unlink $name; + push(@$new,['0644',$sha,$path]); + } else { + push(@$old,$path); + } +} + sub split_path($$) { my($rev,$path) = @_; my $branch; @@ -431,6 +513,10 @@ sub commit { if (not defined $author) { $author_name = $author_email = "unknown"; + } elsif (defined $users_file) { + die "User $author is not listed in $users_file\n" + unless exists $users{$author}; + ($author_name,$author_email) = @{$users{$author}}; } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { ($author_name, $author_email) = ($1, $2); } else { @@ -540,6 +626,9 @@ sub commit { my $opath = $action->[3]; print STDERR "$revision: $branch: could not fetch '$opath'\n"; } + } elsif ($node_kind eq $SVN::Node::dir) { + get_ignore(\@new, \@old, $revision, + $branch,$path); } } elsif ($action->[0] eq "D") { push(@old,$path); @@ -548,6 +637,9 @@ sub commit { if ($node_kind eq $SVN::Node::file) { my $f = get_file($revision,$branch,$path); push(@new,$f) if $f; + } elsif ($node_kind eq $SVN::Node::dir) { + get_ignore(\@new, \@old, $revision, + $branch,$path); } } else { die "$revision: unknown action '".$action->[0]."' for $path\n"; @@ -151,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u break; continue; } - if (read_ref(git_path("%s", path), sha1) < 0) + if (read_ref(git_path("%s", path), sha1) < 0) { + fprintf(stderr, "%s points nowhere!", path); continue; - if (!has_sha1_file(sha1)) + } + if (!has_sha1_file(sha1)) { + fprintf(stderr, "%s does not point to a valid " + "commit object!", path); continue; + } retval = fn(path, sha1); if (retval) break; |