diff options
61 files changed, 1855 insertions, 579 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index a04c5adf8e..16bdd55233 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -91,6 +91,12 @@ core.warnAmbiguousRefs:: If true, git will warn you if the ref name you passed it is ambiguous and might match multiple refs in the .git/refs/ tree. True by default. +core.compression: + An integer -1..9, indicating the compression level for objects that + are not in a pack file. -1 is the zlib and git default. 0 means no + compression, and 1..9 are various speed/size tradeoffs, 9 being + slowest. + alias.*:: Command aliases for the gitlink:git[1] command wrapper - e.g. after defining "alias.last = cat-file commit HEAD", the invocation diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 0fe66f2d0c..517a86b238 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -15,9 +15,9 @@ SYNOPSIS DESCRIPTION ----------- Updates the index file for given paths, or all modified files if -'-a' is specified, and makes a commit object. The command -VISUAL and EDITOR environment variables to edit the commit log -message. +'-a' is specified, and makes a commit object. The command specified +by either the VISUAL or EDITOR environment variables are used to edit +the commit log message. Several environment variable are used during commits. They are documented in gitlink:git-commit-tree[1]. @@ -131,7 +131,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \ + git-shortlog.perl git-rerere.perl \ git-annotate.perl git-cvsserver.perl \ git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \ git-send-email.perl @@ -173,7 +173,8 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \ git-read-tree$X git-commit-tree$X git-write-tree$X \ git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X + git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ + git-fmt-merge-msg$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -229,7 +230,7 @@ BUILTIN_OBJS = \ builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ - builtin-update-ref.o + builtin-update-ref.o builtin-fmt-merge-msg.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -587,11 +588,11 @@ git-ssh-push$X: rsh.o git-imap-send$X: imap-send.o $(LIB_FILE) http.o http-fetch.o http-push.o: http.h -git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE) +git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -git-http-push$X: revision.o http.o http-push.o $(LIB_FILE) +git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c new file mode 100644 index 0000000000..65274824d3 --- /dev/null +++ b/builtin-fmt-merge-msg.c @@ -0,0 +1,355 @@ +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "tag.h" + +static const char *fmt_merge_msg_usage = + "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]"; + +static int merge_summary = 0; + +static int fmt_merge_msg_config(const char *key, const char *value) +{ + if (!strcmp("merge.summary", key)) + merge_summary = git_config_bool(key, value); + return 0; +} + +struct list { + char **list; + void **payload; + unsigned nr, alloc; +}; + +static void append_to_list(struct list *list, char *value, void *payload) +{ + if (list->nr == list->alloc) { + list->alloc += 32; + list->list = realloc(list->list, sizeof(char *) * list->alloc); + list->payload = realloc(list->payload, + sizeof(char *) * list->alloc); + } + list->payload[list->nr] = payload; + list->list[list->nr++] = value; +} + +static int find_in_list(struct list *list, char *value) +{ + int i; + + for (i = 0; i < list->nr; i++) + if (!strcmp(list->list[i], value)) + return i; + + return -1; +} + +static void free_list(struct list *list) +{ + int i; + + if (list->alloc == 0) + return; + + for (i = 0; i < list->nr; i++) { + free(list->list[i]); + if (list->payload[i]) + free(list->payload[i]); + } + free(list->list); + free(list->payload); + list->nr = list->alloc = 0; +} + +struct src_data { + struct list branch, tag, r_branch, generic; + int head_status; +}; + +static struct list srcs = { NULL, NULL, 0, 0}; +static struct list origins = { NULL, NULL, 0, 0}; + +static int handle_line(char *line) +{ + int i, len = strlen(line); + unsigned char *sha1; + char *src, *origin; + struct src_data *src_data; + + if (len < 43 || line[40] != '\t') + return 1; + + if (!strncmp(line + 41, "not-for-merge", 13)) + return 0; + + if (line[41] != '\t') + return 2; + + line[40] = 0; + sha1 = xmalloc(20); + i = get_sha1(line, sha1); + line[40] = '\t'; + if (i) + return 3; + + if (line[len - 1] == '\n') + line[len - 1] = 0; + line += 42; + + src = strstr(line, " of "); + if (src) { + *src = 0; + src += 4; + } else + src = "HEAD"; + + i = find_in_list(&srcs, src); + if (i < 0) { + i = srcs.nr; + append_to_list(&srcs, strdup(src), + xcalloc(1, sizeof(struct src_data))); + } + src_data = srcs.payload[i]; + + if (!strncmp(line, "branch ", 7)) { + origin = strdup(line + 7); + append_to_list(&src_data->branch, origin, NULL); + src_data->head_status |= 2; + } else if (!strncmp(line, "tag ", 4)) { + origin = line; + append_to_list(&src_data->tag, strdup(origin + 4), NULL); + src_data->head_status |= 2; + } else if (!strncmp(line, "remote branch ", 14)) { + origin = strdup(line + 14); + append_to_list(&src_data->r_branch, origin, NULL); + src_data->head_status |= 2; + } else if (!strcmp(line, "HEAD")) { + origin = strdup(src); + src_data->head_status |= 1; + } else { + origin = strdup(src); + append_to_list(&src_data->generic, strdup(line), NULL); + src_data->head_status |= 2; + } + + if (!strcmp(".", src) || !strcmp(src, origin)) { + int len = strlen(origin); + if (origin[0] == '\'' && origin[len - 1] == '\'') { + char *new_origin = malloc(len - 1); + memcpy(new_origin, origin + 1, len - 2); + new_origin[len - 1] = 0; + origin = new_origin; + } else + origin = strdup(origin); + } else { + char *new_origin = malloc(strlen(origin) + strlen(src) + 5); + sprintf(new_origin, "%s of %s", origin, src); + origin = new_origin; + } + append_to_list(&origins, origin, sha1); + return 0; +} + +static void print_joined(const char *singular, const char *plural, + struct list *list) +{ + if (list->nr == 0) + return; + if (list->nr == 1) { + printf("%s%s", singular, list->list[0]); + } else { + int i; + printf("%s", plural); + for (i = 0; i < list->nr - 1; i++) + printf("%s%s", i > 0 ? ", " : "", list->list[i]); + printf(" and %s", list->list[list->nr - 1]); + } +} + +static void shortlog(const char *name, unsigned char *sha1, + struct commit *head, struct rev_info *rev, int limit) +{ + int i, count = 0; + struct commit *commit; + struct object *branch; + struct list subjects = { NULL, NULL, 0, 0 }; + int flags = UNINTERESTING | TREECHANGE | SEEN | SHOWN | ADDED; + + branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); + if (!branch || branch->type != TYPE_COMMIT) + return; + + setup_revisions(0, NULL, rev, NULL); + rev->ignore_merges = 1; + add_pending_object(rev, branch, name); + add_pending_object(rev, &head->object, "^HEAD"); + head->object.flags |= UNINTERESTING; + prepare_revision_walk(rev); + while ((commit = get_revision(rev)) != NULL) { + char *oneline, *bol, *eol; + + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + + count++; + if (subjects.nr > limit) + continue; + + bol = strstr(commit->buffer, "\n\n"); + if (!bol) { + append_to_list(&subjects, strdup(sha1_to_hex( + commit->object.sha1)), + NULL); + continue; + } + + bol += 2; + eol = strchr(bol, '\n'); + + if (eol) { + int len = eol - bol; + oneline = malloc(len + 1); + memcpy(oneline, bol, len); + oneline[len] = 0; + } else + oneline = strdup(bol); + append_to_list(&subjects, oneline, NULL); + } + + if (count > limit) + printf("\n* %s: (%d commits)\n", name, count); + else + printf("\n* %s:\n", name); + + for (i = 0; i < subjects.nr; i++) + if (i >= limit) + printf(" ...\n"); + else + printf(" %s\n", subjects.list[i]); + + clear_commit_marks((struct commit *)branch, flags); + clear_commit_marks(head, flags); + free_commit_list(rev->commits); + rev->commits = NULL; + rev->pending.nr = 0; + + free_list(&subjects); +} + +int cmd_fmt_merge_msg(int argc, char **argv, char **envp) +{ + int limit = 20, i = 0; + char line[1024]; + FILE *in = stdin; + const char *sep = ""; + unsigned char head_sha1[20]; + const char *head, *current_branch; + + git_config(fmt_merge_msg_config); + + while (argc > 1) { + if (!strcmp(argv[1], "--summary")) + merge_summary = 1; + else if (!strcmp(argv[1], "--no-summary")) + merge_summary = 0; + else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) { + if (argc < 2) + die ("Which file?"); + if (!strcmp(argv[2], "-")) + in = stdin; + else { + fclose(in); + in = fopen(argv[2], "r"); + } + argc--; argv++; + } else + break; + argc--; argv++; + } + + if (argc > 1) + usage(fmt_merge_msg_usage); + + /* get current branch */ + head = strdup(git_path("HEAD")); + current_branch = resolve_ref(head, head_sha1, 1); + current_branch += strlen(head) - 4; + free((char *)head); + if (!strncmp(current_branch, "refs/heads/", 11)) + current_branch += 11; + + while (fgets(line, sizeof(line), in)) { + i++; + if (line[0] == 0) + continue; + if (handle_line(line)) + die ("Error in line %d: %s", i, line); + } + + printf("Merge "); + for (i = 0; i < srcs.nr; i++) { + struct src_data *src_data = srcs.payload[i]; + const char *subsep = ""; + + printf(sep); + sep = "; "; + + if (src_data->head_status == 1) { + printf(srcs.list[i]); + continue; + } + if (src_data->head_status == 3) { + subsep = ", "; + printf("HEAD"); + } + if (src_data->branch.nr) { + printf(subsep); + subsep = ", "; + print_joined("branch ", "branches ", &src_data->branch); + } + if (src_data->r_branch.nr) { + printf(subsep); + subsep = ", "; + print_joined("remote branch ", "remote branches ", + &src_data->r_branch); + } + if (src_data->tag.nr) { + printf(subsep); + subsep = ", "; + print_joined("tag ", "tags ", &src_data->tag); + } + if (src_data->generic.nr) { + printf(subsep); + print_joined("commit ", "commits ", &src_data->generic); + } + if (strcmp(".", srcs.list[i])) + printf(" of %s", srcs.list[i]); + } + + if (!strcmp("master", current_branch)) + putchar('\n'); + else + printf(" into %s\n", current_branch); + + if (merge_summary) { + struct commit *head; + struct rev_info rev; + + head = lookup_commit(head_sha1); + init_revisions(&rev); + rev.commit_format = CMIT_FMT_ONELINE; + rev.ignore_merges = 1; + rev.limited = 1; + + for (i = 0; i < origins.nr; i++) + shortlog(origins.list[i], origins.payload[i], + head, &rev, limit); + } + + /* No cleanup yet; is standalone anyway */ + + return 0; +} + diff --git a/builtin-grep.c b/builtin-grep.c index 2e7986cece..6973c66704 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -446,7 +446,7 @@ static int exec_grep(int argc, const char **argv) static int external_grep(struct grep_opt *opt, const char **paths, int cached) { - int i, nr, argc, hit, len; + int i, nr, argc, hit, len, status; const char *argv[MAXARGS+1]; char randarg[ARGBUF]; char *argptr = randarg; @@ -536,12 +536,17 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) argv[argc++] = name; if (argc < MAXARGS) continue; - hit += exec_grep(argc, argv); + status = exec_grep(argc, argv); + if (0 < status) + hit = 1; argc = nr; } - if (argc > nr) - hit += exec_grep(argc, argv); - return 0; + if (argc > nr) { + status = exec_grep(argc, argv); + if (0 < status) + hit = 1; + } + return hit; } static int grep_cache(struct grep_opt *opt, const char **paths, int cached) @@ -652,6 +657,13 @@ static int grep_object(struct grep_opt *opt, const char **paths, static const char builtin_grep_usage[] = "git-grep <option>* <rev>* [-e] <pattern> [<path>...]"; +static const char emsg_invalid_context_len[] = +"%s: invalid context length argument"; +static const char emsg_missing_context_len[] = +"missing context length argument"; +static const char emsg_missing_argument[] = +"option requires an argument -%s"; + int cmd_grep(int argc, const char **argv, char **envp) { int hit = 0; @@ -759,7 +771,7 @@ int cmd_grep(int argc, const char **argv, char **envp) case 'A': case 'B': case 'C': if (!arg[2]) { if (argc <= 1) - usage(builtin_grep_usage); + die(emsg_missing_context_len); scan = *++argv; argc--; } @@ -771,7 +783,7 @@ int cmd_grep(int argc, const char **argv, char **envp) break; } if (sscanf(scan, "%u", &num) != 1) - usage(builtin_grep_usage); + die(emsg_invalid_context_len, scan); switch (arg[1]) { case 'A': opt.post_context = num; @@ -790,7 +802,7 @@ int cmd_grep(int argc, const char **argv, char **envp) int lno = 0; char buf[1024]; if (argc <= 1) - usage(builtin_grep_usage); + die(emsg_missing_argument, arg); patterns = fopen(argv[1], "r"); if (!patterns) die("'%s': %s", argv[1], strerror(errno)); @@ -815,10 +827,14 @@ int cmd_grep(int argc, const char **argv, char **envp) argc--; continue; } - usage(builtin_grep_usage); + die(emsg_missing_argument, arg); } - if (!strcmp("--", arg)) + if (!strcmp("--", arg)) { + /* later processing wants to have this at argv[1] */ + argv--; + argc++; break; + } if (*arg == '-') usage(builtin_grep_usage); diff --git a/builtin-log.c b/builtin-log.c index bcd4e5e161..864c6cd9ea 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -161,6 +161,65 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject) freopen(filename, "w", stdout); } +static int get_patch_id(struct commit *commit, struct diff_options *options, + unsigned char *sha1) +{ + diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1, + "", options); + diffcore_std(options); + return diff_flush_patch_id(options, sha1); +} + +static void get_patch_ids(struct rev_info *rev, struct diff_options *options) +{ + struct rev_info check_rev; + struct commit *commit; + struct object *o1, *o2; + unsigned flags1, flags2; + unsigned char sha1[20]; + + if (rev->pending.nr != 2) + die("Need exactly one range."); + + o1 = rev->pending.objects[0].item; + flags1 = o1->flags; + o2 = rev->pending.objects[1].item; + flags2 = o2->flags; + + if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) + die("Not a range."); + + diff_setup(options); + options->recursive = 1; + if (diff_setup_done(options) < 0) + die("diff_setup_done failed"); + + /* given a range a..b get all patch ids for b..a */ + init_revisions(&check_rev); + o1->flags ^= UNINTERESTING; + o2->flags ^= UNINTERESTING; + add_pending_object(&check_rev, o1, "o1"); + add_pending_object(&check_rev, o2, "o2"); + prepare_revision_walk(&check_rev); + + while ((commit = get_revision(&check_rev)) != NULL) { + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + + if (!get_patch_id(commit, options, sha1)) + created_object(sha1, xcalloc(1, sizeof(struct object))); + } + + /* reset for next revision walk */ + clear_commit_marks((struct commit *)o1, + SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks((struct commit *)o2, + SEEN | UNINTERESTING | SHOWN | ADDED); + o1->flags = flags1; + o2->flags = flags2; +} + int cmd_format_patch(int argc, const char **argv, char **envp) { struct commit *commit; @@ -171,6 +230,8 @@ int cmd_format_patch(int argc, const char **argv, char **envp) int numbered = 0; int start_number = -1; int keep_subject = 0; + int ignore_if_in_upstream = 0; + struct diff_options patch_id_opts; char *add_signoff = NULL; init_revisions(&rev); @@ -235,6 +296,8 @@ int cmd_format_patch(int argc, const char **argv, char **envp) rev.mime_boundary = git_version_string; else if (!strncmp(argv[i], "--attach=", 9)) rev.mime_boundary = argv[i] + 9; + else if (!strcmp(argv[i], "--ignore-if-in-upstream")) + ignore_if_in_upstream = 1; else argv[j++] = argv[i]; } @@ -265,14 +328,25 @@ int cmd_format_patch(int argc, const char **argv, char **envp) add_head(&rev); } + if (ignore_if_in_upstream) + get_patch_ids(&rev, &patch_id_opts); + if (!use_stdout) realstdout = fdopen(dup(1), "w"); prepare_revision_walk(&rev); while ((commit = get_revision(&rev)) != NULL) { + unsigned char sha1[20]; + /* ignore merges */ if (commit->parents && commit->parents->next) continue; + + if (ignore_if_in_upstream && + !get_patch_id(commit, &patch_id_opts, sha1) && + lookup_object(sha1)) + continue; + nr++; list = realloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 821642a7af..3e40747cf5 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -165,7 +165,7 @@ static int handle_subject(char *line) static int slurp_attr(const char *line, const char *name, char *attr) { - char *ends, *ap = strcasestr(line, name); + const char *ends, *ap = strcasestr(line, name); size_t sz; if (!ap) { diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index b27a6d382b..5f5ade45ae 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -329,7 +329,7 @@ int cmd_rev_parse(int argc, const char **argv, char **envp) dotdot = strstr(arg, ".."); if (dotdot) { unsigned char end[20]; - char *next = dotdot + 2; + const char *next = dotdot + 2; const char *this = arg; *dotdot = 0; if (!*next) @@ -49,6 +49,7 @@ extern int cmd_cat_file(int argc, const char **argv, char **envp); extern int cmd_rev_parse(int argc, const char **argv, char **envp); extern int cmd_update_index(int argc, const char **argv, char **envp); extern int cmd_update_ref(int argc, const char **argv, char **envp); +extern int cmd_fmt_merge_msg(int argc, const char **argv, char **envp); extern int cmd_write_tree(int argc, const char **argv, char **envp); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); @@ -183,6 +183,7 @@ extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; extern const char *apply_default_whitespace; +extern int zlib_compression_level; #define GIT_REPO_VERSION 0 extern int repository_format_version; @@ -321,13 +322,17 @@ struct ref { char name[FLEX_ARRAY]; /* more */ }; +#define REF_NORMAL (1u << 0) +#define REF_HEADS (1u << 1) +#define REF_TAGS (1u << 2) + extern int git_connect(int fd[2], char *url, const char *prog); extern int finish_connect(pid_t pid); extern int path_match(const char *path, int nr, char **match); extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int nr_refspec, char **refspec, int all); extern int get_ack(int fd, unsigned char *result_sha1); -extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny); +extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags); extern int server_supports(const char *feature); extern struct packed_git *parse_pack_index(unsigned char *sha1); diff --git a/combine-diff.c b/combine-diff.c index 2fd0ced395..caffb926ea 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -205,7 +205,8 @@ static void consume_line(void *state_, char *line, unsigned long len) } static void combine_diff(const unsigned char *parent, mmfile_t *result_file, - struct sline *sline, int cnt, int n, int num_parent) + struct sline *sline, unsigned int cnt, int n, + int num_parent) { unsigned int p_lno, lno; unsigned long nmask = (1UL << n); @@ -293,7 +294,7 @@ static unsigned long find_next(struct sline *sline, unsigned long mark, unsigned long i, unsigned long cnt, - int uninteresting) + int look_for_uninteresting) { /* We have examined up to i-1 and are about to look at i. * Find next interesting or uninteresting line. Here, @@ -303,7 +304,7 @@ static unsigned long find_next(struct sline *sline, * that are surrounded by interesting() ones. */ while (i <= cnt) - if (uninteresting + if (look_for_uninteresting ? !(sline[i].flag & mark) : (sline[i].flag & mark)) return i; @@ -489,7 +490,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt, return has_interesting; } -static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, unsigned long cnt, int n) +static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n) { l0 = sline[l0].p_lno[n]; l1 = sline[l1].p_lno[n]; @@ -523,7 +524,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) rlines--; /* pointing at the last delete hunk */ for (i = 0; i <= num_parent; i++) putchar(combine_marker); for (i = 0; i < num_parent; i++) - show_parent_lno(sline, lno, hunk_end, cnt, i); + show_parent_lno(sline, lno, hunk_end, i); printf(" +%lu,%lu ", lno+1, rlines); for (i = 0; i <= num_parent; i++) putchar(combine_marker); putchar('\n'); @@ -619,18 +620,18 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (0 <= (fd = open(elem->path, O_RDONLY)) && !fstat(fd, &st)) { int len = st.st_size; - int cnt = 0; + int sz = 0; elem->mode = canon_mode(st.st_mode); result_size = len; result = xmalloc(len + 1); - while (cnt < len) { - int done = xread(fd, result+cnt, len-cnt); + while (sz < len) { + int done = xread(fd, result+sz, len-sz); if (done == 0) break; if (done < 0) die("read error '%s'", elem->path); - cnt += done; + sz += done; } result[len] = 0; } @@ -645,7 +646,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, close(fd); } - for (cnt = 0, cp = result; cp - result < result_size; cp++) { + for (cnt = 0, cp = result; cp < result + result_size; cp++) { if (*cp == '\n') cnt++; } @@ -658,7 +659,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, sline[lno].lost_tail = &sline[lno].lost_head; sline[lno].flag = 0; } - for (lno = 0, cp = result; cp - result < result_size; cp++) { + for (lno = 0, cp = result; cp < result + result_size; cp++) { if (*cp == '\n') { sline[lno].len = cp - sline[lno].bol; lno++; @@ -739,9 +740,9 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } free(result); - for (i = 0; i < cnt; i++) { - if (sline[i].lost_head) { - struct lline *ll = sline[i].lost_head; + for (lno = 0; lno < cnt; lno++) { + if (sline[lno].lost_head) { + struct lline *ll = sline[lno].lost_head; while (ll) { struct lline *tmp = ll; ll = ll->next; @@ -236,6 +236,7 @@ static struct commit_graft *lookup_commit_graft(const unsigned char *sha1) int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) { + char *tail = buffer; char *bufptr = buffer; unsigned char parent[20]; struct commit_list **pptr; @@ -245,9 +246,10 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) if (item->object.parsed) return 0; item->object.parsed = 1; - if (memcmp(bufptr, "tree ", 5)) + tail += size; + if (tail <= bufptr + 5 || memcmp(bufptr, "tree ", 5)) return error("bogus commit object %s", sha1_to_hex(item->object.sha1)); - if (get_sha1_hex(bufptr + 5, parent) < 0) + if (tail <= bufptr + 45 || get_sha1_hex(bufptr + 5, parent) < 0) return error("bad tree pointer in commit %s", sha1_to_hex(item->object.sha1)); item->tree = lookup_tree(parent); @@ -257,10 +259,12 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) pptr = &item->parents; graft = lookup_commit_graft(item->object.sha1); - while (!memcmp(bufptr, "parent ", 7)) { + while (bufptr + 48 < tail && !memcmp(bufptr, "parent ", 7)) { struct commit *new_parent; - if (get_sha1_hex(bufptr + 7, parent) || bufptr[47] != '\n') + if (tail <= bufptr + 48 || + get_sha1_hex(bufptr + 7, parent) || + bufptr[47] != '\n') return error("bad parents in commit %s", sha1_to_hex(item->object.sha1)); bufptr += 48; if (graft) @@ -543,7 +547,7 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com const char *hex = abbrev ? find_unique_abbrev(p->object.sha1, abbrev) : sha1_to_hex(p->object.sha1); - char *dots = (abbrev && strlen(hex) != 40) ? "..." : ""; + const char *dots = (abbrev && strlen(hex) != 40) ? "..." : ""; parent = parent->next; offset += sprintf(buf + offset, " %s%s", hex, dots); @@ -244,9 +244,9 @@ int git_config_bool(const char *name, const char *value) return 1; if (!*value) return 0; - if (!strcasecmp(value, "true")) + if (!strcasecmp(value, "true") || !strcasecmp(value, "yes")) return 1; - if (!strcasecmp(value, "false")) + if (!strcasecmp(value, "false") || !strcasecmp(value, "no")) return 0; return git_config_int(name, value) != 0; } @@ -279,6 +279,16 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.compression")) { + int level = git_config_int(var, value); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die("bad zlib compression level %d", level); + zlib_compression_level = level; + return 0; + } + if (!strcmp(var, "user.name")) { strlcpy(git_default_name, value, sizeof(git_default_name)); return 0; @@ -12,11 +12,40 @@ static char *server_capabilities = NULL; +static int check_ref(const char *name, int len, unsigned int flags) +{ + if (!flags) + return 1; + + if (len > 45 || memcmp(name, "refs/", 5)) + return 0; + + /* Skip the "refs/" part */ + name += 5; + len -= 5; + + /* REF_NORMAL means that we don't want the magic fake tag refs */ + if ((flags & REF_NORMAL) && check_ref_format(name) < 0) + return 0; + + /* REF_HEADS means that we want regular branch heads */ + if ((flags & REF_HEADS) && !memcmp(name, "heads/", 6)) + return 1; + + /* REF_TAGS means that we want tags */ + if ((flags & REF_TAGS) && !memcmp(name, "tags/", 5)) + return 1; + + /* All type bits clear means that we are ok with anything */ + return !(flags & ~REF_NORMAL); +} + /* * Read all the refs from the other end */ struct ref **get_remote_heads(int in, struct ref **list, - int nr_match, char **match, int ignore_funny) + int nr_match, char **match, + unsigned int flags) { *list = NULL; for (;;) { @@ -43,10 +72,8 @@ struct ref **get_remote_heads(int in, struct ref **list, server_capabilities = strdup(name + name_len + 1); } - if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) && - check_ref_format(name + 5)) + if (!check_ref(name, name_len, flags)) continue; - if (nr_match && !path_match(name, nr_match, match)) continue; ref = xcalloc(1, sizeof(*ref) + len - 40); @@ -328,9 +355,9 @@ static enum protocol get_protocol(const char *name) */ static int git_tcp_connect_sock(char *host) { - int sockfd = -1; + int sockfd = -1, saved_errno = 0; char *colon, *end; - char *port = STR(DEFAULT_GIT_PORT); + const char *port = STR(DEFAULT_GIT_PORT); struct addrinfo hints, *ai0, *ai; int gai; @@ -362,9 +389,12 @@ static int git_tcp_connect_sock(char *host) for (ai0 = ai; ai; ai = ai->ai_next) { sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sockfd < 0) + if (sockfd < 0) { + saved_errno = errno; continue; + } if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) { + saved_errno = errno; close(sockfd); sockfd = -1; continue; @@ -375,7 +405,7 @@ static int git_tcp_connect_sock(char *host) freeaddrinfo(ai0); if (sockfd < 0) - die("unable to connect a socket (%s)", strerror(errno)); + die("unable to connect a socket (%s)", strerror(saved_errno)); return sockfd; } @@ -387,7 +417,7 @@ static int git_tcp_connect_sock(char *host) */ static int git_tcp_connect_sock(char *host) { - int sockfd = -1; + int sockfd = -1, saved_errno = 0; char *colon, *end; char *port = STR(DEFAULT_GIT_PORT), *ep; struct hostent *he; @@ -426,8 +456,10 @@ static int git_tcp_connect_sock(char *host) for (ap = he->h_addr_list; *ap; ap++) { sockfd = socket(he->h_addrtype, SOCK_STREAM, 0); - if (sockfd < 0) + if (sockfd < 0) { + saved_errno = errno; continue; + } memset(&sa, 0, sizeof sa); sa.sin_family = he->h_addrtype; @@ -435,6 +467,7 @@ static int git_tcp_connect_sock(char *host) memcpy(&sa.sin_addr, *ap, he->h_length); if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) { + saved_errno = errno; close(sockfd); sockfd = -1; continue; @@ -443,7 +476,7 @@ static int git_tcp_connect_sock(char *host) } if (sockfd < 0) - die("unable to connect a socket (%s)", strerror(errno)); + die("unable to connect a socket (%s)", strerror(saved_errno)); return sockfd; } @@ -451,8 +484,7 @@ static int git_tcp_connect_sock(char *host) #endif /* NO_IPV6 */ -static void git_tcp_connect(int fd[2], - const char *prog, char *host, char *path) +static void git_tcp_connect(int fd[2], char *host) { int sockfd = git_tcp_connect_sock(host); @@ -522,10 +554,9 @@ static int git_use_proxy(const char *host) return (git_proxy_command && *git_proxy_command); } -static void git_proxy_connect(int fd[2], - const char *prog, char *host, char *path) +static void git_proxy_connect(int fd[2], char *host) { - char *port = STR(DEFAULT_GIT_PORT); + const char *port = STR(DEFAULT_GIT_PORT); char *colon, *end; int pipefd[2][2]; pid_t pid; @@ -643,9 +674,9 @@ int git_connect(int fd[2], char *url, const char *prog) */ char *target_host = strdup(host); if (git_use_proxy(host)) - git_proxy_connect(fd, prog, host, path); + git_proxy_connect(fd, host); else - git_tcp_connect(fd, prog, host, path); + git_tcp_connect(fd, host); /* * Separate original protocol components prog and path * from extended components with a NUL byte. diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 2453cdcfae..3f6ed699f0 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -95,16 +95,17 @@ "Register FILE into the git version-control system." (vc-git--run-command file "update-index" "--add" "--")) -(defun vc-git-print-log (file) +(defun vc-git-print-log (file &optional buffer) (let ((name (file-relative-name file)) (coding-system-for-read git-commits-coding-system)) - (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--"))) + (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--"))) -(defun vc-git-diff (file &optional rev1 rev2) - (let ((name (file-relative-name file))) +(defun vc-git-diff (file &optional rev1 rev2 buffer) + (let ((name (file-relative-name file)) + (buf (or buffer "*vc-diff*"))) (if (and rev1 rev2) - (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--") - (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--")) + (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--") + (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--")) ; git-diff-index doesn't set exit status like diff does (if (vc-git-workfile-unchanged-p file) 0 1))) diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index 08c30103f5..8bc4188e03 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -19,6 +19,7 @@ my $TZ = $ENV{TZ}; # make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; +$| = 1; # unbuffer STDOUT # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. @@ -34,6 +35,8 @@ use POSIX qw/strftime/; use IPC::Open3; use Memoize; memoize('revisions_eq'); +memoize('cmt_metadata'); +memoize('get_commit_time'); my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); $_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB}; @@ -43,7 +46,8 @@ my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, - $_repack, $_repack_nr, $_repack_flags, + $_repack, $_repack_nr, $_repack_flags, $_q, + $_message, $_file, $_follow_parent, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m); @@ -53,9 +57,12 @@ my @repo_path_split_cache; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'branch|b=s' => \@_branch_from, + 'follow-parent|follow' => \$_follow_parent, 'branch-all-refs|B' => \$_branch_all_refs, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, + 'no-metadata' => \$_no_metadata, + 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -63,6 +70,12 @@ my %multi_opts = ( 'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags, 'branches|b=s' => \$_branches ); my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared ); +my %cmt_opts = ( 'edit|e' => \$_edit, + 'rmdir' => \$_rmdir, + 'find-copies-harder' => \$_find_copies_harder, + 'l=i' => \$_l, + 'copy-similarity|C=i'=> \$_cp_similarity +); # yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome: my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" ); @@ -74,14 +87,7 @@ my %cmd = ( " (requires URL argument)", \%init_opts ], commit => [ \&commit, "Commit git revisions to SVN", - { 'stdin|' => \$_stdin, - 'edit|e' => \$_edit, - 'rmdir' => \$_rmdir, - 'find-copies-harder' => \$_find_copies_harder, - 'l=i' => \$_l, - 'copy-similarity|C=i'=> \$_cp_similarity, - %fc_opts, - } ], + { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", @@ -91,6 +97,8 @@ my %cmd = ( 'graft-branches' => [ \&graft_branches, 'Detect merges/branches from already imported history', { 'merge-rx|m' => \@_opt_m, + 'branch|b=s' => \@_branch_from, + 'branch-all-refs|B' => \$_branch_all_refs, 'no-default-regex' => \$_no_default_regex, 'no-graft-copy' => \$_no_graft_copy } ], 'multi-init' => [ \&multi_init, @@ -108,6 +116,10 @@ my %cmd = ( 'show-commit' => \$_show_commit, 'authors-file|A=s' => \$_authors, } ], + 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', + { 'message|m=s' => \$_message, + 'file|F=s' => \$_file, + %cmt_opts } ], ); my $cmd; @@ -134,7 +146,7 @@ usage(1) unless defined $cmd; init_vars(); load_authors() if $_authors; load_all_refs() if $_branch_all_refs; -svn_compat_check(); +svn_compat_check() unless $_use_lib; migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -252,9 +264,19 @@ when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN } sub init { - $SVN_URL = shift or die "SVN repository location required " . + my $url = shift or die "SVN repository location required " . "as a command-line argument\n"; - $SVN_URL =~ s!/+$!!; # strip trailing slash + $url =~ s!/+$!!; # strip trailing slash + + if (my $repo_path = shift) { + unless (-d $repo_path) { + mkpath([$repo_path]); + } + $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git"; + init_vars(); + } + + $SVN_URL = $url; unless (-d $GIT_DIR) { my @init_db = ('git-init-db'); push @init_db, "--template=$_template" if defined $_template; @@ -379,7 +401,8 @@ sub fetch_lib { # performance sucks with it enabled, so it's much # faster to fetch revision ranges instead of relying # on the limiter. - $SVN_LOG->get_log( '/'.$SVN_PATH, $min, $max, 0, 1, 1, + libsvn_get_log($SVN_LOG, '/'.$SVN_PATH, + $min, $max, 0, 1, 1, sub { my $log_msg; if ($last_commit) { @@ -479,11 +502,7 @@ sub commit_lib { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; - if (defined $LC_ALL) { - $ENV{LC_ALL} = $LC_ALL; - } else { - delete $ENV{LC_ALL}; - } + set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -589,13 +608,14 @@ sub graft_branches { my $l_map = read_url_paths(); my @re = map { qr/$_/is } @_opt_m if @_opt_m; unless ($_no_default_regex) { - push @re, ( qr/\b(?:merge|merging|merged)\s+(\S.+)/is, - qr/\b(?:from|of)\s+(\S.+)/is ); + push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i, + qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i, + qr/\b(?:from|of)\s+([\w\.\-]+)/i ); } foreach my $u (keys %$l_map) { if (@re) { foreach my $p (keys %{$l_map->{$u}}) { - graft_merge_msg($grafts,$l_map,$u,$p); + graft_merge_msg($grafts,$l_map,$u,$p,@re); } } unless ($_no_graft_copy) { @@ -606,6 +626,7 @@ sub graft_branches { } } } + graft_tree_joins($grafts); write_grafts($grafts, $comments, $gr_file); unlink "$gr_file~$gr_sha1" if $gr_sha1; @@ -716,6 +737,55 @@ out: print '-' x72,"\n" unless $_incremental || $_oneline; } +sub commit_diff_usage { + print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n"; + exit 1 +} + +sub commit_diff { + if (!$_use_lib) { + print STDERR "commit-diff must be used with SVN libraries\n"; + exit 1; + } + my $ta = shift or commit_diff_usage(); + my $tb = shift or commit_diff_usage(); + if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) { + print STDERR "Needed URL or usable git-svn id command-line\n"; + commit_diff_usage(); + } + if (defined $_message && defined $_file) { + print STDERR "Both --message/-m and --file/-F specified ", + "for the commit message.\n", + "I have no idea what you mean\n"; + exit 1; + } + if (defined $_file) { + $_message = file_to_s($_message); + } else { + $_message ||= get_commit_message($tb, + "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; + } + my $repo; + ($repo, $SVN_PATH) = repo_path_split($SVN_URL); + $SVN_LOG ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($repo); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); + my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum, + ra => $SVN, c => $tb, + svn_path => $SVN_PATH + }, + $SVN->get_commit_editor($_message, + sub {print "Committed $_[0]\n"},@lock) + ); + my $mods = libsvn_checkout_tree($ta, $tb, $ed); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + $ed->abort_edit; + } else { + $ed->close_edit; + } +} + ########################### utility functions ######################### sub cmt_showable { @@ -768,35 +838,19 @@ sub fetch_child_id { my $id = shift; print "Fetching $id\n"; my $ref = "$GIT_DIR/refs/remotes/$id"; - my $ca = file_to_s($ref) if (-r $ref); - defined(my $pid = fork) or croak $!; + defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + $_repack = undef; $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); fetch(@_); exit 0; } - waitpid $pid, 0; - croak $? if $?; - return unless $_repack || -r $ref; - - my $cb = file_to_s($ref); - - defined($pid = open my $fh, '-|') or croak $!; - my $url = file_to_s("$GIT_DIR/svn/$id/info/url"); - $url = qr/\Q$url\E/; - if (!$pid) { - exec qw/git-rev-list --pretty=raw/, - $ca ? "$ca..$cb" : $cb or croak $!; - } while (<$fh>) { - if (/^ git-svn-id: $url\@\d+ [a-f0-9\-]+$/) { - check_repack(); - } elsif (/^ git-svn-id: \S+\@\d+ [a-f0-9\-]+$/) { - last; - } + print $_; + check_repack() if (/^r\d+ = $sha1/); } - close $fh; + close $fh or croak $?; } sub rec_fetch { @@ -878,6 +932,77 @@ sub common_prefix { return ''; } +# grafts set here are 'stronger' in that they're based on actual tree +# matches, and won't be deleted from merge-base checking in write_grafts() +sub graft_tree_joins { + my $grafts = shift; + map_tree_joins() if (@_branch_from && !%tree_map); + return unless %tree_map; + + git_svn_each(sub { + my $i = shift; + defined(my $pid = open my $fh, '-|') or croak $!; + if (!$pid) { + exec qw/git-rev-list --pretty=raw/, + "refs/remotes/$i" or croak $!; + } + while (<$fh>) { + next unless /^commit ($sha1)$/o; + my $c = $1; + my ($t) = (<$fh> =~ /^tree ($sha1)$/o); + next unless $tree_map{$t}; + + my $l; + do { + $l = readline $fh; + } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/); + + my ($s, $tz) = ($1, $2); + if ($tz =~ s/^\+//) { + $s += tz_to_s_offset($tz); + } elsif ($tz =~ s/^\-//) { + $s -= tz_to_s_offset($tz); + } + + my ($url_a, $r_a, $uuid_a) = cmt_metadata($c); + + foreach my $p (@{$tree_map{$t}}) { + next if $p eq $c; + my $mb = eval { + safe_qx('git-merge-base', $c, $p) + }; + next unless ($@ || $?); + if (defined $r_a) { + # see if SVN says it's a relative + my ($url_b, $r_b, $uuid_b) = + cmt_metadata($p); + next if (defined $url_b && + defined $url_a && + ($url_a eq $url_b) && + ($uuid_a eq $uuid_b)); + if ($uuid_a eq $uuid_b) { + if ($r_b < $r_a) { + $grafts->{$c}->{$p} = 2; + next; + } elsif ($r_b > $r_a) { + $grafts->{$p}->{$c} = 2; + next; + } + } + } + my $ct = get_commit_time($p); + if ($ct < $s) { + $grafts->{$c}->{$p} = 2; + } elsif ($ct > $s) { + $grafts->{$p}->{$c} = 2; + } + # what should we do when $ct == $s ? + } + } + close $fh or croak $?; + }); +} + # this isn't funky-filename safe, but good enough for now... sub graft_file_copy_cmd { my ($grafts, $l_map, $u) = @_; @@ -924,7 +1049,7 @@ sub graft_file_copy_lib { $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { my $pool = SVN::Pool->new; - $SVN_LOG->get_log( "/$path", $min, $max, 0, 1, 1, + libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); @@ -956,7 +1081,7 @@ sub process_merge_msg_matches { my $re = qr/\Q$w\E/i; foreach (keys %{$l_map->{$u}}) { if (/$re/) { - push @strong, $_; + push @strong, $l_map->{$u}->{$_}; last; } } @@ -965,7 +1090,7 @@ sub process_merge_msg_matches { $re = qr/\Q$w\E/i; foreach (keys %{$l_map->{$u}}) { if (/$re/) { - push @strong, $_; + push @strong, $l_map->{$u}->{$_}; last; } } @@ -978,7 +1103,7 @@ sub process_merge_msg_matches { return unless defined $rev; } foreach my $m (@strong) { - my ($r0, $s0) = find_rev_before($rev, $m); + my ($r0, $s0) = find_rev_before($rev, $m, 1); $grafts->{$c->{c}}->{$s0} = 1 if defined $s0; } } @@ -1340,12 +1465,12 @@ sub libsvn_checkout_tree { foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) { my $f = $m->{chg}; if (defined $o{$f}) { - $ed->$f($m); + $ed->$f($m, $_q); } else { croak "Invalid change type: $f\n"; } } - $ed->rmdirs if $_rmdir; + $ed->rmdirs($_q) if $_rmdir; return $mods; } @@ -1392,7 +1517,6 @@ sub get_commit_message { my %log_msg = ( msg => '' ); open my $msg, '>', $commit_msg or croak $!; - print "commit: $commit\n"; chomp(my $type = `git-cat-file -t $commit`); if ($type eq 'commit') { my $pid = open my $msg_fh, '-|'; @@ -1429,6 +1553,14 @@ sub get_commit_message { return \%log_msg; } +sub set_svn_commit_env { + if (defined $LC_ALL) { + $ENV{LC_ALL} = $LC_ALL; + } else { + delete $ENV{LC_ALL}; + } +} + sub svn_commit_tree { my ($last, $commit) = @_; my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; @@ -1436,11 +1568,7 @@ sub svn_commit_tree { my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/); print "Committing $commit: $oneline\n"; - if (defined $LC_ALL) { - $ENV{LC_ALL} = $LC_ALL; - } else { - delete $ENV{LC_ALL}; - } + set_svn_commit_env(); my @ci_output = safe_qx(qw(svn commit -F),$commit_msg); $ENV{LC_ALL} = 'C'; unlink $commit_msg; @@ -1789,8 +1917,34 @@ sub git_commit { croak $? if $?; restore_index($index); } + + # just in case we clobber the existing ref, we still want that ref + # as our parent: + if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) { + push @tmp_parents, $cur; + } + if (exists $tree_map{$tree}) { - push @tmp_parents, @{$tree_map{$tree}}; + foreach my $p (@{$tree_map{$tree}}) { + my $skip; + foreach (@tmp_parents) { + # see if a common parent is found + my $mb = eval { + safe_qx('git-merge-base', $_, $p) + }; + next if ($@ || $?); + $skip = 1; + last; + } + next if $skip; + my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); + next if (($SVN_UUID eq $uuid_p) && + ($log_msg->{revision} > $r_p)); + next if (defined $url_p && defined $SVN_URL && + ($SVN_UUID eq $uuid_p) && + ($url_p eq $SVN_URL)); + push @tmp_parents, $p; + } } foreach (@tmp_parents) { next if $seen_parent{$_}; @@ -1800,31 +1954,26 @@ sub git_commit { last if @exec_parents > 16; } - defined(my $pid = open my $out_fh, '-|') or croak $!; - if ($pid == 0) { - my $msg_fh = IO::File->new_tmpfile or croak $!; - print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ", - "$SVN_URL\@$log_msg->{revision}", + set_commit_env($log_msg); + my @exec = ('git-commit-tree', $tree); + push @exec, '-p', $_ foreach @exec_parents; + defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) + or croak $!; + print $msg_fh $log_msg->{msg} or croak $!; + unless ($_no_metadata) { + print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}", " $SVN_UUID\n" or croak $!; - $msg_fh->flush == 0 or croak $!; - seek $msg_fh, 0, 0 or croak $!; - set_commit_env($log_msg); - my @exec = ('git-commit-tree',$tree); - push @exec, '-p', $_ foreach @exec_parents; - open STDIN, '<&', $msg_fh or croak $!; - exec @exec or croak $!; } + $msg_fh->flush == 0 or croak $!; + close $msg_fh or croak $!; chomp(my $commit = do { local $/; <$out_fh> }); - close $out_fh or croak $?; + close $out_fh or croak $!; + waitpid $pid, 0; + croak $? if $?; if ($commit !~ /^$sha1$/o) { - croak "Failed to commit, invalid sha1: $commit\n"; + die "Failed to commit, invalid sha1: $commit\n"; } - my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit); - if (my $primary_parent = shift @exec_parents) { - quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"); - push @update_ref, $primary_parent unless $?; - } - sys(@update_ref); + sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit); revdb_set($REVDB, $log_msg->{revision}, $commit); # this output is read via pipe, do not change: @@ -1909,6 +2058,11 @@ sub safe_qx { } sub svn_compat_check { + if ($_follow_parent) { + print STDERR 'E: --follow-parent functionality is only ', + "available when SVN libraries are used\n"; + exit 1; + } my @co_help = safe_qx(qw(svn co -h)); unless (grep /ignore-externals/,@co_help) { print STDERR "W: Installed svn version does not support ", @@ -2118,6 +2272,7 @@ sub init_vars { $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; $SVN_WC = "$GIT_SVN_DIR/tree"; + %tree_map = (); } # convert GetOpt::Long specs for use by git-repo-config @@ -2185,6 +2340,7 @@ sub write_grafts { print $fh $_ foreach @{$comments->{$c}}; } my $p = $grafts->{$c}; + my %x; # real parents delete $p->{$c}; # commits are not self-reproducing... my $pid = open my $ch, '-|'; defined $pid or croak $!; @@ -2192,13 +2348,41 @@ sub write_grafts { exec(qw/git-cat-file commit/, $c) or croak $!; } while (<$ch>) { - if (/^parent ([a-f\d]{40})/) { - $p->{$1} = 1; + if (/^parent ($sha1)/) { + $x{$1} = $p->{$1} = 1; } else { - last unless /^\S/i; + last unless /^\S/; } } close $ch; # breaking the pipe + + # if real parents are the only ones in the grafts, drop it + next if join(' ',sort keys %$p) eq join(' ',sort keys %x); + + my (@ip, @jp, $mb); + my %del = %x; + @ip = @jp = keys %$p; + foreach my $i (@ip) { + next if $del{$i} || $p->{$i} == 2; + foreach my $j (@jp) { + next if $i eq $j || $del{$j} || $p->{$j} == 2; + $mb = eval { safe_qx('git-merge-base',$i,$j) }; + next unless $mb; + chomp $mb; + next if $x{$mb}; + if ($mb eq $j) { + delete $p->{$i}; + $del{$i} = 1; + } elsif ($mb eq $i) { + delete $p->{$j}; + $del{$j} = 1; + } + } + } + + # if real parents are the only ones in the grafts, drop it + next if join(' ',sort keys %$p) eq join(' ',sort keys %x); + print $fh $c, ' ', join(' ', sort keys %$p),"\n"; } if ($comments->{'END'}) { @@ -2207,6 +2391,28 @@ sub write_grafts { close $fh or croak $!; } +sub read_url_paths_all { + my ($l_map, $pfx, $p) = @_; + my @dir; + foreach (<$p/*>) { + if (-r "$_/info/url") { + $pfx .= '/' if $pfx && $pfx !~ m!/$!; + my $id = $pfx . basename $_; + my $url = file_to_s("$_/info/url"); + my ($u, $p) = repo_path_split($url); + $l_map->{$u}->{$p} = $id; + } elsif (-d $_) { + push @dir, $_; + } + } + foreach (@dir) { + my $x = $_; + $x =~ s!^\Q$GIT_DIR\E/svn/!!o; + read_url_paths_all($l_map, $x, $_); + } +} + +# this one only gets ids that have been imported, not new ones sub read_url_paths { my $l_map = {}; git_svn_each(sub { my $x = shift; @@ -2218,7 +2424,7 @@ sub read_url_paths { } sub extract_metadata { - my $id = shift; + my $id = shift or return (undef, undef, undef); my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+) \s([a-f\d\-]+)$/x); if (!$rev || !$uuid || !$url) { @@ -2229,6 +2435,31 @@ sub extract_metadata { return ($url, $rev, $uuid); } +sub cmt_metadata { + return extract_metadata((grep(/^git-svn-id: /, + safe_qx(qw/git-cat-file commit/, shift)))[-1]); +} + +sub get_commit_time { + my $cmt = shift; + defined(my $pid = open my $fh, '-|') or croak $!; + if (!$pid) { + exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!; + } + while (<$fh>) { + /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next; + my ($s, $tz) = ($1, $2); + if ($tz =~ s/^\+//) { + $s += tz_to_s_offset($tz); + } elsif ($tz =~ s/^\-//) { + $s -= tz_to_s_offset($tz); + } + close $fh; + return $s; + } + die "Can't get commit time for commit: $cmt\n"; +} + sub tz_to_s_offset { my ($tz) = @_; $tz =~ s/(\d\d)$//; @@ -2358,8 +2589,8 @@ sub libsvn_load { return unless $_use_lib; $_use_lib = eval { require SVN::Core; - if ($SVN::Core::VERSION lt '1.2.1') { - die "Need SVN::Core 1.2.1 or better ", + if ($SVN::Core::VERSION lt '1.1.0') { + die "Need SVN::Core 1.1.0 or better ", "(got $SVN::Core::VERSION) ", "Falling back to command-line svn\n"; } @@ -2386,15 +2617,20 @@ sub libsvn_connect { sub libsvn_get_file { my ($gui, $f, $rev) = @_; my $p = $f; - return unless ($p =~ s#^\Q$SVN_PATH\E/?##); + return unless ($p =~ s#^\Q$SVN_PATH\E/##); my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; defined($pid = open3($in, $out, '>&STDERR', qw/git-hash-object -w --stdin/)) or croak $!; - my ($r, $props) = $SVN->get_file($f, $rev, $in, $pool); + # redirect STDOUT for SVN 1.1.x compatibility + open my $stdout, '>&', \*STDOUT or croak $!; + open STDOUT, '>&', $in or croak $!; + my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool); $in->flush == 0 or croak $!; + open STDOUT, '>&', $stdout or croak $!; close $in or croak $!; + close $stdout or croak $!; $pool->clear; chomp($hash = do { local $/; <$out> }); close $out or croak $!; @@ -2460,6 +2696,7 @@ sub libsvn_fetch { my $m = $paths->{$f}->action(); $f =~ s#^/+##; if ($m =~ /^[DR]$/) { + print "\t$m\t$f\n" unless $_q; process_rm($gui, $last_commit, $f); next if $m eq 'D'; # 'R' can be file replacements, too, right? @@ -2468,14 +2705,17 @@ sub libsvn_fetch { my $t = $SVN->check_path($f, $rev, $pool); if ($t == $SVN::Node::file) { if ($m =~ /^[AMR]$/) { - push @amr, $f; + push @amr, [ $m, $f ]; } else { die "Unrecognized action: $m, ($f r$rev)\n"; } } $pool->clear; } - libsvn_get_file($gui, $_, $rev) foreach (@amr); + foreach (@amr) { + print "\t$_->[0]\t$_->[1]\n" unless $_q; + libsvn_get_file($gui, $_->[1], $rev) + } close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); } @@ -2491,8 +2731,29 @@ sub svn_grab_base_rev { chomp(my $c = do { local $/; <$fh> }); close $fh; if (defined $c && length $c) { - my ($url, $rev, $uuid) = extract_metadata((grep(/^git-svn-id: /, - safe_qx(qw/git-cat-file commit/, $c)))[-1]); + my ($url, $rev, $uuid) = cmt_metadata($c); + return ($rev, $c) if defined $rev; + } + if ($_no_metadata) { + my $offset = -41; # from tail + my $rl; + open my $fh, '<', $REVDB or + die "--no-metadata specified and $REVDB not readable\n"; + seek $fh, $offset, 2; + $rl = readline $fh; + defined $rl or return (undef, undef); + chomp $rl; + while ($c ne $rl && tell $fh != 0) { + $offset -= 41; + seek $fh, $offset, 2; + $rl = readline $fh; + defined $rl or return (undef, undef); + chomp $rl; + } + my $rev = tell $fh; + croak $! if ($rev < -1); + $rev = ($rev - 41) / 41; + close $fh or croak $!; return ($rev, $c); } return (undef, undef); @@ -2527,6 +2788,7 @@ sub libsvn_traverse { if ($t == $SVN::Node::dir) { libsvn_traverse($gui, $cwd, $d, $rev); } elsif ($t == $SVN::Node::file) { + print "\tA\t$cwd/$d\n" unless $_q; libsvn_get_file($gui, "$cwd/$d", $rev); } } @@ -2566,7 +2828,8 @@ sub revisions_eq { if ($_use_lib) { # should be OK to use Pool here (r1 - r0) should be small my $pool = SVN::Pool->new; - $SVN->get_log("/$path", $r0, $r1, 0, 1, 1, sub {$nr++},$pool); + libsvn_get_log($SVN, "/$path", $r0, $r1, + 0, 1, 1, sub {$nr++}, $pool); $pool->clear; } else { my ($url, undef) = repo_path_split($SVN_URL); @@ -2589,15 +2852,45 @@ sub libsvn_find_parent_branch { print STDERR "Found possible branch point: ", "$branch_from => $svn_path, $r\n"; $branch_from =~ s#^/##; - my $l_map = read_url_paths(); + my $l_map = {}; + read_url_paths_all($l_map, '', "$GIT_DIR/svn"); my $url = $SVN->{url}; defined $l_map->{$url} or return; - my $id = $l_map->{$url}->{$branch_from} or return; + my $id = $l_map->{$url}->{$branch_from}; + if (!defined $id && $_follow_parent) { + print STDERR "Following parent: $branch_from\@$r\n"; + # auto create a new branch and follow it + $id = basename($branch_from); + $id .= '@'.$r if -r "$GIT_DIR/svn/$id"; + while (-r "$GIT_DIR/svn/$id") { + # just grow a tail if we're not unique enough :x + $id .= '-'; + } + } + return unless defined $id; + my ($r0, $parent) = find_rev_before($r,$id,1); + if ($_follow_parent && (!defined $r0 || !defined $parent)) { + defined(my $pid = fork) or croak $!; + if (!$pid) { + $GIT_SVN = $ENV{GIT_SVN_ID} = $id; + init_vars(); + $SVN_URL = "$url/$branch_from"; + $SVN_LOG = $SVN = undef; + setup_git_svn(); + # we can't assume SVN_URL exists at r+1: + $_revision = "0:$r"; + fetch_lib(); + exit 0; + } + waitpid $pid, 0; + croak $? if $?; + ($r0, $parent) = find_rev_before($r,$id,1); + } return unless (defined $r0 && defined $parent); if (revisions_eq($branch_from, $r0, $r)) { unlink $GIT_SVN_INDEX; - print STDERR "Found branch parent: $parent\n"; + print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; sys(qw/git-read-tree/, $parent); return libsvn_fetch($parent, $paths, $rev, $author, $date, $msg); @@ -2606,6 +2899,14 @@ sub libsvn_find_parent_branch { return undef; } +sub libsvn_get_log { + my ($ra, @args) = @_; + if ($SVN::Core::VERSION le '1.2.0') { + splice(@args, 3, 1); + } + $ra->get_log(@args); +} + sub libsvn_new_tree { if (my $log_entry = libsvn_find_parent_branch(@_)) { return $log_entry; @@ -2639,6 +2940,10 @@ sub find_graft_path_parents { my $i = $tree_paths->{$x}; my ($r, $parent) = find_rev_before($r0, $i, 1); if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) { + my ($url_b, undef, $uuid_b) = cmt_metadata($c); + my ($url_a, undef, $uuid_a) = cmt_metadata($parent); + next if ($url_a && $url_b && $url_a eq $url_b && + $uuid_b eq $uuid_a); $grafts->{$c}->{$parent} = 1; } } @@ -2820,7 +3125,7 @@ sub url_path { } sub rmdirs { - my ($self) = @_; + my ($self, $q) = @_; my $rm = $self->{rm}; delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; @@ -2861,6 +3166,7 @@ sub rmdirs { foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) { $self->close_directory($bat->{$d}, $p); my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#); + print "\tD+\t/$d/\n" unless $q; $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p); delete $bat->{$d}; } @@ -2901,21 +3207,23 @@ sub ensure_path { } sub A { - my ($self, $m) = @_; + my ($self, $m, $q) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, undef, -1); + print "\tA\t$m->{file_b}\n" unless $q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } sub C { - my ($self, $m) = @_; + my ($self, $m, $q) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, $self->url_path($m->{file_a}), $self->{r}); + print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } @@ -2929,11 +3237,12 @@ sub delete_entry { } sub R { - my ($self, $m) = @_; + my ($self, $m, $q) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, $self->url_path($m->{file_a}), $self->{r}); + print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); @@ -2943,11 +3252,12 @@ sub R { } sub M { - my ($self, $m) = @_; + my ($self, $m, $q) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->open_file($self->repo_path($m->{file_b}), $pbat,$self->{r},$self->{pool}); + print "\t$m->{chg}\t$m->{file_b}\n" unless $q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } @@ -2996,9 +3306,10 @@ sub chg_file { } sub D { - my ($self, $m) = @_; + my ($self, $m, $q) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); + print "\tD\t$m->{file_b}\n" unless $q; $self->delete_entry($m->{file_b}, $pbat); } @@ -3052,6 +3363,16 @@ diff-index line ($m hash) } ; +# retval of read_url_paths{,_all}(); +$l_map = { + # repository root url + 'https://svn.musicpd.org' => { + # repository path # GIT_SVN_ID + 'mpd/trunk' => 'trunk', + 'mpd/tags/0.11.5' => 'tags/0.11.5', + }, +} + Notes: I don't trust the each() function on unless I created %hash myself because the internal iterator may not have started at base. diff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh index 2843258fc4..d7f972a0c8 100644 --- a/contrib/git-svn/t/lib-git-svn.sh +++ b/contrib/git-svn/t/lib-git-svn.sh @@ -33,7 +33,13 @@ svnrepo=$PWD/svnrepo set -e -svnadmin create $svnrepo +if svnadmin create --help | grep fs-type >/dev/null +then + svnadmin create --fs-type fsfs "$svnrepo" +else + svnadmin create "$svnrepo" +fi + svnrepo="file://$svnrepo/test-git-svn" diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh index 443d518367..b482bb64c0 100644 --- a/contrib/git-svn/t/t0000-contrib-git-svn.sh +++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh @@ -5,6 +5,16 @@ test_description='git-svn tests' GIT_SVN_LC_ALL=$LC_ALL + +case "$LC_ALL" in +*.UTF-8) + have_utf8=t + ;; +*) + have_utf8= + ;; +esac + . ./lib-git-svn.sh mkdir import @@ -173,7 +183,7 @@ then fi -if test -n "$GIT_SVN_LC_ALL" && echo $GIT_SVN_LC_ALL | grep -q '\.UTF-8$' +if test "$have_utf8" = t then name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL" echo '# hello' >> exec-2.sh @@ -203,7 +213,7 @@ fi name='check imported tree checksums expected tree checksums' rm -f expected -if test -n "$GIT_SVN_LC_ALL" && echo $GIT_SVN_LC_ALL | grep -q '\.UTF-8$' +if test "$have_utf8" = t then echo tree f735671b89a7eb30cab1d8597de35bd4271ab813 > expected fi diff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh index 54e0ed7353..a5a235f100 100644 --- a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh +++ b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh @@ -21,8 +21,8 @@ a_empty_crlf= cd import cat >> kw.c <<\EOF -/* Make it look like somebody copied a file from CVS into SVN: */ -/* $Id: kw.c,v 1.1.1.1 1994/03/06 00:00:00 eric Exp $ */ +/* Somebody prematurely put a keyword into this file */ +/* $Id$ */ EOF printf "Hello\r\nWorld\r\n" > crlf diff --git a/contrib/git-svn/t/t0003-graft-branches.sh b/contrib/git-svn/t/t0003-graft-branches.sh new file mode 100644 index 0000000000..cc62d4ece8 --- /dev/null +++ b/contrib/git-svn/t/t0003-graft-branches.sh @@ -0,0 +1,63 @@ +test_description='git-svn graft-branches' +. ./lib-git-svn.sh + +test_expect_success 'initialize repo' " + mkdir import && + cd import && + mkdir -p trunk branches tags && + echo hello > trunk/readme && + svn import -m 'import for git-svn' . $svnrepo && + cd .. && + svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a && + svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a && + svn co $svnrepo wc && + cd wc && + echo feedme >> branches/a/readme && + svn commit -m hungry && + svn up && + cd trunk && + svn merge -r3:4 $svnrepo/branches/a && + svn commit -m 'merge with a' && + cd ../.. && + svn log -v $svnrepo && + git-svn init -i trunk $svnrepo/trunk && + git-svn init -i a $svnrepo/branches/a && + git-svn init -i tags/a $svnrepo/tags/a && + git-svn fetch -i tags/a && + git-svn fetch -i a && + git-svn fetch -i trunk + " + +r1=`git-rev-list remotes/trunk | tail -n1` +r2=`git-rev-list remotes/tags/a | tail -n1` +r3=`git-rev-list remotes/a | tail -n1` +r4=`git-rev-list remotes/a | head -n1` +r5=`git-rev-list remotes/trunk | head -n1` + +test_expect_success 'test graft-branches regexes and copies' " + test -n "$r1" && + test -n "$r2" && + test -n "$r3" && + test -n "$r4" && + test -n "$r5" && + git-svn graft-branches && + grep '^$r2 $r1' $GIT_DIR/info/grafts && + grep '^$r3 $r1' $GIT_DIR/info/grafts && + grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1' + " + +test_debug 'gitk --all & sleep 1' + +test_expect_success 'test graft-branches with tree-joins' " + rm $GIT_DIR/info/grafts && + git-svn graft-branches --no-default-regex --no-graft-copy -B && + grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' && + grep '^$r2 $r1' $GIT_DIR/info/grafts && + grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4' + " + +# the result of this is kinda funky, we have a strange history and +# this is just a test :) +test_debug 'gitk --all &' + +test_done diff --git a/contrib/git-svn/t/t0004-follow-parent.sh b/contrib/git-svn/t/t0004-follow-parent.sh new file mode 100644 index 0000000000..01488ff78a --- /dev/null +++ b/contrib/git-svn/t/t0004-follow-parent.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# +# Copyright (c) 2006 Eric Wong +# + +test_description='git-svn --follow-parent fetching' +. ./lib-git-svn.sh + +if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0 +then + echo 'Skipping: --follow-parent needs SVN libraries' + test_done + exit 0 +fi + +test_expect_success 'initialize repo' " + mkdir import && + cd import && + mkdir -p trunk && + echo hello > trunk/readme && + svn import -m 'initial' . $svnrepo && + cd .. && + svn co $svnrepo wc && + cd wc && + echo world >> trunk/readme && + svn commit -m 'another commit' && + svn up && + svn mv -m 'rename to thunk' trunk thunk && + svn up && + echo goodbye >> thunk/readme && + svn commit -m 'bye now' && + cd .. + " + +test_expect_success 'init and fetch --follow-parent a moved directory' " + git-svn init -i thunk $svnrepo/thunk && + git-svn fetch --follow-parent -i thunk && + git-rev-parse --verify refs/remotes/trunk && + test '$?' -eq '0' + " + +test_debug 'gitk --all &' + +test_done diff --git a/contrib/git-svn/t/t0005-commit-diff.sh b/contrib/git-svn/t/t0005-commit-diff.sh new file mode 100644 index 0000000000..f994b72f80 --- /dev/null +++ b/contrib/git-svn/t/t0005-commit-diff.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Copyright (c) 2006 Eric Wong +test_description='git-svn commit-diff' +. ./lib-git-svn.sh + +if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0 +then + echo 'Skipping: commit-diff needs SVN libraries' + test_done + exit 0 +fi + +test_expect_success 'initialize repo' " + mkdir import && + cd import && + echo hello > readme && + svn import -m 'initial' . $svnrepo && + cd .. && + echo hello > readme && + git update-index --add readme && + git commit -a -m 'initial' && + echo world >> readme && + git commit -a -m 'another' + " + +head=`git rev-parse --verify HEAD^0` +prev=`git rev-parse --verify HEAD^1` + +# the internals of the commit-diff command are the same as the regular +# commit, so only a basic test of functionality is needed since we've +# already tested commit extensively elsewhere + +test_expect_success 'test the commit-diff command' " + test -n '$prev' && test -n '$head' && + git-svn commit-diff '$prev' '$head' '$svnrepo' && + svn co $svnrepo wc && + cmp readme wc/readme + " + +test_done diff --git a/csum-file.c b/csum-file.c index ebaad0397f..6a7b40fd09 100644 --- a/csum-file.c +++ b/csum-file.c @@ -122,7 +122,7 @@ int sha1write_compressed(struct sha1file *f, void *in, unsigned int size) void *out; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, Z_DEFAULT_COMPRESSION); + deflateInit(&stream, zlib_compression_level); maxsize = deflateBound(&stream, size); out = xmalloc(maxsize); @@ -35,7 +35,7 @@ static char *base_path = NULL; * after ~user/. E.g. a request to git://host/~alice/frotz would * go to /home/alice/pub_git/frotz with --user-path=pub_git. */ -static char *user_path = NULL; +static const char *user_path = NULL; /* Timeout, and initial timeout */ static unsigned int timeout = 0; @@ -472,7 +472,7 @@ static void child_handler(int signo) children_reaped = reaped + 1; /* XXX: Custom logging, since we don't wanna getpid() */ if (verbose) { - char *dead = ""; + const char *dead = ""; if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) dead = " (with error)"; if (log_syslog) diff --git a/describe.c b/describe.c index aa3434a4cb..8e68d5df33 100644 --- a/describe.c +++ b/describe.c @@ -97,7 +97,7 @@ static int compare_names(const void *_a, const void *_b) return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; } -static void describe(char *arg, int last_one) +static void describe(const char *arg, int last_one) { unsigned char sha1[20]; struct commit *cmit; @@ -583,7 +583,7 @@ static unsigned char *deflate_it(char *data, z_stream stream; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, Z_BEST_COMPRESSION); + deflateInit(&stream, zlib_compression_level); bound = deflateBound(&stream, size); deflated = xmalloc(bound); stream.next_out = deflated; @@ -2095,6 +2095,145 @@ static void diff_summary(struct diff_filepair *p) } } +struct patch_id_t { + struct xdiff_emit_state xm; + SHA_CTX *ctx; + int patchlen; +}; + +static int remove_space(char *line, int len) +{ + int i; + char *dst = line; + unsigned char c; + + for (i = 0; i < len; i++) + if (!isspace((c = line[i]))) + *dst++ = c; + + return dst - line; +} + +static void patch_id_consume(void *priv, char *line, unsigned long len) +{ + struct patch_id_t *data = priv; + int new_len; + + /* Ignore line numbers when computing the SHA1 of the patch */ + if (!strncmp(line, "@@ -", 4)) + return; + + new_len = remove_space(line, len); + + SHA1_Update(data->ctx, line, new_len); + data->patchlen += new_len; +} + +/* returns 0 upon success, and writes result into sha1 */ +static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i; + SHA_CTX ctx; + struct patch_id_t data; + char buffer[PATH_MAX * 4 + 20]; + + SHA1_Init(&ctx); + memset(&data, 0, sizeof(struct patch_id_t)); + data.ctx = &ctx; + data.xm.consume = patch_id_consume; + + for (i = 0; i < q->nr; i++) { + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + mmfile_t mf1, mf2; + struct diff_filepair *p = q->queue[i]; + int len1, len2; + + if (p->status == 0) + return error("internal diff status error"); + if (p->status == DIFF_STATUS_UNKNOWN) + continue; + if (diff_unmodified_pair(p)) + continue; + if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || + (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) + continue; + if (DIFF_PAIR_UNMERGED(p)) + continue; + + diff_fill_sha1_info(p->one); + diff_fill_sha1_info(p->two); + if (fill_mmfile(&mf1, p->one) < 0 || + fill_mmfile(&mf2, p->two) < 0) + return error("unable to read files to diff"); + + /* Maybe hash p->two? into the patch id? */ + if (mmfile_is_binary(&mf2)) + continue; + + len1 = remove_space(p->one->path, strlen(p->one->path)); + len2 = remove_space(p->two->path, strlen(p->two->path)); + if (p->one->mode == 0) + len1 = snprintf(buffer, sizeof(buffer), + "diff--gita/%.*sb/%.*s" + "newfilemode%06o" + "---/dev/null" + "+++b/%.*s", + len1, p->one->path, + len2, p->two->path, + p->two->mode, + len2, p->two->path); + else if (p->two->mode == 0) + len1 = snprintf(buffer, sizeof(buffer), + "diff--gita/%.*sb/%.*s" + "deletedfilemode%06o" + "---a/%.*s" + "+++/dev/null", + len1, p->one->path, + len2, p->two->path, + p->one->mode, + len1, p->one->path); + else + len1 = snprintf(buffer, sizeof(buffer), + "diff--gita/%.*sb/%.*s" + "---a/%.*s" + "+++b/%.*s", + len1, p->one->path, + len2, p->two->path, + len1, p->one->path, + len2, p->two->path); + SHA1_Update(&ctx, buffer, len1); + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = 3; + xecfg.flags = XDL_EMIT_FUNCNAMES; + ecb.outf = xdiff_outf; + ecb.priv = &data; + xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + } + + SHA1_Final(sha1, &ctx); + return 0; +} + +int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i; + int result = diff_get_patch_id(options, sha1); + + for (i = 0; i < q->nr; i++) + diff_free_filepair(q->queue[i]); + + free(q->queue); + q->queue = NULL; + q->nr = q->alloc = 0; + + return result; +} + static int is_summary_empty(const struct diff_queue_struct *q) { int i; @@ -188,4 +188,6 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed); extern int run_diff_index(struct rev_info *revs, int cached); +extern int diff_flush_patch_id(struct diff_options *, unsigned char *); + #endif /* DIFF_H */ diff --git a/environment.c b/environment.c index 3de8eb3b2a..43823ff7d6 100644 --- a/environment.c +++ b/environment.c @@ -20,6 +20,7 @@ int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; const char *apply_default_whitespace = NULL; +int zlib_compression_level = Z_DEFAULT_COMPRESSION; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; diff --git a/fsck-objects.c b/fsck-objects.c index 769bb2a6a7..ef54a8a411 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -60,12 +60,13 @@ static int objwarning(struct object *obj, const char *err, ...) static void check_connectivity(void) { - int i; + int i, max; /* Look up all the requirements, warn about missing objects.. */ - for (i = 0; i < obj_allocs; i++) { + max = get_max_object_index(); + for (i = 0; i < max; i++) { const struct object_refs *refs; - struct object *obj = objs[i]; + struct object *obj = get_indexed_object(i); if (!obj) continue; @@ -97,7 +97,7 @@ while case "$#" in 0) break;; esac do case "$1" in -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*) - dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;; + dotest=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;; -d|--d|--do|--dot|--dote|--dotes|--dotest) case "$#" in 1) usage ;; esac; shift dotest="$1"; shift;; diff --git a/git-annotate.perl b/git-annotate.perl index a6a7a482cd..6db2f48241 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -102,10 +102,10 @@ while (my $bound = pop @stack) { push @revqueue, $head; init_claim( defined $starting_rev ? $head : 'dirty'); unless (defined $starting_rev) { - my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename) + my $diff = open_pipe("git","diff","HEAD", "--",$filename) or die "Failed to call git diff to check for dirty state: $!"; - _git_diff_parse($diff, $head, "dirty", ( + _git_diff_parse($diff, [$head], "dirty", ( 'author' => gitvar_name("GIT_AUTHOR_IDENT"), 'author_date' => sprintf("%s +0000",time()), ) @@ -154,14 +154,13 @@ sub handle_rev { my %revinfo = git_commit_info($rev); - foreach my $p (@{$revs{$rev}{'parents'}}) { - - git_diff_parse($p, $rev, %revinfo); - push @revqueue, $p; - } + if (exists $revs{$rev}{parents} && + scalar @{$revs{$rev}{parents}} != 0) { + git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo); + push @revqueue, @{$revs{$rev}{'parents'}}; - if (scalar @{$revs{$rev}{parents}} == 0) { + } else { # We must be at the initial rev here, so claim everything that is left. for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) { if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') { @@ -252,89 +251,171 @@ sub git_find_parent { # Get a diff between the current revision and a parent. # Record the commit information that results. sub git_diff_parse { - my ($parent, $rev, %revinfo) = @_; + my ($parents, $rev, %revinfo) = @_; - my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--", - $revs{$rev}{'filename'}, $revs{$parent}{'filename'}) + my @filenames = ( $revs{$rev}{'filename'} ); + foreach my $parent (@$parents) { + push @filenames, $revs{$parent}{'filename'}; + } + + my $diff = open_pipe("git-diff-tree","-M","-p","-c",$rev,"--", + @filenames ) or die "Failed to call git-diff for annotation: $!"; - _git_diff_parse($diff, $parent, $rev, %revinfo); + _git_diff_parse($diff, $parents, $rev, %revinfo); close($diff); } sub _git_diff_parse { - my ($diff, $parent, $rev, %revinfo) = @_; + my ($diff, $parents, $rev, %revinfo) = @_; + + my $ri = 0; - my ($ri, $pi) = (0,0); my $slines = $revs{$rev}{'lines'}; - my @plines; + my (%plines, %pi); my $gotheader = 0; my ($remstart); - my ($hunk_start, $hunk_index); + my $parent_count = @$parents; + + my $diff_header_regexp = "^@"; + $diff_header_regexp .= "@" x @$parents; + $diff_header_regexp .= ' -\d+,\d+' x @$parents; + $diff_header_regexp .= ' \+(\d+),\d+'; + + my %claim_regexps; + my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; + + { + my $i = 0; + foreach my $parent (@$parents) { + + $pi{$parent} = 0; + my $r = '^' . '.' x @$parents . '(.*)$'; + my $p = $r; + substr($p,$i+1, 1) = '\\+'; + + my $m = $r; + substr($m,$i+1, 1) = '-'; + + $claim_regexps{$parent}{plus} = $p; + $claim_regexps{$parent}{minus} = $m; + + $plines{$parent} = []; + + $i++; + } + } + + DIFF: while(<$diff>) { chomp; - if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) { - $remstart = $1; - # Adjust for 0-based arrays - $remstart--; - # Reinit hunk tracking. - $hunk_start = $remstart; - $hunk_index = 0; + if (m/$diff_header_regexp/) { + $remstart = $1 - 1; + # (0-based arrays) + $gotheader = 1; - for (my $i = $ri; $i < $remstart; $i++) { - $plines[$pi++] = $slines->[$i]; - $ri++; + printf("Copying from %d to %d\n", $ri, $remstart); + foreach my $parent (@$parents) { + for (my $i = $ri; $i < $remstart; $i++) { + $plines{$parent}[$pi{$parent}++] = $slines->[$i]; + } } - next; - } elsif (!$gotheader) { - next; - } + $ri = $remstart; - if (m/^\+(.*)$/) { - my $line = $1; - $plines[$pi++] = [ $line, '', '', '', 0 ]; - next; + next DIFF; - } elsif (m/^-(.*)$/) { - my $line = $1; - if (get_line($slines, $ri) eq $line) { - # Found a match, claim - claim_line($ri, $rev, $slines, %revinfo); - } else { - die sprintf("Sync error: %d/%d\n|%s\n|%s\n%s => %s\n", - $ri, $hunk_start + $hunk_index, - $line, - get_line($slines, $ri), - $rev, $parent); - } - $ri++; + } elsif (!$gotheader) { + # Skip over the leadin. + next DIFF; + } - } elsif (m/^\\/) { + if (m/^\\/) { ; # Skip \No newline at end of file. # But this can be internationalized, so only look # for an initial \ } else { - if (substr($_,1) ne get_line($slines,$ri) ) { - die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n", - $hunk_start + $hunk_index, $ri, - substr($_,1), - get_line($slines,$ri), - $rev, $parent); + my %claims = (); + my $negclaim = 0; + my $allclaimed = 0; + my $line; + + if (m/$allparentplus/) { + claim_line($ri, $rev, $slines, %revinfo); + $allclaimed = 1; + + } + + PARENT: + foreach my $parent (keys %claim_regexps) { + my $m = $claim_regexps{$parent}{minus}; + my $p = $claim_regexps{$parent}{plus}; + + if (m/$m/) { + $line = $1; + $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ]; + $negclaim++; + + } elsif (m/$p/) { + $line = $1; + if (get_line($slines, $ri) eq $line) { + # Found a match, claim + $claims{$parent}++; + + } else { + die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n", + $ri, $line, + get_line($slines, $ri), + $rev, $parent); + } + } + } + + if (%claims) { + foreach my $parent (@$parents) { + next if $claims{$parent} || $allclaimed; + $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; + #[ $line, '', '', '', 0 ]; + } + $ri++; + + } elsif ($negclaim) { + next DIFF; + + } else { + if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) { + foreach my $parent (@$parents) { + printf("parent %s is on line %d\n", $parent, $pi{$parent}); + } + + die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", + $ri, + substr($_,scalar @$parents), + get_line($slines,$ri), $rev); + } + foreach my $parent (@$parents) { + $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; + } + $ri++; } - $plines[$pi++] = $slines->[$ri++]; } - $hunk_index++; } + for (my $i = $ri; $i < @{$slines} ; $i++) { - push @plines, $slines->[$ri++]; + foreach my $parent (@$parents) { + push @{$plines{$parent}}, $slines->[$ri]; + } + $ri++; + } + + foreach my $parent (@$parents) { + $revs{$parent}{lines} = $plines{$parent}; } - $revs{$parent}{lines} = \@plines; return; } diff --git a/git-checkout.sh b/git-checkout.sh index 77c2593809..5613bfc403 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -150,8 +150,7 @@ else # Match the index to the working tree, and do a three-way. git diff-files --name-only | git update-index --remove --stdin && work=`git write-tree` && - git read-tree --reset $new && - git checkout-index -f -u -q -a && + git read-tree --reset -u $new && git read-tree -m -u --aggressive $old $new $work || exit if result=`git write-tree 2>/dev/null` diff --git a/git-clone.sh b/git-clone.sh index 6fa0daaacf..6a14b25911 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -133,7 +133,7 @@ while *,--reference) shift; reference="$1" ;; *,--reference=*) - reference=`expr "$1" : '--reference=\(.*\)'` ;; + reference=`expr "z$1" : 'z--reference=\(.*\)'` ;; *,-o|*,--or|*,--ori|*,--orig|*,--origi|*,--origin) case "$2" in '') diff --git a/git-commit.sh b/git-commit.sh index d7f3ade493..22c4ce86c3 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -29,7 +29,7 @@ THIS_INDEX="$GIT_DIR/index" NEXT_INDEX="$GIT_DIR/next-index$$" rm -f "$NEXT_INDEX" save_index () { - cp "$THIS_INDEX" "$NEXT_INDEX" + cp -p "$THIS_INDEX" "$NEXT_INDEX" } report () { @@ -223,13 +223,13 @@ do -F*|-f*) no_edit=t log_given=t$log_given - logfile=`expr "$1" : '-[Ff]\(.*\)'` + logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` shift ;; --F=*|--f=*|--fi=*|--fil=*|--file=*) no_edit=t log_given=t$log_given - logfile=`expr "$1" : '-[^=]*=\(.*\)'` + logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` shift ;; -a|--a|--al|--all) @@ -237,7 +237,7 @@ do shift ;; --au=*|--aut=*|--auth=*|--autho=*|--author=*) - force_author=`expr "$1" : '-[^=]*=\(.*\)'` + force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` shift ;; --au|--aut|--auth|--autho|--author) @@ -277,11 +277,11 @@ $1" log_given=m$log_given if test "$log_message" = '' then - log_message=`expr "$1" : '-m\(.*\)'` + log_message=`expr "z$1" : 'z-m\(.*\)'` else log_message="$log_message -`expr "$1" : '-m\(.*\)'`" +`expr "z$1" : 'z-m\(.*\)'`" fi no_edit=t shift @@ -290,11 +290,11 @@ $1" log_given=m$log_given if test "$log_message" = '' then - log_message=`expr "$1" : '-[^=]*=\(.*\)'` + log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` else log_message="$log_message -`expr "$1" : '-[^=]*=\(.*\)'`" +`expr "z$1" : 'zq-[^=]*=\(.*\)'`" fi no_edit=t shift @@ -321,7 +321,7 @@ $1" --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ --reedit-messag=*|--reedit-message=*) log_given=t$log_given - use_commit=`expr "$1" : '-[^=]*=\(.*\)'` + use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` no_edit= shift ;; @@ -346,7 +346,7 @@ $1" --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ --reuse-message=*) log_given=t$log_given - use_commit=`expr "$1" : '-[^=]*=\(.*\)'` + use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` no_edit=t shift ;; diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 50f5d9642a..e5a00a1285 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -467,11 +467,6 @@ my $orig_git_index; $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; my %index; # holds filenames of one index per branch -$index{$opt_o} = tmpnam(); - -$ENV{GIT_INDEX_FILE} = $index{$opt_o}; -system("git-read-tree", $opt_o); -die "read-tree failed: $?\n" if $?; unless(-d $git_dir) { system("git-init-db"); @@ -499,14 +494,6 @@ unless(-d $git_dir) { $orig_branch = $last_branch; $tip_at_start = `git-rev-parse --verify HEAD`; - # populate index - unless ($index{$last_branch}) { - $index{$last_branch} = tmpnam(); - } - $ENV{GIT_INDEX_FILE} = $index{$last_branch}; - system('git-read-tree', $last_branch); - die "read-tree failed: $?\n" if $?; - # Get the last import timestamps opendir(D,"$git_dir/refs/heads"); while(defined(my $head = readdir(D))) { @@ -623,6 +610,27 @@ my(@old,@new,@skipped,%ignorebranch); $ignorebranch{'#CVSPS_NO_BRANCH'} = 1; sub commit { + if ($branch eq $opt_o && !$index{branch} && !get_headref($branch, $git_dir)) { + # looks like an initial commit + # use the index primed by git-init-db + $ENV{GIT_INDEX_FILE} = '.git/index'; + $index{$branch} = '.git/index'; + } else { + # use an index per branch to speed up + # imports of projects with many branches + unless ($index{$branch}) { + $index{$branch} = tmpnam(); + $ENV{GIT_INDEX_FILE} = $index{$branch}; + if ($ancestor) { + system("git-read-tree", $ancestor); + } else { + system("git-read-tree", $branch); + } + die "read-tree failed: $?\n" if $?; + } + } + $ENV{GIT_INDEX_FILE} = $index{$branch}; + update_index(@old, @new); @old = @new = (); my $tree = write_tree(); @@ -811,30 +819,6 @@ while(<CVS>) { close(H) or die "Could not write branch $branch: $!"; } - if(($ancestor || $branch) ne $last_branch) { - print "Switching from $last_branch to $branch\n" if $opt_v; - unless ($index{$branch}) { - $index{$branch} = tmpnam(); - $ENV{GIT_INDEX_FILE} = $index{$branch}; - system("git-read-tree", $branch); - die "read-tree failed: $?\n" if $?; - } - # just in case - $ENV{GIT_INDEX_FILE} = $index{$branch}; - if ($ancestor) { - print "have ancestor $ancestor" if $opt_v; - system("git-read-tree", $ancestor); - die "read-tree failed: $?\n" if $?; - } - } else { - # just in case - unless ($index{$branch}) { - $index{$branch} = tmpnam(); - $ENV{GIT_INDEX_FILE} = $index{$branch}; - system("git-read-tree", $branch); - die "read-tree failed: $?\n" if $?; - } - } $last_branch = $branch if $branch ne $last_branch; $state = 9; } elsif($state == 8) { @@ -898,7 +882,9 @@ while(<CVS>) { commit() if $branch and $state != 11; foreach my $git_index (values %index) { - unlink($git_index); + if ($git_index ne '.git/index') { + unlink($git_index); + } } if (defined $orig_git_index) { diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl deleted file mode 100755 index 5986e5414a..0000000000 --- a/git-fmt-merge-msg.perl +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/perl -w -# -# Copyright (c) 2005 Junio C Hamano -# -# Read .git/FETCH_HEAD and make a human readable merge message -# by grouping branches and tags together to form a single line. - -use strict; - -my @src; -my %src; -sub andjoin { - my ($label, $labels, $stuff) = @_; - my $l = scalar @$stuff; - my $m = ''; - if ($l == 0) { - return (); - } - if ($l == 1) { - $m = "$label$stuff->[0]"; - } - else { - $m = ("$labels" . - join (', ', @{$stuff}[0..$l-2]) . - " and $stuff->[-1]"); - } - return ($m); -} - -sub repoconfig { - my ($val) = qx{git-repo-config --get merge.summary}; - return $val; -} - -sub current_branch { - my ($bra) = qx{git-symbolic-ref HEAD}; - chomp($bra); - $bra =~ s|^refs/heads/||; - if ($bra ne 'master') { - $bra = " into $bra"; - } else { - $bra = ""; - } - return $bra; -} - -sub shortlog { - my ($tip) = @_; - my @result; - foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) { - s/^[0-9a-f]{40}\s+//; - push @result, $_; - } - die "git-log failed\n" if $?; - return @result; -} - -my @origin = (); -while (<>) { - my ($bname, $tname, $gname, $src, $sha1, $origin); - chomp; - s/^([0-9a-f]*) //; - $sha1 = $1; - next if (/^not-for-merge/); - s/^ //; - if (s/ of (.*)$//) { - $src = $1; - } else { - # Pulling HEAD - $src = $_; - $_ = 'HEAD'; - } - if (! exists $src{$src}) { - push @src, $src; - $src{$src} = { - BRANCH => [], - TAG => [], - R_BRANCH => [], - GENERIC => [], - # &1 == has HEAD. - # &2 == has others. - HEAD_STATUS => 0, - }; - } - if (/^branch (.*)$/) { - $origin = $1; - push @{$src{$src}{BRANCH}}, $1; - $src{$src}{HEAD_STATUS} |= 2; - } - elsif (/^tag (.*)$/) { - $origin = $_; - 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; - } - else { - push @{$src{$src}{GENERIC}}, $_; - $src{$src}{HEAD_STATUS} |= 2; - $origin = $src; - } - if ($src eq '.' || $src eq $origin) { - $origin =~ s/^'(.*)'$/$1/; - push @origin, [$sha1, "$origin"]; - } - else { - push @origin, [$sha1, "$origin of $src"]; - } -} - -my @msg; -for my $src (@src) { - if ($src{$src}{HEAD_STATUS} == 1) { - # Only HEAD is fetched, nothing else. - push @msg, $src; - next; - } - my @this; - if ($src{$src}{HEAD_STATUS} == 3) { - # HEAD is fetched among others. - push @this, andjoin('', '', ['HEAD']); - } - 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 ", - $src{$src}{GENERIC}); - my $this = join(', ', @this); - if ($src ne '.') { - $this .= " of $src"; - } - push @msg, $this; -} - -my $into = current_branch(); - -print "Merge ", join("; ", @msg), $into, "\n"; - -if (!repoconfig) { - exit(0); -} - -# We limit the merge message to the latst 20 or so per each branch. -my $limit = 20; - -for (@origin) { - my ($sha1, $name) = @$_; - my @log = shortlog($sha1); - if ($limit + 1 <= @log) { - print "\n* $name: (" . scalar(@log) . " commits)\n"; - } - else { - print "\n* $name:\n"; - } - my $cnt = 0; - for my $log (@log) { - if ($limit < ++$cnt) { - print " ...\n"; - last; - } - print " $log"; - } -} diff --git a/git-merge.sh b/git-merge.sh index fc25f8dda0..24e3b507ef 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -103,7 +103,7 @@ do -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) case "$#,$1" in *,*=*) - strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;; + strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; 1,*) usage ;; *) diff --git a/git-pull.sh b/git-pull.sh index aa8c208092..076785c96b 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -24,7 +24,7 @@ do -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) case "$#,$1" in *,*=*) - strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;; + strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; 1,*) usage ;; *) diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 12d9d0cbc9..86b51abd21 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -9,7 +9,7 @@ while case "$#" in 0) break;; esac do case "$1" in --au=*|--aut=*|--auth=*|--autho=*|--author=*) - quilt_author=$(expr "$1" : '-[^=]*\(.*\)') + quilt_author=$(expr "z$1" : 'z-[^=]*\(.*\)') shift ;; @@ -26,7 +26,7 @@ do ;; --pa=*|--pat=*|--patc=*|--patch=*|--patche=*|--patches=*) - QUILT_PATCHES=$(expr "$1" : '-[^=]*\(.*\)') + QUILT_PATCHES=$(expr "z$1" : 'z-[^=]*\(.*\)') shift ;; diff --git a/git-rebase.sh b/git-rebase.sh index 9ad1c44d48..3945e06714 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -34,11 +34,6 @@ When you have resolved this problem run \"git rebase --continue\". If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " - -MRESOLVEMSG=" -When you have resolved this problem run \"git rebase --continue\". -To restore the original branch and stop rebasing run \"git rebase --abort\". -" unset newbase strategy=recursive do_merge= @@ -54,13 +49,18 @@ continue_merge () { then echo "You still have unmerged paths in your index" echo "did you forget update-index?" - die "$MRESOLVEMSG" + die "$RESOLVEMSG" fi if test -n "`git-diff-index HEAD`" then + if ! git-commit -C "`cat $dotest/current`" + then + echo "Commit failed, please do not call \"git commit\"" + echo "directly, but instead do one of the following: " + die "$RESOLVEMSG" + fi printf "Committed: %0${prec}d" $msgnum - git-commit -C "`cat $dotest/current`" else printf "Already applied: %0${prec}d" $msgnum fi @@ -87,11 +87,11 @@ call_merge () { ;; 1) test -d "$GIT_DIR/rr-cache" && git-rerere - die "$MRESOLVEMSG" + die "$RESOLVEMSG" ;; 2) echo "Strategy: $rv $strategy failed, try another" 1>&2 - die "$MRESOLVEMSG" + die "$RESOLVEMSG" ;; *) die "Unknown exit code ($rv) from command:" \ @@ -179,7 +179,7 @@ do -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) case "$#,$1" in *,*=*) - strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;; + strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; 1,*) usage ;; *) diff --git a/git-repack.sh b/git-repack.sh index eb75c8cda9..640ad8d90b 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -54,9 +54,24 @@ else fi mkdir -p "$PACKDIR" || exit - mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" && - mv .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" || - exit + for sfx in pack idx + do + if test -f "$PACKDIR/pack-$name.$sfx" + then + mv -f "$PACKDIR/pack-$name.$sfx" \ + "$PACKDIR/old-pack-$name.$sfx" + fi + done && + mv -f .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" && + mv -f .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" && + test -f "$PACKDIR/pack-$name.pack" && + test -f "$PACKDIR/pack-$name.idx" || { + echo >&2 "Couldn't replace the existing pack with updated one." + echo >&2 "The original set of packs have been saved as" + echo >&2 "old-pack-$name.{pack,idx} in $PACKDIR." + exit 1 + } + rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx" fi if test "$remove_redundant" = t diff --git a/git-send-email.perl b/git-send-email.perl index c5d9e73351..b04b8f40e9 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -22,6 +22,17 @@ use Term::ReadLine; use Getopt::Long; use Data::Dumper; +package FakeTerm; +sub new { + my ($class, $reason) = @_; + return bless \$reason, shift; +} +sub readline { + my $self = shift; + die "Cannot use readline on FakeTerm: $$self"; +} +package main; + # most mail servers generate the Date: header, but not all... $ENV{LC_ALL} = 'C'; use POSIX qw/strftime/; @@ -46,7 +57,12 @@ my $smtp_server; # Example reply to: #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; -my $term = new Term::ReadLine 'git-send-email'; +my $term = eval { + new Term::ReadLine 'git-send-email'; +}; +if ($@) { + $term = new FakeTerm "$@: going non-interactive"; +} # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: diff --git a/git-svnimport.perl b/git-svnimport.perl index 38ac732ca9..26dc454795 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -534,7 +534,7 @@ sub commit { my($author_name,$author_email,$dest); my(@old,@new,@parents); - if (not defined $author) { + if (not defined $author or $author eq "") { $author_name = $author_email = "unknown"; } elsif (defined $users_file) { die "User $author is not listed in $users_file\n" @@ -16,7 +16,8 @@ static void prepend_to_path(const char *dir, int len) { - char *path, *old_path = getenv("PATH"); + const char *old_path = getenv("PATH"); + char *path; int path_len = len; if (!old_path) @@ -99,7 +100,7 @@ static int split_cmdline(char *cmdline, const char ***argv) static int handle_alias(int *argcp, const char ***argv) { - int nongit = 0, ret = 0; + int nongit = 0, ret = 0, saved_errno = errno; const char *subdir; subdir = setup_git_directory_gently(&nongit); @@ -137,6 +138,8 @@ static int handle_alias(int *argcp, const char ***argv) if (subdir) chdir(subdir); + errno = saved_errno; + return ret; } @@ -184,7 +187,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mailinfo", cmd_mailinfo }, { "stripspace", cmd_stripspace }, { "update-index", cmd_update_index }, - { "update-ref", cmd_update_ref } + { "update-ref", cmd_update_ref }, + { "fmt-merge-msg", cmd_fmt_merge_msg } }; int i; @@ -206,7 +210,6 @@ int main(int argc, const char **argv, char **envp) { const char *cmd = argv[0]; char *slash = strrchr(cmd, '/'); - char git_command[PATH_MAX + 1]; const char *exec_path = NULL; int done_alias = 0; @@ -313,7 +316,7 @@ int main(int argc, const char **argv, char **envp) cmd_usage(0, exec_path, "'%s' is not a git-command", cmd); fprintf(stderr, "Failed to run command '%s': %s\n", - git_command, strerror(errno)); + cmd, strerror(errno)); return 1; } diff --git a/http-push.c b/http-push.c index 3c89a17496..f761584d7e 100644 --- a/http-push.c +++ b/http-push.c @@ -492,7 +492,7 @@ static void start_put(struct transfer_request *request) /* Set it up */ memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, Z_BEST_COMPRESSION); + deflateInit(&stream, zlib_compression_level); size = deflateBound(&stream, len + hdrlen); request->buffer.buffer = xmalloc(size); @@ -1274,7 +1274,7 @@ xml_cdata(void *userData, const XML_Char *s, int len) strlcpy(ctx->cdata, s, len + 1); } -static struct remote_lock *lock_remote(char *path, long timeout) +static struct remote_lock *lock_remote(const char *path, long timeout) { struct active_request_slot *slot; struct slot_results results; @@ -2130,7 +2130,7 @@ static int remote_exists(const char *path) return -1; } -static void fetch_symref(char *path, char **symref, unsigned char *sha1) +static void fetch_symref(const char *path, char **symref, unsigned char *sha1) { char *url; struct buffer buffer; diff --git a/imap-send.c b/imap-send.c index 94e39cd94c..65c71c602d 100644 --- a/imap-send.c +++ b/imap-send.c @@ -242,7 +242,7 @@ socket_read( Socket_t *sock, char *buf, int len ) } static int -socket_write( Socket_t *sock, char *buf, int len ) +socket_write( Socket_t *sock, const char *buf, int len ) { int n = write( sock->fd, buf, len ); if (n != len) { diff --git a/name-rev.c b/name-rev.c index 3a5ac35d16..6a23f2d8a2 100644 --- a/name-rev.c +++ b/name-rev.c @@ -234,12 +234,15 @@ int main(int argc, char **argv) fwrite(p_start, p - p_start, 1, stdout); } } else if (all) { - int i; + int i, max; - for (i = 0; i < obj_allocs; i++) - if (objs[i]) - printf("%s %s\n", sha1_to_hex(objs[i]->sha1), - get_rev_name(objs[i])); + max = get_max_object_index(); + for (i = 0; i < max; i++) { + struct object * obj = get_indexed_object(i); + if (!obj) + continue; + printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj)); + } } else { int i; for (i = 0; i < revs.nr; i++) @@ -5,79 +5,97 @@ #include "commit.h" #include "tag.h" -struct object **objs; -static int nr_objs; -int obj_allocs; +static struct object **obj_hash; +static int nr_objs, obj_hash_size; + +unsigned int get_max_object_index(void) +{ + return obj_hash_size; +} + +struct object *get_indexed_object(unsigned int idx) +{ + return obj_hash[idx]; +} const char *type_names[] = { "none", "blob", "tree", "commit", "bad" }; +static unsigned int hash_obj(struct object *obj, unsigned int n) +{ + unsigned int hash = *(unsigned int *)obj->sha1; + return hash % n; +} + +static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size) +{ + int j = hash_obj(obj, size); + + while (hash[j]) { + j++; + if (j >= size) + j = 0; + } + hash[j] = obj; +} + static int hashtable_index(const unsigned char *sha1) { unsigned int i; memcpy(&i, sha1, sizeof(unsigned int)); - return (int)(i % obj_allocs); + return (int)(i % obj_hash_size); } -static int find_object(const unsigned char *sha1) +struct object *lookup_object(const unsigned char *sha1) { int i; + struct object *obj; - if (!objs) - return -1; + if (!obj_hash) + return NULL; i = hashtable_index(sha1); - while (objs[i]) { - if (memcmp(sha1, objs[i]->sha1, 20) == 0) - return i; + while ((obj = obj_hash[i]) != NULL) { + if (!memcmp(sha1, obj->sha1, 20)) + break; i++; - if (i == obj_allocs) + if (i == obj_hash_size) i = 0; } - return -1 - i; + return obj; } -struct object *lookup_object(const unsigned char *sha1) +static void grow_object_hash(void) { - int pos = find_object(sha1); - if (pos >= 0) - return objs[pos]; - return NULL; + int i; + int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size; + struct object **new_hash; + + new_hash = calloc(new_hash_size, sizeof(struct object *)); + for (i = 0; i < obj_hash_size; i++) { + struct object *obj = obj_hash[i]; + if (!obj) + continue; + insert_obj_hash(obj, new_hash, new_hash_size); + } + free(obj_hash); + obj_hash = new_hash; + obj_hash_size = new_hash_size; } void created_object(const unsigned char *sha1, struct object *obj) { - int pos; - obj->parsed = 0; - memcpy(obj->sha1, sha1, 20); - obj->type = TYPE_NONE; obj->used = 0; + obj->type = TYPE_NONE; + obj->flags = 0; + memcpy(obj->sha1, sha1, 20); - if (obj_allocs - 1 <= nr_objs * 2) { - int i, count = obj_allocs; - obj_allocs = (obj_allocs < 32 ? 32 : 2 * obj_allocs); - objs = xrealloc(objs, obj_allocs * sizeof(struct object *)); - memset(objs + count, 0, (obj_allocs - count) - * sizeof(struct object *)); - for (i = 0; i < obj_allocs; i++) - if (objs[i]) { - int j = find_object(objs[i]->sha1); - if (j != i) { - j = -1 - j; - objs[j] = objs[i]; - objs[i] = NULL; - } - } - } - - pos = find_object(sha1); - if (pos >= 0) - die("Inserting %s twice\n", sha1_to_hex(sha1)); - pos = -pos-1; + if (obj_hash_size - 1 <= nr_objs * 2) + grow_object_hash(); - objs[pos] = obj; + insert_obj_hash(obj, obj_hash, obj_hash_size); nr_objs++; } @@ -40,10 +40,11 @@ struct object { }; extern int track_object_refs; -extern int obj_allocs; -extern struct object **objs; extern const char *type_names[]; +extern unsigned int get_max_object_index(void); +extern struct object *get_indexed_object(unsigned int); + static inline const char *typename(unsigned int type) { return type_names[type > TYPE_TAG ? TYPE_BAD : type]; diff --git a/pack-objects.c b/pack-objects.c index bed2497b79..b486ea528a 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -970,11 +970,12 @@ struct unpacked { * one. */ static int try_delta(struct unpacked *trg, struct unpacked *src, - struct delta_index *src_index, unsigned max_depth) + unsigned max_depth) { struct object_entry *trg_entry = trg->entry; struct object_entry *src_entry = src->entry; - unsigned long size, src_size, delta_size, sizediff, max_size; + unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz; + char type[10]; void *delta_buf; /* Don't bother doing diffs between different types */ @@ -988,6 +989,14 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, return -1; /* + * We do not bother to try a delta that we discarded + * on an earlier try, but only when reusing delta data. + */ + if (!no_reuse_delta && trg_entry->in_pack && + trg_entry->in_pack == src_entry->in_pack) + return 0; + + /* * If the current object is at pack edge, take the depth the * objects that depend on the current object into account -- * otherwise they would become too deep. @@ -1001,19 +1010,38 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, return 0; /* Now some size filtering heuristics. */ - size = trg_entry->size; - max_size = size/2 - 20; + trg_size = trg_entry->size; + max_size = trg_size/2 - 20; max_size = max_size * (max_depth - src_entry->depth) / max_depth; if (max_size == 0) return 0; if (trg_entry->delta && trg_entry->delta_size <= max_size) max_size = trg_entry->delta_size-1; src_size = src_entry->size; - sizediff = src_size < size ? size - src_size : 0; + sizediff = src_size < trg_size ? trg_size - src_size : 0; if (sizediff >= max_size) return 0; - delta_buf = create_delta(src_index, trg->data, size, &delta_size, max_size); + /* Load data if not already done */ + if (!trg->data) { + trg->data = read_sha1_file(trg_entry->sha1, type, &sz); + if (sz != trg_size) + die("object %s inconsistent object length (%lu vs %lu)", + sha1_to_hex(trg_entry->sha1), sz, trg_size); + } + if (!src->data) { + src->data = read_sha1_file(src_entry->sha1, type, &sz); + if (sz != src_size) + die("object %s inconsistent object length (%lu vs %lu)", + sha1_to_hex(src_entry->sha1), sz, src_size); + } + if (!src->index) { + src->index = create_delta_index(src->data, src_size); + if (!src->index) + die("out of memory"); + } + + delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); if (!delta_buf) return 0; @@ -1046,8 +1074,6 @@ static void find_deltas(struct object_entry **list, int window, int depth) while (--i >= 0) { struct object_entry *entry = list[i]; struct unpacked *n = array + idx; - unsigned long size; - char type[10]; int j; if (!entry->preferred_base) @@ -1074,11 +1100,8 @@ static void find_deltas(struct object_entry **list, int window, int depth) free_delta_index(n->index); n->index = NULL; free(n->data); + n->data = NULL; n->entry = entry; - n->data = read_sha1_file(entry->sha1, type, &size); - if (size != entry->size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(entry->sha1), size, entry->size); j = window; while (--j > 0) { @@ -1089,7 +1112,7 @@ static void find_deltas(struct object_entry **list, int window, int depth) m = array + other_idx; if (!m->entry) break; - if (try_delta(n, m, m->index, depth) < 0) + if (try_delta(n, m, depth) < 0) break; } /* if we made n a delta, and if n is already at max @@ -1099,10 +1122,6 @@ static void find_deltas(struct object_entry **list, int window, int depth) if (entry->delta && depth <= entry->depth) continue; - n->index = create_delta_index(n->data, size); - if (!n->index) - die("out of memory"); - idx++; if (idx >= window) idx = 0; diff --git a/peek-remote.c b/peek-remote.c index a90cf22069..2b30980b04 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -7,11 +7,11 @@ static const char peek_remote_usage[] = "git-peek-remote [--exec=upload-pack] [host:]directory"; static const char *exec = "git-upload-pack"; -static int peek_remote(int fd[2]) +static int peek_remote(int fd[2], unsigned flags) { struct ref *ref; - get_remote_heads(fd[0], &ref, 0, NULL, 0); + get_remote_heads(fd[0], &ref, 0, NULL, flags); packet_flush(fd[1]); while (ref) { @@ -28,6 +28,7 @@ int main(int argc, char **argv) int fd[2]; pid_t pid; int nongit = 0; + unsigned flags = 0; setup_git_directory_gently(&nongit); @@ -35,22 +36,35 @@ int main(int argc, char **argv) char *arg = argv[i]; if (*arg == '-') { - if (!strncmp("--exec=", arg, 7)) + if (!strncmp("--exec=", arg, 7)) { exec = arg + 7; - else - usage(peek_remote_usage); - continue; + continue; + } + if (!strcmp("--tags", arg)) { + flags |= REF_TAGS; + continue; + } + if (!strcmp("--heads", arg)) { + flags |= REF_HEADS; + continue; + } + if (!strcmp("--refs", arg)) { + flags |= REF_NORMAL; + continue; + } + usage(peek_remote_usage); } dest = arg; break; } + if (!dest || i != argc - 1) usage(peek_remote_usage); pid = git_connect(fd, dest, exec); if (pid < 0) return 1; - ret = peek_remote(fd); + ret = peek_remote(fd, flags); close(fd[0]); close(fd[1]); finish_connect(pid); @@ -13,7 +13,7 @@ * a!b ==> a'\!'b ==> 'a'\!'b' */ #undef EMIT -#define EMIT(x) ( (++len < n) && (*bp++ = (x)) ) +#define EMIT(x) do { if (++len < n) *bp++ = (x); } while(0) static inline int need_bs_quote(char c) { diff --git a/revision.c b/revision.c index ae4ca82003..ab89c22417 100644 --- a/revision.c +++ b/revision.c @@ -280,7 +280,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1) static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) { struct commit_list **pp, *parent; - int tree_changed = 0; + int tree_changed = 0, tree_same = 0; if (!commit->tree) return; @@ -298,6 +298,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) parse_commit(p); switch (rev_compare_tree(revs, p->tree, commit->tree)) { case REV_TREE_SAME: + tree_same = 1; if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) { /* Even if a merge with an uninteresting * side branch brought the entire change @@ -334,7 +335,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) } die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1)); } - if (tree_changed) + if (tree_changed && !tree_same) commit->object.flags |= TREECHANGE; } @@ -895,6 +896,8 @@ static int rewrite_one(struct rev_info *revs, struct commit **pp) struct commit *p = *pp; if (!revs->limited) add_parents_to_list(revs, p, &revs->commits); + if (p->parents && p->parents->next) + return 0; if (p->object.flags & (TREECHANGE | UNINTERESTING)) return 0; if (!p->parents) @@ -987,8 +990,15 @@ struct commit *get_revision(struct rev_info *revs) commit->parents && commit->parents->next) continue; if (revs->prune_fn && revs->dense) { - if (!(commit->object.flags & TREECHANGE)) - continue; + /* Commit without changes? */ + if (!(commit->object.flags & TREECHANGE)) { + /* drop merges unless we want parenthood */ + if (!revs->parents) + continue; + /* non-merge - always ignore it */ + if (!commit->parents || !commit->parents->next) + continue; + } if (revs->parents) rewrite_parents(revs, commit); } diff --git a/send-pack.c b/send-pack.c index af93b11f23..4019a4b981 100644 --- a/send-pack.c +++ b/send-pack.c @@ -239,7 +239,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) int expect_status_report = 0; /* No funny business with the matcher */ - remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1); + remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL); get_local_heads(); /* Does the other end support the reporting? */ diff --git a/sha1_file.c b/sha1_file.c index c80528b506..bc35808440 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -343,7 +343,7 @@ static void read_info_alternates(const char * relative_base, int depth) void prepare_alt_odb(void) { - char *alt; + const char *alt; alt = getenv(ALTERNATE_DB_ENVIRONMENT); if (!alt) alt = ""; @@ -1458,7 +1458,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha /* Set it up */ memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, Z_BEST_COMPRESSION); + deflateInit(&stream, zlib_compression_level); size = deflateBound(&stream, len+hdrlen); compressed = xmalloc(size); @@ -1511,7 +1511,7 @@ static void *repack_object(const unsigned char *sha1, unsigned long *objsize) /* Set it up */ memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, Z_BEST_COMPRESSION); + deflateInit(&stream, zlib_compression_level); size = deflateBound(&stream, len + hdrlen); buf = xmalloc(size); @@ -73,6 +73,7 @@ First digit tells the family: 4 - the diff commands 5 - the pull and exporting commands 6 - the revision tree commands (even e.g. merge-base) + 7 - the porcelainish commands concerning the working tree Second digit tells the particular command we are testing. diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh new file mode 100755 index 0000000000..4795872a77 --- /dev/null +++ b/t/t4014-format-patch.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='Format-patch skipping already incorporated patches' + +. ./test-lib.sh + +test_expect_success setup ' + + for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file && + git add file && + git commit -m Initial && + git checkout -b side && + + for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file && + git update-index file && + git commit -m "Side change #1" && + + for i in D E F; do echo "$i"; done >>file && + git update-index file && + git commit -m "Side change #2" && + git tag C2 && + + for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file && + git update-index file && + git commit -m "Side change #3" && + + git checkout master && + git diff-tree -p C2 | git apply --index && + git commit -m "Master accepts moral equivalent of #2" + +' + +test_expect_success "format-patch --ignore-if-in-upstream" ' + + git format-patch --stdout master..side >patch0 && + cnt=`grep "^From " patch0 | wc -l` && + test $cnt = 3 + +' + +test_expect_success "format-patch --ignore-if-in-upstream" ' + + git format-patch --stdout \ + --ignore-if-in-upstream master..side >patch1 && + cnt=`grep "^From " patch1 | wc -l` && + test $cnt = 2 + +' + +test_expect_success "format-patch result applies" ' + + git checkout -b rebuild-0 master && + git am -3 patch0 && + cnt=`git rev-list master.. | wc -l` && + test $cnt = 2 +' + +test_expect_success "format-patch --ignore-if-in-upstream result applies" ' + + git checkout -b rebuild-1 master && + git am -3 patch1 && + cnt=`git rev-list master.. | wc -l` && + test $cnt = 2 +' + +test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh new file mode 100755 index 0000000000..b64e8b7d77 --- /dev/null +++ b/t/t7201-co.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='git-checkout tests.' + +. ./test-lib.sh + +fill () { + for i + do + echo "$i" + done +} + +test_expect_success setup ' + + fill 1 2 3 4 5 >one && + fill a b c d e >two && + git add one two && + git commit -m "Initial A one, A two" && + + git checkout -b side && + fill 1 2 3 >one && + fill A B C D E >three && + rm -f two && + git update-index --add --remove one two three && + git commit -m "Side M one, D two, A three" && + + git checkout master +' + +test_expect_success "checkout with dirty tree without -m" ' + + fill 0 1 2 3 4 5 >one && + if git checkout side + then + echo Not happy + false + else + echo "happy - failed correctly" + fi + +' + +test_expect_success "checkout -m with dirty tree" ' + + git checkout -f master && + git clean && + + fill 0 1 2 3 4 5 >one && + git checkout -m side && + + fill " master" "* side" >expect.branch && + git branch >current.branch && + diff expect.branch current.branch && + + fill "M one" "A three" "D two" >expect.master && + git diff --name-status master >current.master && + diff expect.master current.master && + + fill "M one" >expect.side && + git diff --name-status side >current.side && + diff expect.side current.side && + + : >expect.index && + git diff --cached >current.index && + diff expect.index current.index +' + +test_done diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh index 2496397da3..3a6490e8f8 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -6,4 +6,10 @@ test_description='git-annotate' PROG='git annotate' . ../annotate-tests.sh +test_expect_success \ + 'Annotating an old revision works' \ + '[ $(git annotate file master | awk "{print \$3}" | grep -c "^A$") -eq 2 ] && \ + [ $(git annotate file master | awk "{print \$3}" | grep -c "^B$") -eq 2 ]' + + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index a61da1efbd..e9ea33c18d 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -25,10 +25,13 @@ test_expect_success \ git add fake.sendmail GIT_AUTHOR_NAME="A" git commit -a -m "Second."' -test_expect_success \ - 'Extract patches and send' \ - 'git format-patch -n HEAD^1 - git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" ./0001*txt' +test_expect_success 'Extract patches' ' + patches=`git format-patch -n HEAD^1` +' + +test_expect_success 'Send patches' ' + git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors +' cat >expected <<\EOF !nobody@example.com! diff --git a/upload-pack.c b/upload-pack.c index 7b86f6965b..b18eb9ba0d 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -1,3 +1,6 @@ +#include <signal.h> +#include <sys/wait.h> +#include <sys/poll.h> #include "cache.h" #include "refs.h" #include "pkt-line.h" @@ -5,9 +8,6 @@ #include "object.h" #include "commit.h" #include "exec_cmd.h" -#include <signal.h> -#include <sys/poll.h> -#include <sys/wait.h> static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>"; @@ -95,8 +95,8 @@ static void create_pack_file(void) int i; int args; const char **argv; + const char **p; char *buf; - char **p; if (create_full_pack) { args = 10; @@ -441,7 +441,7 @@ static int receive_needs(void) static int send_ref(const char *refname, const unsigned char *sha1) { - static char *capabilities = "multi_ack thin-pack side-band"; + static const char *capabilities = "multi_ack thin-pack side-band"; struct object *o = parse_object(sha1); if (!o) |