diff options
41 files changed, 706 insertions, 259 deletions
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 2c3a4c433..400cbb3b1 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -84,3 +84,64 @@ all parents. include::diff-generate-patch.txt[] + + +other diff formats +------------------ + +The `--summary` option describes newly added, deleted, renamed and +copied files. The `--stat` option adds diffstat(1) graph to the +output. These options can be combined with other options, such as +`-p`, and are meant for human consumption. + +When showing a change that involves a rename or a copy, `--stat` output +formats the pathnames compactly by combining common prefix and suffix of +the pathnames. For example, a change that moves `arch/i386/Makefile` to +`arch/x86/Makefile` while modifying 4 lines will be shown like this: + +------------------------------------ +arch/{i386 => x86}/Makefile | 4 +-- +------------------------------------ + +The `--numstat` option gives the diffstat(1) information but is designed +for easier machine consumption. An entry in `--numstat` output looks +like this: + +---------------------------------------- +1 2 README +3 1 arch/{i386 => x86}/Makefile +---------------------------------------- + +That is, from left to right: + +. the number of added lines; +. a tab; +. the number of deleted lines; +. a tab; +. pathname (possibly with rename/copy information); +. a newline. + +When `-z` output option is in effect, the output is formatted this way: + +---------------------------------------- +1 2 README NUL +3 1 NUL arch/i386/Makefile NUL arch/x86/Makefile NUL +---------------------------------------- + +That is: + +. the number of added lines; +. a tab; +. the number of deleted lines; +. a tab; +. a NUL (only exists if renamed/copied); +. pathname in preimage; +. a NUL (only exists if renamed/copied); +. pathname in postimage (only exists if renamed/copied); +. a NUL. + +The extra `NUL` before the preimage path in renamed case is to allow +scripts that read the output to tell if the current record being read is +a single-path record or a rename/copy record without reading ahead. +After reading added and deleted lines, reading up to `NUL` would yield +the pathname, but if that is `NUL`, the record will show two paths. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index d0154bbc0..5d22b7b58 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -175,19 +175,19 @@ endif::git-format-patch[] Shorthand for "--text". --ignore-space-at-eol:: - Ignore changes in white spaces at EOL. + Ignore changes in whitespace at EOL. --ignore-space-change:: - Ignore changes in amount of white space. This ignores white - space at line end, and consider all other sequences of one or - more white space characters to be equivalent. + Ignore changes in amount of whitespace. This ignores whitespace + at line end, and considers all other sequences of one or + more whitespace characters to be equivalent. -b:: Shorthand for "--ignore-space-change". --ignore-all-space:: - Ignore white space when comparing lines. This ignores - difference even if one line has white space where the other + Ignore whitespace when comparing lines. This ignores + differences even if one line has whitespace where the other line has none. -w:: diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index bf94cd43b..721ca998c 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -65,7 +65,7 @@ OPTIONS operation to a subset of the working tree. See ``Interactive mode'' for details. --p, \--patch: +-p, \--patch:: Similar to Interactive mode but the initial command loop is bypassed and the 'patch' subcommand is invoked using each of the specified filepatterns before exiting. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index bae3e7b90..9ec38f92b 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -119,7 +119,7 @@ discouraged. --no-add:: When applying a patch, ignore additions made by the - patch. This can be used to extract common part between + patch. This can be used to extract the common part between two files by first running `diff` on them and applying the result with this option, which would apply the deletion part but not addition part. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 2220ef6ea..e14720b73 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -8,8 +8,8 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- [verse] -git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] -git-shortlog [-n|--numbered] [-s|--summary] [<committish>...] +git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] [-e] +git-shortlog [-n|--numbered] [-s|--summary] [-e|--email] [<committish>...] DESCRIPTION ----------- @@ -32,6 +32,9 @@ OPTIONS -s, \--summary:: Suppress commit description and provide a commit count summary only. +-e, \--email:: + Show the email address of each author. + FILES ----- diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index fc1874424..65f55e4ce 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -244,8 +244,7 @@ This commit is referred to as a "merge commit", or sometimes just a The unique identifier of an <<def_object,object>>. The <<def_hash,hash>> of the object's contents using the Secure Hash Algorithm 1 and usually represented by the 40 character hexadecimal encoding of - the <<def_hash,hash>> of the object (possibly followed by - a white space). + the <<def_hash,hash>> of the object. [[def_object_type]]object type:: One of the identifiers @@ -1033,7 +1033,7 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install - $(MAKE) -C perl prefix='$(prefix_SQ)' install + $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui install diff --git a/builtin-blame.c b/builtin-blame.c index c158d319d..5466d01f9 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -130,6 +130,14 @@ static void origin_decref(struct origin *o) } } +static void drop_origin_blob(struct origin *o) +{ + if (o->file.ptr) { + free(o->file.ptr); + o->file.ptr = NULL; + } +} + /* * Each group of lines is described by a blame_entry; it can be split * as we pass blame to the parents. They form a linked list in the @@ -380,6 +388,7 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); + diff_tree_release_paths(&diff_opts); if (porigin) { /* * Create a freestanding copy that is not part of @@ -436,6 +445,7 @@ static struct origin *find_rename(struct scoreboard *sb, } } diff_flush(&diff_opts); + diff_tree_release_paths(&diff_opts); return porigin; } @@ -1157,7 +1167,7 @@ static int find_copy_in_parent(struct scoreboard *sb, } } diff_flush(&diff_opts); - + diff_tree_release_paths(&diff_opts); return retval; } @@ -1274,8 +1284,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) } finish: - for (i = 0; i < MAXPARENT; i++) - origin_decref(parent_origin[i]); + for (i = 0; i < MAXPARENT; i++) { + if (parent_origin[i]) { + drop_origin_blob(parent_origin[i]); + origin_decref(parent_origin[i]); + } + } + drop_origin_blob(origin); } /* diff --git a/builtin-commit.c b/builtin-commit.c index b6b81d531..9cb7589ac 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -660,12 +660,16 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; rev.commit_format = get_commit_format("format:%h: %s"); - rev.always_show_header = 1; + rev.always_show_header = 0; printf("Created %scommit ", initial_commit ? "initial " : ""); - log_tree_commit(&rev, commit); - printf("\n"); + if (!log_tree_commit(&rev, commit)) { + struct strbuf buf = STRBUF_INIT; + format_commit_message(commit, "%h: %s", &buf); + printf("%s\n", buf.buf); + strbuf_release(&buf); + } } int git_commit_config(const char *k, const char *v) diff --git a/builtin-diff.c b/builtin-diff.c index 1b615991e..55fb84c73 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -176,18 +176,6 @@ static int builtin_diff_combined(struct rev_info *revs, return 0; } -void add_head(struct rev_info *revs) -{ - unsigned char sha1[20]; - struct object *obj; - if (get_sha1("HEAD", sha1)) - return; - obj = parse_object(sha1); - if (!obj) - return; - add_pending_object(revs, obj, "HEAD"); -} - static void refresh_index_quietly(void) { struct lock_file *lock_file; @@ -272,7 +260,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--")) break; else if (!strcmp(arg, "--cached")) { - add_head(&rev); + add_head_to_pending(&rev); if (!rev.pending.nr) die("No HEAD commit to compare with (yet)"); break; diff --git a/builtin-fast-export.c b/builtin-fast-export.c index 2136aadfd..ef27eee71 100755 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -103,7 +103,7 @@ static void handle_object(const unsigned char *sha1) mark_object(object); printf("blob\nmark :%d\ndata %lu\n", last_idnum, size); - if (fwrite(buf, size, 1, stdout) != 1) + if (size && fwrite(buf, size, 1, stdout) != 1) die ("Could not write blob %s", sha1_to_hex(sha1)); printf("\n"); diff --git a/builtin-log.c b/builtin-log.c index b6a11220e..cc3cc9069 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -18,9 +18,6 @@ static int default_show_root = 1; static const char *fmt_patch_subject_prefix = "PATCH"; -/* this is in builtin-diff.c */ -void add_head(struct rev_info *revs); - static void add_name_decoration(const char *prefix, const char *name, struct object *obj) { int plen = strlen(prefix); @@ -746,7 +743,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * does not have. */ rev.pending.objects[0].item->flags |= UNINTERESTING; - add_head(&rev); + add_head_to_pending(&rev); } /* * Otherwise, it is "format-patch -22 HEAD", and/or diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 250dc56ab..7dd0d7f82 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1709,6 +1709,16 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, list++; sub_size--; } + if (!sub_size) { + /* + * It is possible for some "paths" to have + * so many objects that no hash boundary + * might be found. Let's just steal the + * exact half in that case. + */ + sub_size = victim->remaining / 2; + list -= sub_size; + } target->list = list; victim->list_size -= sub_size; victim->remaining -= sub_size; diff --git a/builtin-reset.c b/builtin-reset.c index 4c61025aa..713c2d534 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -158,6 +158,7 @@ static int read_from_tree(const char *prefix, const char **argv, return 1; diffcore_std(&opt); diff_flush(&opt); + diff_tree_release_paths(&opt); if (!index_was_discarded) /* The index is still clobbered from do_diff_cache() */ diff --git a/builtin-shortlog.c b/builtin-shortlog.c index b9cc13444..3d8d7094a 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -11,6 +11,7 @@ static const char shortlog_usage[] = "git-shortlog [-n] [-s] [<commit-id>... ]"; static char *common_repo_prefix; +static int email; static int compare_by_number(const void *a1, const void *a2) { @@ -57,6 +58,14 @@ static void insert_one_record(struct path_list *list, len--; namebuf[len] = '\0'; } + else + len = strlen(namebuf); + + if (email) { + size_t room = sizeof(namebuf) - len - 1; + int maillen = eoemail - boemail + 1; + snprintf(namebuf + len, room, " %.*s", maillen, boemail); + } buffer = xstrdup(namebuf); item = path_list_insert(buffer, list); @@ -219,6 +228,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "-s") || !strcmp(argv[1], "--summary")) summary = 1; + else if (!strcmp(argv[1], "-e") || + !strcmp(argv[1], "--email")) + email = 1; else if (!prefixcmp(argv[1], "-w")) { wrap_lines = 1; parse_wrap_args(argv[1], &in1, &in2, &wrap); @@ -237,9 +249,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) read_mailmap(&mailmap, ".mailmap", &common_repo_prefix); + /* assume HEAD if from a tty */ + if (!rev.pending.nr && isatty(0)) + add_head_to_pending(&rev); if (rev.pending.nr == 0) { - if (isatty(0)) - fprintf(stderr, "(reading log to summarize from standard input)\n"); read_from_stdin(&list); } else @@ -253,7 +266,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) struct path_list *onelines = list.items[i].util; if (summary) { - printf("%s: %d\n", list.items[i].path, onelines->nr); + printf("%6d\t%s\n", onelines->nr, list.items[i].path); } else { printf("%s (%d):\n", list.items[i].path, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) { diff --git a/builtin-tag.c b/builtin-tag.c index 517419fd3..274901a40 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -236,14 +236,18 @@ static const char tag_template[] = "# Write a tag message\n" "#\n"; +static void set_signingkey(const char *value) +{ + if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey)) + die("signing key value too long (%.10s...)", value); +} + static int git_tag_config(const char *var, const char *value) { if (!strcmp(var, "user.signingkey")) { if (!value) die("user.signingkey without value"); - if (strlcpy(signingkey, value, sizeof(signingkey)) - >= sizeof(signingkey)) - die("user.signingkey value too long"); + set_signingkey(value); return 0; } @@ -396,6 +400,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, options, git_tag_usage, 0); + if (keyid) { + sign = 1; + set_signingkey(keyid); + } if (sign) annotate = 1; @@ -608,7 +608,7 @@ extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char /* pager.c */ extern void setup_pager(void); extern char *pager_program; -extern int pager_in_use; +extern int pager_in_use(void); extern int pager_use_color; extern char *editor_program; @@ -135,7 +135,7 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) auto_color: if (stdout_is_tty < 0) stdout_is_tty = isatty(1); - if (stdout_is_tty || (pager_in_use && pager_use_color)) { + if (stdout_is_tty || (pager_in_use() && pager_use_color)) { char *term = getenv("TERM"); if (term && strcmp(term, "dumb")) return 1; diff --git a/config.mak.in b/config.mak.in index 7d5df9bf3..15fb26cb9 100644 --- a/config.mak.in +++ b/config.mak.in @@ -23,6 +23,7 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH +ASCIIDOC8=@ASCIIDOC8@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ diff --git a/configure.ac b/configure.ac index dd4b4eb98..6f641e32f 100644 --- a/configure.ac +++ b/configure.ac @@ -122,6 +122,27 @@ if test -z "$NO_TCLTK"; then AC_SUBST(TCLTK_PATH) fi fi +AC_CHECK_PROGS(ASCIIDOC, [asciidoc]) +if test -n "$ASCIIDOC"; then + AC_MSG_CHECKING([for asciidoc version]) + asciidoc_version=`$ASCIIDOC --version 2>&1` + case "${asciidoc_version}" in + asciidoc' '8*) + ASCIIDOC8=YesPlease + AC_MSG_RESULT([${asciidoc_version} > 7]) + ;; + asciidoc' '7*) + ASCIIDOC8= + AC_MSG_RESULT([${asciidoc_version}]) + ;; + *) + ASCIIDOC8= + AC_MSG_RESULT([${asciidoc_version} (unknown)]) + ;; + esac +fi +AC_SUBST(ASCIIDOC8) + ## Checks for libraries. AC_MSG_NOTICE([CHECKS for libraries]) diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index e147da059..28a4899a0 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -49,6 +49,7 @@ (eval-when-compile (require 'cl)) (require 'ewoc) (require 'log-edit) +(require 'easymenu) ;;;; Customizations @@ -1297,7 +1298,47 @@ Return the list of files that haven't been handled." (define-key toggle-map "i" 'git-toggle-show-ignored) (define-key toggle-map "k" 'git-toggle-show-unknown) (define-key toggle-map "m" 'git-toggle-all-marks) - (setq git-status-mode-map map))) + (setq git-status-mode-map map)) + (easy-menu-define git-menu git-status-mode-map + "Git Menu" + `("Git" + ["Refresh" git-refresh-status t] + ["Commit" git-commit-file t] + ("Merge" + ["Next Unmerged File" git-next-unmerged-file t] + ["Prev Unmerged File" git-prev-unmerged-file t] + ["Mark as Resolved" git-resolve-file t] + ["Interactive Merge File" git-find-file-imerge t] + ["Diff Against Common Base File" git-diff-file-base t] + ["Diff Combined" git-diff-file-combined t] + ["Diff Against Merge Head" git-diff-file-merge-head t] + ["Diff Against Mine" git-diff-file-mine t] + ["Diff Against Other" git-diff-file-other t]) + "--------" + ["Add File" git-add-file t] + ["Revert File" git-revert-file t] + ["Ignore File" git-ignore-file t] + ["Remove File" git-remove-file t] + "--------" + ["Find File" git-find-file t] + ["View File" git-view-file t] + ["Diff File" git-diff-file t] + ["Interactive Diff File" git-diff-file-idiff t] + ["Log" git-log-file t] + "--------" + ["Mark" git-mark-file t] + ["Mark All" git-mark-all t] + ["Unmark" git-unmark-file t] + ["Unmark All" git-unmark-all t] + ["Toggle All Marks" git-toggle-all-marks t] + ["Hide Handled Files" git-remove-handled t] + "--------" + ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate] + ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored] + ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown] + "--------" + ["Quit" git-status-quit t]))) + ;; git mode should only run in the *git status* buffer (put 'git-status-mode 'mode-class 'special) @@ -734,7 +734,9 @@ struct diffstat_t { int nr; int alloc; struct diffstat_file { + char *from_name; char *name; + char *print_name; unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; @@ -755,11 +757,14 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, } diffstat->files[diffstat->nr++] = x; if (name_b) { - x->name = pprint_rename(name_a, name_b); + x->from_name = xstrdup(name_a); + x->name = xstrdup(name_b); x->is_renamed = 1; } - else + else { + x->from_name = NULL; x->name = xstrdup(name_a); + } return x; } @@ -803,6 +808,28 @@ static void show_graph(char ch, int cnt, const char *set, const char *reset) printf("%s", reset); } +static void fill_print_name(struct diffstat_file *file) +{ + char *pname; + + if (file->print_name) + return; + + if (!file->is_renamed) { + struct strbuf buf; + strbuf_init(&buf, 0); + if (quote_c_style(file->name, &buf, NULL, 0)) { + pname = strbuf_detach(&buf, NULL); + } else { + pname = file->name; + strbuf_release(&buf); + } + } else { + pname = pprint_rename(file->from_name, file->name); + } + file->print_name = pname; +} + static void show_stats(struct diffstat_t* data, struct diff_options *options) { int i, len, add, del, total, adds = 0, dels = 0; @@ -836,19 +863,8 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; int change = file->added + file->deleted; - - if (!file->is_renamed) { /* renames are already quoted by pprint_rename */ - struct strbuf buf; - strbuf_init(&buf, 0); - if (quote_c_style(file->name, &buf, NULL, 0)) { - free(file->name); - file->name = strbuf_detach(&buf, NULL); - } else { - strbuf_release(&buf); - } - } - - len = strlen(file->name); + fill_print_name(file); + len = strlen(file->print_name); if (max_len < len) max_len = len; @@ -873,7 +889,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { const char *prefix = ""; - char *name = data->files[i]->name; + char *name = data->files[i]->print_name; int added = data->files[i]->added; int deleted = data->files[i]->deleted; int name_len; @@ -901,17 +917,17 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) printf("%s%d%s", add_c, added, reset); printf(" bytes"); printf("\n"); - goto free_diffstat_file; + continue; } else if (data->files[i]->is_unmerged) { show_name(prefix, name, len, reset, set); printf(" Unmerged\n"); - goto free_diffstat_file; + continue; } else if (!data->files[i]->is_renamed && (added + deleted == 0)) { total_files--; - goto free_diffstat_file; + continue; } /* @@ -933,11 +949,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) show_graph('+', add, add_c, reset); show_graph('-', del, del_c, reset); putchar('\n'); - free_diffstat_file: - free(data->files[i]->name); - free(data->files[i]); } - free(data->files); printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n", set, total_files, adds, dels, reset); } @@ -962,11 +974,7 @@ static void show_shortstats(struct diffstat_t* data) dels += deleted; } } - free(data->files[i]->name); - free(data->files[i]); } - free(data->files); - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); } @@ -975,6 +983,9 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) { int i; + if (data->nr == 0) + return; + for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; @@ -982,15 +993,39 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) printf("-\t-\t"); else printf("%d\t%d\t", file->added, file->deleted); - if (!file->is_renamed) { - write_name_quoted(file->name, stdout, options->line_termination); + if (options->line_termination) { + fill_print_name(file); + if (!file->is_renamed) + write_name_quoted(file->name, stdout, + options->line_termination); + else { + fputs(file->print_name, stdout); + putchar(options->line_termination); + } } else { - fputs(file->name, stdout); - putchar(options->line_termination); + if (file->is_renamed) { + putchar('\0'); + write_name_quoted(file->from_name, stdout, '\0'); + } + write_name_quoted(file->name, stdout, '\0'); } } } +static void free_diffstat_info(struct diffstat_t *diffstat) +{ + int i; + for (i = 0; i < diffstat->nr; i++) { + struct diffstat_file *f = diffstat->files[i]; + if (f->name != f->print_name) + free(f->print_name); + free(f->name); + free(f->from_name); + free(f); + } + free(diffstat->files); +} + struct checkdiff_t { struct xdiff_emit_state xm; const char *filename; @@ -1009,13 +1044,20 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0; /* check space before tab */ - for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++) + for (i = 1; i < len; i++) { if (line[i] == ' ') spaces++; - if (line[i - 1] == '\t' && spaces) - space_before_tab = 1; + else if (line[i] == '\t') { + if (spaces) { + space_before_tab = 1; + break; + } + } + else + break; + } - /* check white space at line end */ + /* check whitespace at line end */ if (line[len - 1] == '\n') len--; if (isspace(line[len - 1])) @@ -1029,7 +1071,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) putchar(','); } if (white_space_at_end) - printf("white space at end"); + printf("whitespace at end"); printf(":%s ", reset); emit_line_with_ws(1, set, reset, ws, line, len, data->ws_rule); @@ -2943,8 +2985,9 @@ void diff_flush(struct diff_options *options) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) show_stats(&diffstat, options); - else if (output_format & DIFF_FORMAT_SHORTSTAT) + if (output_format & DIFF_FORMAT_SHORTSTAT) show_shortstats(&diffstat); + free_diffstat_info(&diffstat); separator++; } diff --git a/environment.c b/environment.c index f3e3d4138..18a1c4eec 100644 --- a/environment.c +++ b/environment.c @@ -31,7 +31,6 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 16 * 1024 * 1024; char *pager_program; -int pager_in_use; int pager_use_color = 1; char *editor_program; char *excludes_file; diff --git a/git-checkout.sh b/git-checkout.sh index f6d58ac04..5621c69d8 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -2,13 +2,13 @@ OPTIONS_KEEPDASHDASH=t OPTIONS_SPEC="\ -git-branch [options] [<branch>] [<paths>...] +git-checkout [options] [<branch>] [<paths>...] -- b= create a new branch started at <branch> -l create the new branchs reflog -track tells if the new branch should track the remote branch +l create the new branch's reflog +track arrange that the new branch tracks the remote branch f proceed even if the index or working tree is not HEAD -m performa three-way merge on local modifications if needed +m merge local modifications into the new branch q,quiet be quiet " SUBDIRECTORY_OK=Sometimes diff --git a/git-clone.sh b/git-clone.sh index ecf9d89a1..68085a322 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -205,7 +205,10 @@ fi # it is local if base=$(get_repo_base "$repo"); then repo="$base" - local=yes + if test -z "$depth" + then + local=yes + fi fi dir="$2" @@ -297,7 +300,8 @@ yes) find objects -type f -print | sed -e 1q) # objects directory should not be empty because # we are cloning! - test -f "$repo/$sample_file" || exit + test -f "$repo/$sample_file" || + die "fatal: cannot clone empty repository" if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null then rm -f "$GIT_DIR/objects/sample" diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 1e7727d27..9ee3f8045 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -80,6 +80,10 @@ case "${1:-.}${2:-.}${3:-.}" in echo "ERROR: $4: Not merging symbolic link changes." exit 1 ;; + *,160000,*) + echo "ERROR: $4: Not merging conflicting submodule changes." + exit 1 + ;; esac src2=`git-unpack-file $3` diff --git a/git-send-email.perl b/git-send-email.perl index c0e1dd348..1d6f46645 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -368,9 +368,10 @@ if ($thread && !defined $initial_reply_to && $prompting) { $initial_reply_to = $_; } - -$initial_reply_to =~ s/^\s*<?/</; -$initial_reply_to =~ s/>?\s*$/>/; +if (defined $initial_reply_to && $_ ne "") { + $initial_reply_to =~ s/^\s*<?/</; + $initial_reply_to =~ s/>?\s*$/>/; +} if (!defined $smtp_server) { foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) { diff --git a/git-svn.perl b/git-svn.perl index 9f884eb21..54d784469 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -529,7 +529,7 @@ sub cmd_find_rev { "$head history\n"; } my $desired_revision = substr($revision_or_hash, 1); - $result = $gs->rev_db_get($desired_revision); + $result = $gs->rev_map_get($desired_revision); } else { my (undef, $rev, undef) = cmt_metadata($revision_or_hash); $result = $rev; @@ -1128,12 +1128,12 @@ sub working_head_info { if (defined $url && defined $rev) { next if $max{$url} and $max{$url} < $rev; if (my $gs = Git::SVN->find_by_url($url)) { - my $c = $gs->rev_db_get($rev); + my $c = $gs->rev_map_get($rev); if ($c && $c eq $hash) { close $fh; # break the pipe return ($url, $rev, $uuid, $gs); } else { - $max{$url} ||= $gs->rev_db_max; + $max{$url} ||= $gs->rev_map_max; } } } @@ -1234,6 +1234,8 @@ sub md5sum { package Git::SVN; use strict; use warnings; +use Fcntl qw/:DEFAULT :seek/; +use constant rev_map_fmt => 'NH40'; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head $_use_svnsync_props $no_reuse_existing $_minimize_url @@ -1362,7 +1364,7 @@ sub fetch_all { if ($fetch) { foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); - my $lr = $gs->rev_db_max; + my $lr = $gs->rev_map_max; if (defined $lr) { $base = $lr if ($lr < $base); } @@ -1897,38 +1899,20 @@ sub last_rev_commit { return ($rev, $c); } } - my $db_path = $self->db_path; - unless (-e $db_path) { + my $map_path = $self->map_path; + unless (-e $map_path) { ($self->{last_rev}, $self->{last_commit}) = (undef, undef); return (undef, undef); } - my $offset = -41; # from tail - my $rl; - open my $fh, '<', $db_path or croak "$db_path not readable: $!\n"; - sysseek($fh, $offset, 2); # don't care for errors - sysread($fh, $rl, 41) == 41 or return (undef, undef); - chomp $rl; - while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) { - $offset -= 41; - sysseek($fh, $offset, 2); # don't care for errors - sysread($fh, $rl, 41) == 41 or return (undef, undef); - chomp $rl; - } - if ($c && $c ne $rl) { - die "$db_path and ", $self->refname, - " inconsistent!:\n$c != $rl\n"; - } - my $rev = sysseek($fh, 0, 1) or croak $!; - $rev = ($rev - 41) / 41; - close $fh or croak $!; - ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); - return ($rev, $c); + my ($rev, $commit) = $self->rev_map_max(1); + ($self->{last_rev}, $self->{last_commit}) = ($rev, $commit); + return ($rev, $commit); } sub get_fetch_range { my ($self, $min, $max) = @_; $max ||= $self->ra->get_latest_revnum; - $min ||= $self->rev_db_max; + $min ||= $self->rev_map_max; (++$min, $max); } @@ -2073,7 +2057,7 @@ sub do_git_commit { " was r$lr, but we are about to fetch: ", "r$log_entry->{revision}!\n"; } - if (my $c = $self->rev_db_get($log_entry->{revision})) { + if (my $c = $self->rev_map_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } @@ -2116,14 +2100,14 @@ sub do_git_commit { die "Failed to commit, invalid sha1: $commit\n"; } - $self->rev_db_set($log_entry->{revision}, $commit, 1); + $self->rev_map_set($log_entry->{revision}, $commit, 1); $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; print "r$log_entry->{revision}"; if (defined $log_entry->{svm_revision}) { print " (\@$log_entry->{svm_revision})"; - $self->rev_db_set($log_entry->{svm_revision}, $commit, + $self->rev_map_set($log_entry->{svm_revision}, $commit, 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; @@ -2465,25 +2449,44 @@ sub set_tree { } } +sub rebuild_from_rev_db { + my ($self, $path) = @_; + my $r = -1; + open my $fh, '<', $path or croak "open: $!"; + while (<$fh>) { + length($_) == 41 or croak "inconsistent size in ($_) != 41"; + chomp($_); + ++$r; + next if $_ eq ('0' x 40); + $self->rev_map_set($r, $_); + print "r$r = $_\n"; + } + close $fh or croak "close: $!"; + unlink $path or croak "unlink: $!"; +} + sub rebuild { my ($self) = @_; - my $db_path = $self->db_path; - return if (-e $db_path && ! -z $db_path); + my $map_path = $self->map_path; + return if (-e $map_path && ! -z $map_path); return unless ::verify_ref($self->refname.'^0'); - if (-f $self->{db_root}) { - rename $self->{db_root}, $db_path or die - "rename $self->{db_root} => $db_path failed: $!\n"; - my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#); - symlink $base, $self->{db_root} or die - "symlink $base => $self->{db_root} failed: $!\n"; + if ($self->use_svm_props || $self->no_metadata) { + my $rev_db = $self->rev_db_path; + $self->rebuild_from_rev_db($rev_db); + if ($self->use_svm_props) { + my $svm_rev_db = $self->rev_db_path($self->svm_uuid); + $self->rebuild_from_rev_db($svm_rev_db); + } + $self->unlink_rev_db_symlink; return; } - print "Rebuilding $db_path ...\n"; - my ($log, $ctx) = command_output_pipe("log", '--no-color', $self->refname); - my $latest; + print "Rebuilding $map_path ...\n"; + my ($log, $ctx) = + command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/, + $self->refname, '--'); my $full_url = $self->full_url; remove_username($full_url); - my $svn_uuid; + my $svn_uuid = $self->ra_uuid; my $c; while (<$log>) { if ( m{^commit ($::sha1)$} ) { @@ -2499,46 +2502,85 @@ sub rebuild { # if we merged or otherwise started elsewhere, this is # how we break out of it - if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || + if (($uuid ne $svn_uuid) || ($full_url && $url && ($url ne $full_url))) { next; } - $latest ||= $rev; - $svn_uuid ||= $uuid; - $self->rev_db_set($rev, $c); + $self->rev_map_set($rev, $c); print "r$rev = $c\n"; } command_close_pipe($log, $ctx); - print "Done rebuilding $db_path\n"; + print "Done rebuilding $map_path\n"; + my $rev_db_path = $self->rev_db_path; + if (-f $self->rev_db_path) { + unlink $self->rev_db_path or croak "unlink: $!"; + } + $self->unlink_rev_db_symlink; } -# rev_db: +# rev_map: # Tie::File seems to be prone to offset errors if revisions get sparse, # it's not that fast, either. Tie::File is also not in Perl 5.6. So # one of my favorite modules is out :< Next up would be one of the DBM -# modules, but I'm not sure which is most portable... So I'll just -# go with something that's plain-text, but still capable of -# being randomly accessed. So here's my ultra-simple fixed-width -# database. All records are 40 characters + "\n", so it's easy to seek -# to a revision: (41 * rev) is the byte offset. -# A record of 40 0s denotes an empty revision. -# And yes, it's still pretty fast (faster than Tie::File). +# modules, but I'm not sure which is most portable... +# +# This is the replacement for the rev_db format, which was too big +# and inefficient for large repositories with a lot of sparse history +# (mainly tags) +# +# The format is this: +# - 24 bytes for every record, +# * 4 bytes for the integer representing an SVN revision number +# * 20 bytes representing the sha1 of a git commit +# - No empty padding records like the old format +# (except the last record, which can be overwritten) +# - new records are written append-only since SVN revision numbers +# increase monotonically +# - lookups on SVN revision number are done via a binary search +# - Piping the file to xxd -c24 is a good way of dumping it for +# viewing or editing (piped back through xxd -r), should the need +# ever arise. +# - The last record can be padding revision with an all-zero sha1 +# This is used to optimize fetch performance when using multiple +# "fetch" directives in .git/config +# # These files are disposable unless noMetadata or useSvmProps is set -sub _rev_db_set { +sub _rev_map_set { my ($fh, $rev, $commit) = @_; - my $offset = $rev * 41; - # assume that append is the common case: - seek $fh, 0, 2 or croak $!; - my $pos = tell $fh; - if ($pos < $offset) { - for (1 .. (($offset - $pos) / 41)) { - print $fh (('0' x 40),"\n") or croak $!; + + my $size = (stat($fh))[7]; + ($size % 24) == 0 or croak "inconsistent size: $size"; + + my $wr_offset = 0; + if ($size > 0) { + sysseek($fh, -24, SEEK_END) or croak "seek: $!"; + my $read = sysread($fh, my $buf, 24) or croak "read: $!"; + $read == 24 or croak "read only $read bytes (!= 24)"; + my ($last_rev, $last_commit) = unpack(rev_map_fmt, $buf); + if ($last_commit eq ('0' x40)) { + if ($size >= 48) { + sysseek($fh, -48, SEEK_END) or croak "seek: $!"; + $read = sysread($fh, $buf, 24) or + croak "read: $!"; + $read == 24 or + croak "read only $read bytes (!= 24)"; + ($last_rev, $last_commit) = + unpack(rev_map_fmt, $buf); + if ($last_commit eq ('0' x40)) { + croak "inconsistent .rev_map\n"; + } + } + if ($last_rev >= $rev) { + croak "last_rev is higher!: $last_rev >= $rev"; + } + $wr_offset = -24; } } - seek $fh, $offset, 0 or croak $!; - print $fh $commit,"\n" or croak $!; + sysseek($fh, $wr_offset, SEEK_END) or croak "seek: $!"; + syswrite($fh, pack(rev_map_fmt, $rev, $commit), 24) == 24 or + croak "write: $!"; } sub mkfile { @@ -2551,10 +2593,10 @@ sub mkfile { } } -sub rev_db_set { +sub rev_map_set { my ($self, $rev, $commit, $update_ref, $uuid) = @_; length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; - my $db = $self->db_path($uuid); + my $db = $self->map_path($uuid); my $db_lock = "$db.lock"; my $sig; if ($update_ref) { @@ -2569,16 +2611,18 @@ sub rev_db_set { # and we can't afford to lose it because rebuild() won't work if ($self->use_svm_props || $self->no_metadata) { $sync = 1; - copy($db, $db_lock) or die "rev_db_set(@_): ", + copy($db, $db_lock) or die "rev_map_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; } else { - rename $db, $db_lock or die "rev_db_set(@_): ", + rename $db, $db_lock or die "rev_map_set(@_): ", "Failed to rename: ", "$db => $db_lock ($!)\n"; } - open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n"; - _rev_db_set($fh, $rev, $commit); + + sysopen(my $fh, $db_lock, O_RDWR | O_CREAT) + or croak "Couldn't open $db_lock: $!\n"; + _rev_map_set($fh, $rev, $commit); if ($sync) { $fh->flush or die "Couldn't flush $db_lock: $!\n"; $fh->sync or die "Couldn't sync $db_lock: $!\n"; @@ -2589,7 +2633,7 @@ sub rev_db_set { command_noisy('update-ref', '-m', "r$rev", $self->refname, $commit); } - rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ", + rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ", "$db_lock => $db ($!)\n"; delete $LOCKFILES{$db_lock}; if ($update_ref) { @@ -2599,29 +2643,76 @@ sub rev_db_set { } } -sub rev_db_max { - my ($self) = @_; +# If want_commit, this will return an array of (rev, commit) where +# commit _must_ be a valid commit in the archive. +# Otherwise, it'll return the max revision (whether or not the +# commit is valid or just a 0x40 placeholder). +sub rev_map_max { + my ($self, $want_commit) = @_; $self->rebuild; - my $db_path = $self->db_path; - my @stat = stat $db_path or return 0; - ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; - my $max = $stat[7] / 41; - (($max > 0) ? $max - 1 : 0); + my $map_path = $self->map_path; + stat $map_path or return $want_commit ? (0, undef) : 0; + sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!"; + my $size = (stat($fh))[7]; + ($size % 24) == 0 or croak "inconsistent size: $size"; + + if ($size == 0) { + close $fh or croak "close: $!"; + return $want_commit ? (0, undef) : 0; + } + + sysseek($fh, -24, SEEK_END) or croak "seek: $!"; + sysread($fh, my $buf, 24) == 24 or croak "read: $!"; + my ($r, $c) = unpack(rev_map_fmt, $buf); + if ($want_commit && $c eq ('0' x40)) { + if ($size < 48) { + return $want_commit ? (0, undef) : 0; + } + sysseek($fh, -48, SEEK_END) or croak "seek: $!"; + sysread($fh, $buf, 24) == 24 or croak "read: $!"; + ($r, $c) = unpack(rev_map_fmt, $buf); + if ($c eq ('0'x40)) { + croak "Penultimate record is all-zeroes in $map_path"; + } + } + close $fh or croak "close: $!"; + $want_commit ? ($r, $c) : $r; } -sub rev_db_get { +sub rev_map_get { my ($self, $rev, $uuid) = @_; - my $ret; - my $offset = $rev * 41; - my $db_path = $self->db_path($uuid); - return undef unless -e $db_path; - open my $fh, '<', $db_path or croak $!; - if (sysseek($fh, $offset, 0) == $offset) { - my $read = sysread($fh, $ret, 40); - $ret = undef if ($read != 40 || $ret eq ('0'x40)); + my $map_path = $self->map_path($uuid); + return undef unless -e $map_path; + + sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!"; + my $size = (stat($fh))[7]; + ($size % 24) == 0 or croak "inconsistent size: $size"; + + if ($size == 0) { + close $fh or croak "close: $fh"; + return undef; } - close $fh or croak $!; - $ret; + + my ($l, $u) = (0, $size - 24); + my ($r, $c, $buf); + + while ($l <= $u) { + my $i = int(($l/24 + $u/24) / 2) * 24; + sysseek($fh, $i, SEEK_SET) or croak "seek: $!"; + sysread($fh, my $buf, 24) == 24 or croak "read: $!"; + my ($r, $c) = unpack('NH40', $buf); + + if ($r < $rev) { + $l = $i + 24; + } elsif ($r > $rev) { + $u = $i - 24; + } else { # $r == $rev + close($fh) or croak "close: $!"; + return $c eq ('0' x 40) ? undef : $c; + } + } + close($fh) or croak "close: $!"; + undef; } # Finds the first svn revision that exists on (if $eq_ok is true) or @@ -2633,7 +2724,7 @@ sub find_rev_before { --$rev unless $eq_ok; $min_rev ||= 1; while ($rev >= $min_rev) { - if (my $c = $self->rev_db_get($rev)) { + if (my $c = $self->rev_map_get($rev)) { return ($rev, $c); } --$rev; @@ -2648,9 +2739,9 @@ sub find_rev_before { sub find_rev_after { my ($self, $rev, $eq_ok, $max_rev) = @_; ++$rev unless $eq_ok; - $max_rev ||= $self->rev_db_max(); + $max_rev ||= $self->rev_map_max; while ($rev <= $max_rev) { - if (my $c = $self->rev_db_get($rev)) { + if (my $c = $self->rev_map_get($rev)) { return ($rev, $c); } ++$rev; @@ -2673,13 +2764,32 @@ sub _new { bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", - db_root => "$dir/.rev_db", repo_id => $repo_id }, $class; + map_root => "$dir/.rev_map", repo_id => $repo_id }, $class; } -sub db_path { +# for read-only access of old .rev_db formats +sub unlink_rev_db_symlink { + my ($self) = @_; + my $link = $self->rev_db_path; + $link =~ s/\.[\w-]+$// or croak "missing UUID at the end of $link"; + if (-l $link) { + unlink $link or croak "unlink: $link failed!"; + } +} + +sub rev_db_path { + my ($self, $uuid) = @_; + my $db_path = $self->map_path($uuid); + $db_path =~ s{/\.rev_map\.}{/\.rev_db\.} + or croak "map_path: $db_path does not contain '/.rev_map.' !"; + $db_path; +} + +# the new replacement for .rev_db +sub map_path { my ($self, $uuid) = @_; $uuid ||= $self->ra_uuid; - "$self->{db_root}.$uuid"; + "$self->{map_root}.$uuid"; } sub uri_encode { @@ -3763,7 +3873,7 @@ sub gs_fetch_loop_common { foreach my $gs ($self->match_globs(\%exists, $paths, $globs, $r)) { - if ($gs->rev_db_max >= $r) { + if ($gs->rev_map_max >= $r) { next; } next unless $gs->match_paths($paths, $r); @@ -3792,8 +3902,9 @@ sub gs_fetch_loop_common { # pre-fill the .rev_db since it'll eventually get filled in # with '0' x40 if something new gets committed foreach my $gs (@$gsv) { - next if defined $gs->rev_db_get($max); - $gs->rev_db_set($max, 0 x40); + next if $gs->rev_map_max >= $max; + next if defined $gs->rev_map_get($max); + $gs->rev_map_set($max, 0 x40); } foreach my $g (@$globs) { my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev"; @@ -3969,39 +4080,7 @@ sub cmt_showable { } sub log_use_color { - return 1 if $color; - my ($dc, $dcvar); - $dcvar = 'color.diff'; - $dc = `git-config --get $dcvar`; - if ($dc eq '') { - # nothing at all; fallback to "diff.color" - $dcvar = 'diff.color'; - $dc = `git-config --get $dcvar`; - } - chomp($dc); - if ($dc eq 'auto') { - my $pc; - $pc = `git-config --get color.pager`; - if ($pc eq '') { - # does not have it -- fallback to pager.color - $pc = `git-config --bool --get pager.color`; - } - else { - $pc = `git-config --bool --get color.pager`; - if ($?) { - $pc = 'false'; - } - } - chomp($pc); - if (-t *STDOUT || (defined $pager && $pc eq 'true')) { - return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); - } - return 0; - } - return 0 if $dc eq 'never'; - return 1 if $dc eq 'always'; - chomp($dc = `git-config --bool --get $dcvar`); - return ($dc eq 'true'); + return $color || Git->repository->get_colorbool('color.diff'); } sub git_svn_log_cmd { @@ -4030,7 +4109,7 @@ sub git_svn_log_cmd { push @cmd, @log_opts; if (defined $r_max && $r_max == $r_min) { push @cmd, '--max-count=1'; - if (my $c = $gs->rev_db_get($r_max)) { + if (my $c = $gs->rev_map_get($r_max)) { push @cmd, $c; } } elsif (defined $r_max) { @@ -4060,6 +4139,7 @@ sub config_pager { } elsif (length $pager == 0 || $pager eq 'cat') { $pager = undef; } + $ENV{GIT_PAGER_IN_USE} = defined($pager); } sub run_pager { @@ -4311,6 +4391,16 @@ package Git::SVN::Migration; # --use-separate-remotes option in git-clone (now default) # - we do not automatically migrate to this (following # the example set by core git) +# +# v5 layout: .rev_db.$UUID => .rev_map.$UUID +# - newer, more-efficient format that uses 24-bytes per record +# with no filler space. +# - use xxd -c24 < .rev_map.$UUID to view and debug +# - This is a one-way migration, repositories updated to the +# new format will not be able to use old git-svn without +# rebuilding the .rev_db. Rebuilding the rev_db is not +# possible if noMetadata or useSvmProps are set; but should +# be no problem for users that use the (sensible) defaults. use strict; use warnings; use Carp qw/croak/; diff --git a/http-push.c b/http-push.c index 78283b4de..fffbe9ccb 100644 --- a/http-push.c +++ b/http-push.c @@ -1275,8 +1275,6 @@ static struct remote_lock *lock_remote(const char *path, long timeout) char *ep; char timeout_header[25]; struct remote_lock *lock = NULL; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; @@ -1345,6 +1343,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout) if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result == CURLE_OK) { + XML_Parser parser = XML_ParserCreate(NULL); + enum XML_Status result; ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1363,6 +1363,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) XML_GetErrorCode(parser))); lock->timeout = -1; } + XML_ParserFree(parser); } } else { fprintf(stderr, "Unable to start LOCK request\n"); @@ -1525,8 +1526,6 @@ static void remote_ls(const char *path, int flags, struct buffer out_buffer; char *in_data; char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; struct remote_ls_ctx ls; @@ -1569,6 +1568,8 @@ static void remote_ls(const char *path, int flags, if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result == CURLE_OK) { + XML_Parser parser = XML_ParserCreate(NULL); + enum XML_Status result; ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1587,6 +1588,7 @@ static void remote_ls(const char *path, int flags, XML_ErrorString( XML_GetErrorCode(parser))); } + XML_ParserFree(parser); } } else { fprintf(stderr, "Unable to start PROPFIND request\n"); @@ -1620,8 +1622,6 @@ static int locking_available(void) struct buffer out_buffer; char *in_data; char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; int lock_flags = 0; @@ -1658,6 +1658,8 @@ static int locking_available(void) if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result == CURLE_OK) { + XML_Parser parser = XML_ParserCreate(NULL); + enum XML_Status result; ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1676,6 +1678,7 @@ static int locking_available(void) XML_GetErrorCode(parser))); lock_flags = 0; } + XML_ParserFree(parser); } } else { fprintf(stderr, "Unable to start PROPFIND request\n"); diff --git a/merge-recursive.c b/merge-recursive.c index 9a1e2f269..2a58dad3f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1046,14 +1046,16 @@ static struct merge_file_info merge_file(struct diff_filespec *o, free(result_buf.ptr); result.clean = (merge_status == 0); - } else { - if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) - die("cannot merge modes?"); - + } else if (S_ISGITLINK(a->mode)) { + result.clean = 0; + hashcpy(result.sha, a->sha1); + } else if (S_ISLNK(a->mode)) { hashcpy(result.sha, a->sha1); if (!sha_eq(a->sha1, b->sha1)) result.clean = 0; + } else { + die("unsupported object type in the tree"); } } @@ -5,6 +5,8 @@ * something different on Windows, for example. */ +static int spawned_pager; + static void run_pager(const char *pager) { /* @@ -41,7 +43,7 @@ void setup_pager(void) else if (!*pager || !strcmp(pager, "cat")) return; - pager_in_use = 1; /* means we are emitting to terminal */ + spawned_pager = 1; /* means we are emitting to terminal */ if (pipe(fd) < 0) return; @@ -70,3 +72,14 @@ void setup_pager(void) die("unable to execute pager '%s'", pager); exit(255); } + +int pager_in_use(void) +{ + const char *env; + + if (spawned_pager) + return 1; + + env = getenv("GIT_PAGER_IN_USE"); + return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0; +} diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 6aecd897f..320253eb8 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -17,9 +17,6 @@ if ($@ || $Error::VERSION < 0.15009) { $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm'; } -my %extra; -$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR}; - # redirect stdout, otherwise the message "Writing perl.mak for Git" # disrupts the output for the target 'instlibdir' open STDOUT, ">&STDERR"; @@ -29,6 +26,5 @@ WriteMakefile( VERSION_FROM => 'Git.pm', PM => \%pm, MAKEFILE => 'perl.mak', - INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3', - %extra + INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3' ); diff --git a/revision.c b/revision.c index 2a5903519..7e2f4f1eb 100644 --- a/revision.c +++ b/revision.c @@ -139,6 +139,18 @@ void add_pending_object(struct rev_info *revs, struct object *obj, const char *n add_pending_object_with_mode(revs, obj, name, S_IFINVALID); } +void add_head_to_pending(struct rev_info *revs) +{ + unsigned char sha1[20]; + struct object *obj; + if (get_sha1("HEAD", sha1)) + return; + obj = parse_object(sha1); + if (!obj) + return; + add_pending_object(revs, obj, "HEAD"); +} + static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags) { struct object *object; diff --git a/revision.h b/revision.h index 992e1e9dd..857231595 100644 --- a/revision.h +++ b/revision.h @@ -130,6 +130,8 @@ extern void add_object(struct object *obj, extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name); +extern void add_head_to_pending(struct rev_info *); + enum commit_action { commit_ignore, commit_show, diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 8d4a44721..9ee35e790 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -82,3 +82,29 @@ stop_httpd () { test -z "$SVN_HTTPD_PORT" && return "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop } + +convert_to_rev_db () { + perl -w -- - "$@" <<\EOF +use strict; +@ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>"; +open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; +open my $rd, '<', $ARGV[0] or die "$!: couldn't open: $ARGV[0]"; +my $size = (stat($rd))[7]; +($size % 24) == 0 or die "Inconsistent size: $size"; +while (sysread($rd, my $buf, 24) == 24) { + my ($r, $c) = unpack('NH40', $buf); + my $offset = $r * 41; + seek $wr, 0, 2 or die $!; + my $pos = tell $wr; + if ($pos < $offset) { + for (1 .. (($offset - $pos) / 41)) { + print $wr (('0' x 40),"\n") or die $!; + } + } + seek $wr, $offset, 0 or die $!; + print $wr $c,"\n" or die $!; +} +close $wr or die $!; +close $rd or die $!; +EOF +} diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 79fdff3f3..6adf9d11d 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -117,4 +117,13 @@ EOF git diff -b > out test_expect_success 'another test, with -b' 'git diff expect out' + +test_expect_success 'check mixed spaces and tabs in indent' ' + + # This is indented with SP HT SP. + echo " foo();" > x && + git diff --check | grep "space before tab" + +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index c7130c4dc..09d56e083 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -640,6 +640,46 @@ test_expect_success 'creating a signed tag with -m message should succeed' ' git diff expect actual ' +get_tag_header u-signed-tag $commit commit $time >expect +echo 'Another message' >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success 'sign with a given key id' ' + + git tag -u committer@example.com -m "Another message" u-signed-tag && + get_tag_msg u-signed-tag >actual && + git diff expect actual + +' + +test_expect_success 'sign with an unknown id (1)' ' + + ! git tag -u author@example.com -m "Another message" o-signed-tag + +' + +test_expect_success 'sign with an unknown id (2)' ' + + ! git tag -u DEADBEEF -m "Another message" o-signed-tag + +' + +cat >fakeeditor <<'EOF' +#!/bin/sh +test -n "$1" && exec >"$1" +echo A signed tag message +echo from a fake editor. +EOF +chmod +x fakeeditor + +get_tag_header implied-sign $commit commit $time >expect +./fakeeditor >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success '-u implies signed tag' ' + GIT_EDITOR=./fakeeditor git-tag -u CDDE430D implied-sign && + get_tag_msg implied-sign >actual && + git diff expect actual +' + cat >sigmsgfile <<EOF Another signed tag message in a file. @@ -667,13 +707,6 @@ test_expect_success 'creating a signed tag with -F - should succeed' ' git diff expect actual ' -cat >fakeeditor <<'EOF' -#!/bin/sh -test -n "$1" && exec >"$1" -echo A signed tag message -echo from a fake editor. -EOF -chmod +x fakeeditor get_tag_header implied-annotate $commit commit $time >expect ./fakeeditor >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 55558aba8..73d8a00e2 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -20,6 +20,8 @@ Test switching across them. . ./test-lib.sh +test_tick + fill () { for i do @@ -30,9 +32,10 @@ fill () { test_expect_success setup ' + fill x y z > same && fill 1 2 3 4 5 6 7 8 >one && fill a b c d e >two && - git add one two && + git add same one two && git commit -m "Initial A one, A two" && git checkout -b renamer && @@ -74,16 +77,44 @@ test_expect_success "checkout with dirty tree without -m" ' ' +test_expect_success "checkout with unrelated dirty tree without -m" ' + + git checkout -f master && + fill 0 1 2 3 4 5 6 7 8 >same && + cp same kept + git checkout side >messages && + git diff same kept + (cat > messages.expect <<EOF +M same +EOF +) && + touch messages.expect && + git diff messages.expect messages +' + test_expect_success "checkout -m with dirty tree" ' git checkout -f master && git clean -f && fill 0 1 2 3 4 5 6 7 8 >one && - git checkout -m side && + git checkout -m side > messages && test "$(git symbolic-ref HEAD)" = "refs/heads/side" && + (cat >expect.messages <<EOF +Merging side with local +Merging: +ab76817 Side M one, D two, A three +virtual local +found 1 common ancestor(s): +7329388 Initial A one, A two +Auto-merged one +M one +EOF +) && + git diff expect.messages messages && + fill "M one" "A three" "D two" >expect.master && git diff --name-status master >current.master && diff expect.master current.master && @@ -145,7 +176,16 @@ test_expect_success 'checkout -m with merge conflict' ' test_expect_success 'checkout to detach HEAD' ' git checkout -f renamer && git clean -f && - git checkout renamer^ && + git checkout renamer^ 2>messages && + (cat >messages.expect <<EOF +Note: moving to "renamer^" which isn'"'"'t a local branch +If you want to create a new branch from this checkout, you may do so +(now or later) by using -b with the checkout command again. Example: + git checkout -b <new_branch_name> +HEAD is now at 7329388... Initial A one, A two +EOF +) && + git diff messages.expect messages && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && test "z$H" = "z$M" && diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 67fdf7023..0a41d52c7 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -97,15 +97,19 @@ test_expect_success 'migrate --minimize on old inited layout' " grep '^:refs/remotes/git-svn' fetch.out " -test_expect_success ".rev_db auto-converted to .rev_db.UUID" " +test_expect_success ".rev_db auto-converted to .rev_map.UUID" " git-svn fetch -i trunk && - expect=$GIT_DIR/svn/trunk/.rev_db.* && + test -z \"\$(ls $GIT_DIR/svn/trunk/.rev_db.* 2>/dev/null)\" && + expect=\"\$(ls $GIT_DIR/svn/trunk/.rev_map.*)\" && test -n \"\$expect\" && - mv \$expect $GIT_DIR/svn/trunk/.rev_db && + rev_db=\$(echo \$expect | sed -e 's,_map,_db,') && + convert_to_rev_db \$expect \$rev_db && + rm -f \$expect && + test -f \$rev_db && git-svn fetch -i trunk && - test -L $GIT_DIR/svn/trunk/.rev_db && - test -f \$expect && - cmp \$expect $GIT_DIR/svn/trunk/.rev_db + test -z \"\$(ls $GIT_DIR/svn/trunk/.rev_db.* 2>/dev/null)\" && + test ! -e $GIT_DIR/svn/trunk/.rev_db && + test -f \$expect " test_done diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index 439bd93c8..cc6191159 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -5,6 +5,8 @@ test_description='git-svn info' . ./lib-git-svn.sh +say 'skipping svn-info test (has a race undiagnosed yet)' +test_done ptouch() { perl -w -e ' diff --git a/tree-diff.c b/tree-diff.c index aa0a10029..e1e2e6c6c 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -326,6 +326,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co die("unable to set up diff options to follow renames"); diff_tree(t1, t2, base, &diff_opts); diffcore_std(&diff_opts); + diff_tree_release_paths(&diff_opts); /* Go through the new set of filepairing, and see if we find a more interesting one */ for (i = 0; i < q->nr; i++) { @@ -342,6 +343,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co choice = p; /* Update the path we use from now on.. */ + diff_tree_release_paths(opt); opt->paths[0] = xstrdup(p->one->path); diff_tree_setup_paths(opt->paths, opt); break; |