diff options
39 files changed, 638 insertions, 346 deletions
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 9eb2d9fe7e..b1bfff630f 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -18,6 +18,7 @@ code. For Git in general, three rough rules are: judgement call, the decision based more on real world constraints people face than what the paper standard says. +Make your code readable and sensible, and don't try to be clever. As for more concrete guidelines, just imitate the existing code (this is a good guideline, no matter which project you are @@ -179,6 +180,47 @@ For C programs: - Use Git's gettext wrappers to make the user interface translatable. See "Marking strings for translation" in po/README. +For Perl programs: + + - Most of the C guidelines above apply. + + - We try to support Perl 5.8 and later ("use Perl 5.008"). + + - use strict and use warnings are strongly preferred. + + - Don't overuse statement modifiers unless using them makes the + result easier to follow. + + ... do something ... + do_this() unless (condition); + ... do something else ... + + is more readable than: + + ... do something ... + unless (condition) { + do_this(); + } + ... do something else ... + + *only* when the condition is so rare that do_this() will be almost + always called. + + - We try to avoid assignments inside "if ()" conditions. + + - Learn and use Git.pm if you need that functionality. + + - For Emacs, it's useful to put the following in + GIT_CHECKOUT/.dir-locals.el, assuming you use cperl-mode: + + ;; note the first part is useful for C editing, too + ((nil . ((indent-tabs-mode . t) + (tab-width . 8) + (fill-column . 80))) + (cperl-mode . ((cperl-indent-level . 8) + (cperl-extra-newline-before-brace . nil) + (cperl-merge-trailing-else . t)))) + For Python scripts: - We follow PEP-8 (http://www.python.org/dev/peps/pep-0008/). diff --git a/Documentation/RelNotes/1.8.2.txt b/Documentation/RelNotes/1.8.2.txt index ad6496f826..abf3b675fd 100644 --- a/Documentation/RelNotes/1.8.2.txt +++ b/Documentation/RelNotes/1.8.2.txt @@ -115,6 +115,9 @@ UI, Workflows & Features rewrite the names and email addresses of people using the mailmap mechanism. + * "git log --cc --graph" now shows the combined diff output with the + ancestry graph. + * "git mergetool" and "git difftool" learned to list the available tool backends in a more consistent manner. @@ -144,6 +147,9 @@ UI, Workflows & Features you do not have any commits in your history, but it now gives you an empty index (to match non-existent commit you are not even on). + * "git status" says what branch is being bisected or rebased when + able, not just "bisecting" or "rebasing". + * "git submodule" started learning a new mode to integrate with the tip of the remote branch (as opposed to integrating with the commit recorded in the superproject's gitlink). @@ -211,6 +217,13 @@ Performance, Internal Implementation, etc. these implementations can reliably update. This can be used to avoid excessive revalidation of contents. + * Some platforms ship with old version of expat where xmlparse.h + needs to be included instead of expat.h; the build procedure has + been taught about this. + + * "make clean" on platforms that cannot compute header dependencies + on the fly did not work with implementations of "rm" that do not + like an empty argument list. Also contains minor documentation updates and code clean-ups. @@ -295,6 +308,9 @@ details). failed to remove the real location of the $GIT_DIR it created. This was most visible when interrupting a submodule update. + * "git cvsimport" mishandled timestamps at DST boundary. + (merge 48c9162 bw/get-tz-offset-perl later to maint). + * We used to have an arbitrary 32 limit for combined diff input, resulting in incorrect number of leading colons shown when showing the "--raw --cc" output. @@ -383,6 +399,11 @@ details). * Command line completion code was inadvertently made incompatible with older versions of bash by using a newer array notation. + * "git push" was taught to refuse updating the branch that is + currently checked out long time ago, but the user manual was left + stale. + (merge d9be248 wk/man-deny-current-branch-is-default-these-days later to maint). + * Some shells do not behave correctly when IFS is unset; work it around by explicitly setting it to the default value. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index d980e2cb7d..388a2254f9 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -110,7 +110,7 @@ apply to the index. See EDITING PATCHES below. If no <pathspec> is given, the current version of Git defaults to "."; in other words, update all tracked files in the current directory and its subdirectories. This default will change in a future version -of Git, hence the form without <filepattern> should not be used. +of Git, hence the form without <pathspec> should not be used. -A:: --all:: diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index b4831bb0cf..f986c5cb3a 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -83,7 +83,7 @@ Bisect reset ~~~~~~~~~~~~ After a bisect session, to clean up the bisection state and return to -the original HEAD, issue the following command: +the original HEAD (i.e., to quit bisecting), issue the following command: ------------------------------------------------ $ git bisect reset @@ -284,6 +284,7 @@ EXAMPLES ------------ $ git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good $ git bisect run make # "make" builds the app +$ git bisect reset # quit the bisect session ------------ * Automatically bisect a test failure between origin and HEAD: @@ -291,6 +292,7 @@ $ git bisect run make # "make" builds the app ------------ $ git bisect start HEAD origin -- # HEAD is bad, origin is good $ git bisect run make test # "make test" builds and tests +$ git bisect reset # quit the bisect session ------------ * Automatically bisect a broken test case: @@ -302,6 +304,7 @@ make || exit 125 # this skips broken builds ~/check_test_case.sh # does the test case pass? $ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run ~/test.sh +$ git bisect reset # quit the bisect session ------------ + Here we use a "test.sh" custom script. In this script, if "make" @@ -351,6 +354,7 @@ use `git cherry-pick` instead of `git merge`.) ------------ $ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh" +$ git bisect reset # quit the bisect session ------------ + This shows that you can do without a run script if you write the test @@ -368,6 +372,7 @@ $ git bisect run sh -c ' rm -f tmp.$$ test $rc = 0' +$ git bisect reset # quit the bisect session ------------ + In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit that diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 32cadc3f73..a4778d725c 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1987,9 +1987,10 @@ handling this case. Note that the target of a "push" is normally a <<def_bare_repository,bare>> repository. You can also push to a -repository that has a checked-out working tree, but the working tree -will not be updated by the push. This may lead to unexpected results if -the branch you push to is the currently checked-out branch! +repository that has a checked-out working tree, but a push to update the +currently checked-out branch is denied by default to prevent confusion. +See the description ofthe receive.denyCurrentBranch option +in linkgit:git-config[1] for details. As with `git fetch`, you may also set up configuration options to save typing; so, for example, after @@ -43,6 +43,9 @@ all:: # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define EXPAT_NEEDS_XMLPARSE_H if you have an old version of expat (e.g., +# 1.1 or 1.2) that provides xmlparse.h instead of expat.h. +# # Define NO_GETTEXT if you don't want Git output to be translated. # A translated Git requires GNU libintl or another gettext implementation, # plus libintl-perl at runtime. @@ -480,9 +483,38 @@ SCRIPT_PERL += git-svn.perl SCRIPT_PYTHON += git-remote-testpy.py SCRIPT_PYTHON += git-p4.py -SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ - $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ +# Generated files for scripts +SCRIPT_SH_GEN = $(patsubst %.sh,%,$(SCRIPT_SH)) +SCRIPT_PERL_GEN = $(patsubst %.perl,%,$(SCRIPT_PERL)) +SCRIPT_PYTHON_GEN = $(patsubst %.py,%,$(SCRIPT_PYTHON)) + +# Individual rules to allow e.g. +# "make -C ../.. SCRIPT_PERL=contrib/foo/bar.perl build-perl-script" +# from subdirectories like contrib/*/ +.PHONY: build-perl-script build-sh-script build-python-script +build-perl-script: $(SCRIPT_PERL_GEN) +build-sh-script: $(SCRIPT_SH_GEN) +build-python-script: $(SCRIPT_PYTHON_GEN) + +.PHONY: install-perl-script install-sh-script install-python-script +install-sh-script: $(SCRIPT_SH_GEN) + $(INSTALL) $(SCRIPT_SH_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' +install-perl-script: $(SCRIPT_PERL_GEN) + $(INSTALL) $(SCRIPT_PERL_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' +install-python-script: $(SCRIPT_PYTHON_GEN) + $(INSTALL) $(SCRIPT_PYTHON_GEN) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' + +.PHONY: clean-perl-script clean-sh-script clean-python-script +clean-sh-script: + $(RM) $(SCRIPT_SH_GEN) +clean-perl-script: + $(RM) $(SCRIPT_PERL_GEN) +clean-python-script: + $(RM) $(SCRIPT_PYTHON_GEN) + +SCRIPTS = $(SCRIPT_SH_GEN) \ + $(SCRIPT_PERL_GEN) \ + $(SCRIPT_PYTHON_GEN) \ git-instaweb ETAGS_TARGET = TAGS @@ -1089,6 +1121,9 @@ else else EXPAT_LIBEXPAT = -lexpat endif + ifdef EXPAT_NEEDS_XMLPARSE_H + BASIC_CFLAGS += -DEXPAT_NEEDS_XMLPARSE_H + endif endif endif @@ -2414,8 +2449,7 @@ clean: profile-clean builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) - $(RM) -r bin-wrappers - $(RM) -r $(dep_dirs) + $(RM) -r bin-wrappers $(dep_dirs) $(RM) -r po/build/ $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* $(RM) -r $(GIT_TARNAME) .doc-tmp-dir diff --git a/builtin/apply.c b/builtin/apply.c index 080ce2ea3e..06f5320b18 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -3600,6 +3600,40 @@ static int get_current_sha1(const char *path, unsigned char *sha1) return 0; } +static int preimage_sha1_in_gitlink_patch(struct patch *p, unsigned char sha1[20]) +{ + /* + * A usable gitlink patch has only one fragment (hunk) that looks like: + * @@ -1 +1 @@ + * -Subproject commit <old sha1> + * +Subproject commit <new sha1> + * or + * @@ -1 +0,0 @@ + * -Subproject commit <old sha1> + * for a removal patch. + */ + struct fragment *hunk = p->fragments; + static const char heading[] = "-Subproject commit "; + char *preimage; + + if (/* does the patch have only one hunk? */ + hunk && !hunk->next && + /* is its preimage one line? */ + hunk->oldpos == 1 && hunk->oldlines == 1 && + /* does preimage begin with the heading? */ + (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL && + !prefixcmp(++preimage, heading) && + /* does it record full SHA-1? */ + !get_sha1_hex(preimage + sizeof(heading) - 1, sha1) && + preimage[sizeof(heading) + 40 - 1] == '\n' && + /* does the abbreviated name on the index line agree with it? */ + !prefixcmp(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) + return 0; /* it all looks fine */ + + /* we may have full object name on the index line */ + return get_sha1_hex(p->old_sha1_prefix, sha1); +} + /* Build an index that contains the just the files needed for a 3way merge */ static void build_fake_ancestor(struct patch *list, const char *filename) { @@ -3620,8 +3654,10 @@ static void build_fake_ancestor(struct patch *list, const char *filename) continue; if (S_ISGITLINK(patch->old_mode)) { - if (get_sha1_hex(patch->old_sha1_prefix, sha1)) - die("submoule change for %s without full index name", + if (!preimage_sha1_in_gitlink_patch(patch, sha1)) + ; /* ok, the textual part looks sane */ + else + die("sha1 information is lacking or useless for submoule %s", name); } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) { ; /* ok */ diff --git a/combine-diff.c b/combine-diff.c index 7f6187f9cd..35d41cd56d 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -526,7 +526,8 @@ static void show_line_to_eol(const char *line, int len, const char *reset) saw_cr_at_eol ? "\r" : ""); } -static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, +static void dump_sline(struct sline *sline, const char *line_prefix, + unsigned long cnt, int num_parent, int use_color, int result_deleted) { unsigned long mark = (1UL<<num_parent); @@ -582,7 +583,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, rlines -= null_context; } - fputs(c_frag, stdout); + printf("%s%s", line_prefix, c_frag); for (i = 0; i <= num_parent; i++) putchar(combine_marker); for (i = 0; i < num_parent; i++) show_parent_lno(sline, lno, hunk_end, i, null_context); @@ -614,7 +615,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, struct sline *sl = &sline[lno++]; ll = (sl->flag & no_pre_delete) ? NULL : sl->lost_head; while (ll) { - fputs(c_old, stdout); + printf("%s%s", line_prefix, c_old); for (j = 0; j < num_parent; j++) { if (ll->parent_map & (1UL<<j)) putchar('-'); @@ -627,6 +628,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, if (cnt < lno) break; p_mask = 1; + fputs(line_prefix, stdout); if (!(sl->flag & (mark-1))) { /* * This sline was here to hang the @@ -680,11 +682,13 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, static void dump_quoted_path(const char *head, const char *prefix, const char *path, + const char *line_prefix, const char *c_meta, const char *c_reset) { static struct strbuf buf = STRBUF_INIT; strbuf_reset(&buf); + strbuf_addstr(&buf, line_prefix); strbuf_addstr(&buf, c_meta); strbuf_addstr(&buf, head); quote_two_c_style(&buf, prefix, path, 0); @@ -696,6 +700,7 @@ static void show_combined_header(struct combine_diff_path *elem, int num_parent, int dense, struct rev_info *rev, + const char *line_prefix, int mode_differs, int show_file_header) { @@ -714,8 +719,8 @@ static void show_combined_header(struct combine_diff_path *elem, show_log(rev); dump_quoted_path(dense ? "diff --cc " : "diff --combined ", - "", elem->path, c_meta, c_reset); - printf("%sindex ", c_meta); + "", elem->path, line_prefix, c_meta, c_reset); + printf("%s%sindex ", line_prefix, c_meta); for (i = 0; i < num_parent; i++) { abb = find_unique_abbrev(elem->parent[i].sha1, abbrev); @@ -734,11 +739,12 @@ static void show_combined_header(struct combine_diff_path *elem, DIFF_STATUS_ADDED) added = 0; if (added) - printf("%snew file mode %06o", - c_meta, elem->mode); + printf("%s%snew file mode %06o", + line_prefix, c_meta, elem->mode); else { if (deleted) - printf("%sdeleted file ", c_meta); + printf("%s%sdeleted file ", + line_prefix, c_meta); printf("mode "); for (i = 0; i < num_parent; i++) { printf("%s%06o", i ? "," : "", @@ -755,16 +761,16 @@ static void show_combined_header(struct combine_diff_path *elem, if (added) dump_quoted_path("--- ", "", "/dev/null", - c_meta, c_reset); + line_prefix, c_meta, c_reset); else dump_quoted_path("--- ", a_prefix, elem->path, - c_meta, c_reset); + line_prefix, c_meta, c_reset); if (deleted) dump_quoted_path("+++ ", "", "/dev/null", - c_meta, c_reset); + line_prefix, c_meta, c_reset); else dump_quoted_path("+++ ", b_prefix, elem->path, - c_meta, c_reset); + line_prefix, c_meta, c_reset); } static void show_patch_diff(struct combine_diff_path *elem, int num_parent, @@ -782,6 +788,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, struct userdiff_driver *userdiff; struct userdiff_driver *textconv = NULL; int is_binary; + const char *line_prefix = diff_line_prefix(opt); context = opt->context; userdiff = userdiff_find_by_path(elem->path); @@ -901,7 +908,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, } if (is_binary) { show_combined_header(elem, num_parent, dense, rev, - mode_differs, 0); + line_prefix, mode_differs, 0); printf("Binary files differ\n"); free(result); return; @@ -962,8 +969,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, if (show_hunks || mode_differs || working_tree_file) { show_combined_header(elem, num_parent, dense, rev, - mode_differs, 1); - dump_sline(sline, cnt, num_parent, + line_prefix, mode_differs, 1); + dump_sline(sline, line_prefix, cnt, num_parent, opt->use_color, result_deleted); } free(result); @@ -986,6 +993,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re { struct diff_options *opt = &rev->diffopt; int line_termination, inter_name_termination, i; + const char *line_prefix = diff_line_prefix(opt); line_termination = opt->line_termination; inter_name_termination = '\t'; @@ -995,7 +1003,10 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re if (rev->loginfo && !rev->no_commit_id) show_log(rev); + if (opt->output_format & DIFF_FORMAT_RAW) { + printf("%s", line_prefix); + /* As many colons as there are parents */ for (i = 0; i < num_parent; i++) putchar(':'); @@ -1033,6 +1044,7 @@ void show_combined_diff(struct combine_diff_path *p, struct rev_info *rev) { struct diff_options *opt = &rev->diffopt; + if (!p->len) return; if (opt->output_format & (DIFF_FORMAT_RAW | @@ -1143,8 +1155,10 @@ void diff_tree_combined(const unsigned char *sha1, if (show_log_first && i == 0) { show_log(rev); + if (rev->verbose_header && opt->output_format) - putchar(opt->line_termination); + printf("%s%c", diff_line_prefix(opt), + opt->line_termination); } diff_flush(&diffopts); } @@ -1172,7 +1186,8 @@ void diff_tree_combined(const unsigned char *sha1, if (opt->output_format & DIFF_FORMAT_PATCH) { if (needsep) - putchar(opt->line_termination); + printf("%s%c", diff_line_prefix(opt), + opt->line_termination); for (p = paths; p; p = p->next) { if (p->len) show_patch_diff(p, num_parent, dense, diff --git a/config.mak.uname b/config.mak.uname index bea34f0511..8743a6d0af 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -523,6 +523,7 @@ endif endif ifeq ($(uname_S),QNX) COMPAT_CFLAGS += -DSA_RESTART=0 + EXPAT_NEEDS_XMLPARSE_H = YesPlease HAVE_STRINGS_H = YesPlease NEEDS_SOCKET = YesPlease NO_FNMATCH_CASEFOLD = YesPlease diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index c8452fb163..059ba9d3e4 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1779,7 +1779,7 @@ __git_config_get_set_variables () while [ $c -gt 1 ]; do word="${words[c]}" case "$word" in - --global|--system|--file=*) + --system|--global|--local|--file=*) config_file="$word" break ;; @@ -1885,7 +1885,7 @@ _git_config () case "$cur" in --*) __gitcomp " - --global --system --file= + --system --global --local --file= --list --replace-all --get --get-all --get-regexp --add --unset --unset-all diff --git a/contrib/mw-to-git/.gitignore b/contrib/mw-to-git/.gitignore new file mode 100644 index 0000000000..b9196555e5 --- /dev/null +++ b/contrib/mw-to-git/.gitignore @@ -0,0 +1 @@ +git-remote-mediawiki diff --git a/contrib/mw-to-git/Makefile b/contrib/mw-to-git/Makefile index 3ed728b0ef..f14971987c 100644 --- a/contrib/mw-to-git/Makefile +++ b/contrib/mw-to-git/Makefile @@ -1,47 +1,17 @@ # -# Copyright (C) 2012 -# Charles Roussel <charles.roussel@ensimag.imag.fr> -# Simon Cathebras <simon.cathebras@ensimag.imag.fr> -# Julien Khayat <julien.khayat@ensimag.imag.fr> -# Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr> -# Simon Perrat <simon.perrat@ensimag.imag.fr> +# Copyright (C) 2013 +# Matthieu Moy <Matthieu.Moy@imag.fr> # ## Build git-remote-mediawiki --include ../../config.mak.autogen --include ../../config.mak +SCRIPT_PERL=git-remote-mediawiki.perl +GIT_ROOT_DIR=../.. +HERE=contrib/mw-to-git/ -ifndef PERL_PATH - PERL_PATH = /usr/bin/perl -endif -ifndef gitexecdir - gitexecdir = $(shell git --exec-path) -endif +SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL)) -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) -gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) -SCRIPT = git-remote-mediawiki +all: build -.PHONY: install help doc test clean - -help: - @echo 'This is the help target of the Makefile. Current configuration:' - @echo ' gitexecdir = $(gitexecdir_SQ)' - @echo ' PERL_PATH = $(PERL_PATH_SQ)' - @echo 'Run "$(MAKE) install" to install $(SCRIPT) in gitexecdir' - @echo 'Run "$(MAKE) test" to run the testsuite' - -install: - sed -e '1s|#!.*/perl|#!$(PERL_PATH_SQ)|' $(SCRIPT) \ - > '$(gitexecdir_SQ)/$(SCRIPT)' - chmod +x '$(gitexecdir)/$(SCRIPT)' - -doc: - @echo 'Sorry, "make doc" is not implemented yet for $(SCRIPT)' - -test: - $(MAKE) -C t/ test - -clean: - $(RM) '$(gitexecdir)/$(SCRIPT)' - $(MAKE) -C t/ clean +build install clean: + $(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL=$(SCRIPT_PERL_FULL) \ + $@-perl-script diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki.perl index 094129de09..094129de09 100755 --- a/contrib/mw-to-git/git-remote-mediawiki +++ b/contrib/mw-to-git/git-remote-mediawiki.perl diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile index 05cdd5c9b2..b50750565f 100644 --- a/contrib/subtree/Makefile +++ b/contrib/subtree/Makefile @@ -30,12 +30,13 @@ $(GIT_SUBTREE): $(GIT_SUBTREE_SH) doc: $(GIT_SUBTREE_DOC) install: $(GIT_SUBTREE) - $(INSTALL) -m 755 $(GIT_SUBTREE) $(libexecdir) + $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(libexecdir) install-doc: install-man install-man: $(GIT_SUBTREE_DOC) - $(INSTALL) -m 644 $^ $(man1dir) + $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) + $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir) $(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML) xmlto -m $(MANPAGE_NORMAL_XSL) man $^ diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 920c664bb7..8a23f58ba0 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -9,6 +9,7 @@ if [ $# -eq 0 ]; then fi OPTS_SPEC="\ git subtree add --prefix=<prefix> <commit> +git subtree add --prefix=<prefix> <repository> <commit> git subtree merge --prefix=<prefix> <commit> git subtree pull --prefix=<prefix> <repository> <refspec...> git subtree push --prefix=<prefix> <repository> <refspec...> @@ -296,7 +297,7 @@ copy_commit() # We're going to set some environment vars here, so # do it in a subshell to get rid of them safely later debug copy_commit "{$1}" "{$2}" "{$3}" - git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" | + git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%B' "$1" | ( read GIT_AUTHOR_NAME read GIT_AUTHOR_EMAIL @@ -497,12 +498,23 @@ cmd_add() ensure_clean if [ $# -eq 1 ]; then - "cmd_add_commit" "$@" + git rev-parse -q --verify "$1^{commit}" >/dev/null || + die "'$1' does not refer to a commit" + + "cmd_add_commit" "$@" elif [ $# -eq 2 ]; then - "cmd_add_repository" "$@" + # Technically we could accept a refspec here but we're + # just going to turn around and add FETCH_HEAD under the + # specified directory. Allowing a refspec might be + # misleading because we won't do anything with any other + # branches fetched via the refspec. + git rev-parse -q --verify "$2^{commit}" >/dev/null || + die "'$2' does not refer to a commit" + + "cmd_add_repository" "$@" else say "error: parameters were '$@'" - die "Provide either a refspec or a repository and refspec." + die "Provide either a commit or a repository and commit." fi } diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt index c5bce41ac7..7ba853eeda 100644 --- a/contrib/subtree/git-subtree.txt +++ b/contrib/subtree/git-subtree.txt @@ -9,7 +9,8 @@ git-subtree - Merge subtrees together and split repository into subtrees SYNOPSIS -------- [verse] -'git subtree' add -P <prefix> <commit> +'git subtree' add -P <prefix> <refspec> +'git subtree' add -P <prefix> <repository> <refspec> 'git subtree' pull -P <prefix> <repository> <refspec...> 'git subtree' push -P <prefix> <repository> <refspec...> 'git subtree' merge -P <prefix> <commit> diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index bc2eeb0944..80d339960b 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -60,7 +60,6 @@ last_commit_message() git log --pretty=format:%s -1 } -# 1 test_expect_success 'init subproj' ' test_create_repo subproj ' @@ -68,7 +67,6 @@ test_expect_success 'init subproj' ' # To the subproject! cd subproj -# 2 test_expect_success 'add sub1' ' create sub1 && git commit -m "sub1" && @@ -76,14 +74,16 @@ test_expect_success 'add sub1' ' git branch -m master subproj ' -# 3 +# Save this hash for testing later. + +subdir_hash=`git rev-parse HEAD` + test_expect_success 'add sub2' ' create sub2 && git commit -m "sub2" && git branch sub2 ' -# 4 test_expect_success 'add sub3' ' create sub3 && git commit -m "sub3" && @@ -93,7 +93,6 @@ test_expect_success 'add sub3' ' # Back to mainline cd .. -# 5 test_expect_success 'add main4' ' create main4 && git commit -m "main4" && @@ -101,101 +100,85 @@ test_expect_success 'add main4' ' git branch subdir ' -# 6 test_expect_success 'fetch subproj history' ' git fetch ./subproj sub1 && git branch sub1 FETCH_HEAD ' -# 7 test_expect_success 'no subtree exists in main tree' ' test_must_fail git subtree merge --prefix=subdir sub1 ' -# 8 test_expect_success 'no pull from non-existant subtree' ' test_must_fail git subtree pull --prefix=subdir ./subproj sub1 ' -# 9 test_expect_success 'check if --message works for add' ' git subtree add --prefix=subdir --message="Added subproject" sub1 && check_equal ''"$(last_commit_message)"'' "Added subproject" && undo ' -# 10 test_expect_success 'check if --message works as -m and --prefix as -P' ' git subtree add -P subdir -m "Added subproject using git subtree" sub1 && check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" && undo ' -# 11 test_expect_success 'check if --message works with squash too' ' git subtree add -P subdir -m "Added subproject with squash" --squash sub1 && check_equal ''"$(last_commit_message)"'' "Added subproject with squash" && undo ' -# 12 test_expect_success 'add subproj to mainline' ' git subtree add --prefix=subdir/ FETCH_HEAD && check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'" ' -# 13 # this shouldn't actually do anything, since FETCH_HEAD is already a parent test_expect_success 'merge fetched subproj' ' git merge -m "merge -s -ours" -s ours FETCH_HEAD ' -# 14 test_expect_success 'add main-sub5' ' create subdir/main-sub5 && git commit -m "main-sub5" ' -# 15 test_expect_success 'add main6' ' create main6 && git commit -m "main6 boring" ' -# 16 test_expect_success 'add main-sub7' ' create subdir/main-sub7 && git commit -m "main-sub7" ' -# 17 test_expect_success 'fetch new subproj history' ' git fetch ./subproj sub2 && git branch sub2 FETCH_HEAD ' -# 18 test_expect_success 'check if --message works for merge' ' git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 && check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" && undo ' -# 19 test_expect_success 'check if --message for merge works with squash too' ' git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 && check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" && undo ' -# 20 test_expect_success 'merge new subproj history into subdir' ' git subtree merge --prefix=subdir FETCH_HEAD && git branch pre-split && check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" ' -# 21 test_expect_success 'Check that prefix argument is required for split' ' echo "You must provide the --prefix option." > expected && test_must_fail git subtree split > actual 2>&1 && @@ -207,7 +190,6 @@ test_expect_success 'Check that prefix argument is required for split' ' rm -f expected actual ' -# 22 test_expect_success 'Check that the <prefix> exists for a split' ' echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected && test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 && @@ -219,7 +201,6 @@ test_expect_success 'Check that the <prefix> exists for a split' ' # rm -f expected actual ' -# 23 test_expect_success 'check if --message works for split+rejoin' ' spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && git branch spl1 "$spl1" && @@ -227,15 +208,24 @@ test_expect_success 'check if --message works for split+rejoin' ' undo ' -# 24 test_expect_success 'check split with --branch' ' - spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) && - undo && - git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 && - check_equal ''"$(git rev-parse splitbr1)"'' "$spl1" + spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) && + undo && + git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 && + check_equal ''"$(git rev-parse splitbr1)"'' "$spl1" +' + +test_expect_success 'check hash of split' ' + spl1=$(git subtree split --prefix subdir) && + undo && + git subtree split --prefix subdir --branch splitbr1test && + check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" + git checkout splitbr1test && + new_hash=$(git rev-parse HEAD~2) && + git checkout mainline && + check_equal ''"$new_hash"'' "$subdir_hash" ' -# 25 test_expect_success 'check split with --branch for an existing branch' ' spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && undo && @@ -244,13 +234,10 @@ test_expect_success 'check split with --branch for an existing branch' ' check_equal ''"$(git rev-parse splitbr2)"'' "$spl1" ' -# 26 test_expect_success 'check split with --branch for an incompatible branch' ' test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir ' - -# 27 test_expect_success 'check split+rejoin' ' spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && undo && @@ -258,7 +245,6 @@ test_expect_success 'check split+rejoin' ' check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'" ' -# 28 test_expect_success 'add main-sub8' ' create subdir/main-sub8 && git commit -m "main-sub8" @@ -267,14 +253,12 @@ test_expect_success 'add main-sub8' ' # To the subproject! cd ./subproj -# 29 test_expect_success 'merge split into subproj' ' git fetch .. spl1 && git branch spl1 FETCH_HEAD && git merge FETCH_HEAD ' -# 30 test_expect_success 'add sub9' ' create sub9 && git commit -m "sub9" @@ -283,19 +267,16 @@ test_expect_success 'add sub9' ' # Back to mainline cd .. -# 31 test_expect_success 'split for sub8' ' split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' git branch split2 "$split2" ' -# 32 test_expect_success 'add main-sub10' ' create subdir/main-sub10 && git commit -m "main-sub10" ' -# 33 test_expect_success 'split for sub10' ' spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' && git branch spl3 "$spl3" @@ -304,7 +285,6 @@ test_expect_success 'split for sub10' ' # To the subproject! cd ./subproj -# 34 test_expect_success 'merge split into subproj' ' git fetch .. spl3 && git branch spl3 FETCH_HEAD && @@ -318,13 +298,11 @@ chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl) chks="sub1 sub2 sub3 sub9" chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl) -# 35 test_expect_success 'make sure exactly the right set of files ends up in the subproj' ' subfiles=''"$(git ls-files | fixnl)"'' && check_equal "$subfiles" "$chkms $chks" ' -# 36 test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' ' allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' && check_equal "$allchanges" "$chkms $chks" @@ -333,20 +311,17 @@ test_expect_success 'make sure the subproj history *only* contains commits that # Back to mainline cd .. -# 37 test_expect_success 'pull from subproj' ' git fetch ./subproj subproj-merge-spl3 && git branch subproj-merge-spl3 FETCH_HEAD && git subtree pull --prefix=subdir ./subproj subproj-merge-spl3 ' -# 38 test_expect_success 'make sure exactly the right set of files ends up in the mainline' ' mainfiles=''"$(git ls-files | fixnl)"'' && check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub" ' -# 39 test_expect_success 'make sure each filename changed exactly once in the entire history' ' # main-sub?? and /subdir/main-sub?? both change, because those are the # changes that were split into their own history. And subdir/sub?? never @@ -355,12 +330,10 @@ test_expect_success 'make sure each filename changed exactly once in the entire check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"'' ' -# 40 test_expect_success 'make sure the --rejoin commits never make it into subproj' ' check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' "" ' -# 41 test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' ' # They are meaningless to subproj since one side of the merge refers to the mainline check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' "" @@ -370,14 +343,12 @@ test_expect_success 'make sure no "git subtree" tagged commits make it into subp mkdir test2 cd test2 -# 42 test_expect_success 'init main' ' test_create_repo main ' cd main -# 43 test_expect_success 'add main1' ' create main1 && git commit -m "main1" @@ -385,14 +356,12 @@ test_expect_success 'add main1' ' cd .. -# 44 test_expect_success 'init sub' ' test_create_repo sub ' cd sub -# 45 test_expect_success 'add sub2' ' create sub2 && git commit -m "sub2" @@ -402,7 +371,6 @@ cd ../main # check if split can find proper base without --onto -# 46 test_expect_success 'add sub as subdir in main' ' git fetch ../sub master && git branch sub2 FETCH_HEAD && @@ -411,7 +379,6 @@ test_expect_success 'add sub as subdir in main' ' cd ../sub -# 47 test_expect_success 'add sub3' ' create sub3 && git commit -m "sub3" @@ -419,20 +386,17 @@ test_expect_success 'add sub3' ' cd ../main -# 48 test_expect_success 'merge from sub' ' git fetch ../sub master && git branch sub3 FETCH_HEAD && git subtree merge --prefix subdir sub3 ' -# 49 test_expect_success 'add main-sub4' ' create subdir/main-sub4 && git commit -m "main-sub4" ' -# 50 test_expect_success 'split for main-sub4 without --onto' ' git subtree split --prefix subdir --branch mainsub4 ' @@ -442,19 +406,16 @@ test_expect_success 'split for main-sub4 without --onto' ' # have been sub3, but it was not, because its cache was not set to # itself) -# 51 test_expect_success 'check that the commit parent is sub3' ' check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"'' ' -# 52 test_expect_success 'add main-sub5' ' mkdir subdir2 && create subdir2/main-sub5 && git commit -m "main-sub5" ' -# 53 test_expect_success 'split for main-sub5 without --onto' ' # also test that we still can split out an entirely new subtree # if the parent of the first commit in the tree is not empty, @@ -487,7 +448,6 @@ joincommits() echo "$commit $all" } -# 54 test_expect_success 'verify one file change per commit' ' x= && list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' && @@ -402,12 +402,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res int nofirst; FILE *file = o->file; - if (o->output_prefix) { - struct strbuf *msg = NULL; - msg = o->output_prefix(o, o->output_prefix_data); - assert(msg); - fwrite(msg->buf, msg->len, 1, file); - } + fputs(diff_line_prefix(o), file); if (len == 0) { has_trailing_newline = (first == '\n'); @@ -625,13 +620,7 @@ static void emit_rewrite_diff(const char *name_a, char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; - char *line_prefix = ""; - struct strbuf *msgbuf; - - if (o && o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } + const char *line_prefix = diff_line_prefix(o); if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -827,18 +816,14 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; struct diff_options *opt = diff_words->opt; - struct strbuf *msgbuf; - char *line_prefix = ""; + const char *line_prefix; if (line[0] != '@' || parse_hunk_header(line, len, &minus_first, &minus_len, &plus_first, &plus_len)) return; assert(opt); - if (opt->output_prefix) { - msgbuf = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = msgbuf->buf; - } + line_prefix = diff_line_prefix(opt); /* POSIX requires that first be decremented by one if len == 0... */ if (minus_len) { @@ -962,14 +947,10 @@ static void diff_words_show(struct diff_words_data *diff_words) struct diff_words_style *style = diff_words->style; struct diff_options *opt = diff_words->opt; - struct strbuf *msgbuf; - char *line_prefix = ""; + const char *line_prefix; assert(opt); - if (opt->output_prefix) { - msgbuf = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = msgbuf->buf; - } + line_prefix = diff_line_prefix(opt); /* special case: only removal */ if (!diff_words->plus.text.size) { @@ -1105,6 +1086,16 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } +const char *diff_line_prefix(struct diff_options *opt) +{ + struct strbuf *msgbuf; + if (!opt->output_prefix) + return ""; + + msgbuf = opt->output_prefix(opt, opt->output_prefix_data); + return msgbuf->buf; +} + static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -1145,13 +1136,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); struct diff_options *o = ecbdata->opt; - char *line_prefix = ""; - struct strbuf *msgbuf; - - if (o && o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } + const char *line_prefix = diff_line_prefix(o); if (ecbdata->header) { fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); @@ -1475,16 +1460,11 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) const char *reset, *add_c, *del_c; const char *line_prefix = ""; int extra_shown = 0; - struct strbuf *msg = NULL; if (data->nr == 0) return; - if (options->output_prefix) { - msg = options->output_prefix(options, options->output_prefix_data); - line_prefix = msg->buf; - } - + line_prefix = diff_line_prefix(options); count = options->stat_count ? options->stat_count : data->nr; reset = diff_get_color_opt(options, DIFF_RESET); @@ -1736,12 +1716,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option dels += deleted; } } - if (options->output_prefix) { - struct strbuf *msg = NULL; - msg = options->output_prefix(options, - options->output_prefix_data); - fprintf(options->file, "%s", msg->buf); - } + fprintf(options->file, "%s", diff_line_prefix(options)); print_stat_summary(options->file, total_files, adds, dels); } @@ -1755,12 +1730,7 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - if (options->output_prefix) { - struct strbuf *msg = NULL; - msg = options->output_prefix(options, - options->output_prefix_data); - fprintf(options->file, "%s", msg->buf); - } + fprintf(options->file, "%s", diff_line_prefix(options)); if (file->is_binary) fprintf(options->file, "-\t-\t"); @@ -1802,13 +1772,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, { unsigned long this_dir = 0; unsigned int sources = 0; - const char *line_prefix = ""; - struct strbuf *msg = NULL; - - if (opt->output_prefix) { - msg = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = msg->buf; - } + const char *line_prefix = diff_line_prefix(opt); while (dir->nr) { struct dirstat_file *f = dir->files; @@ -2058,15 +2022,10 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) const char *reset = diff_get_color(data->o->use_color, DIFF_RESET); const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW); char *err; - char *line_prefix = ""; - struct strbuf *msgbuf; + const char *line_prefix; assert(data->o); - if (data->o->output_prefix) { - msgbuf = data->o->output_prefix(data->o, - data->o->output_prefix_data); - line_prefix = msgbuf->buf; - } + line_prefix = diff_line_prefix(data->o); if (line[0] == '+') { unsigned bad; @@ -2123,7 +2082,8 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, + const char *prefix) { void *cp; void *delta; @@ -2184,7 +2144,8 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char free(data); } -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, + const char *prefix) { fprintf(file, "%sGIT binary patch\n", prefix); emit_binary_diff_body(file, one, two, prefix); @@ -2251,13 +2212,7 @@ static void builtin_diff(const char *name_a, struct userdiff_driver *textconv_one = NULL; struct userdiff_driver *textconv_two = NULL; struct strbuf header = STRBUF_INIT; - struct strbuf *msgbuf; - char *line_prefix = ""; - - if (o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } + const char *line_prefix = diff_line_prefix(o); if (DIFF_OPT_TST(o, SUBMODULE_LOG) && (!one->mode || S_ISGITLINK(one->mode)) && @@ -2956,14 +2911,9 @@ static void fill_metainfo(struct strbuf *msg, { const char *set = diff_get_color(use_color, DIFF_METAINFO); const char *reset = diff_get_color(use_color, DIFF_RESET); - struct strbuf *msgbuf; - char *line_prefix = ""; + const char *line_prefix = diff_line_prefix(o); *must_show_header = 1; - if (o->output_prefix) { - msgbuf = o->output_prefix(o, o->output_prefix_data); - line_prefix = msgbuf->buf; - } strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: @@ -3900,12 +3850,8 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) { int line_termination = opt->line_termination; int inter_name_termination = line_termination ? '\t' : '\0'; - if (opt->output_prefix) { - struct strbuf *msg = NULL; - msg = opt->output_prefix(opt, opt->output_prefix_data); - fprintf(opt->file, "%s", msg->buf); - } + fprintf(opt->file, "%s", diff_line_prefix(opt)); if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, diff_unique_abbrev(p->one->sha1, opt->abbrev)); @@ -4175,12 +4121,7 @@ static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_fil static void diff_summary(struct diff_options *opt, struct diff_filepair *p) { FILE *file = opt->file; - char *line_prefix = ""; - - if (opt->output_prefix) { - struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data); - line_prefix = buf->buf; - } + const char *line_prefix = diff_line_prefix(opt); switch(p->status) { case DIFF_STATUS_DELETED: @@ -4481,13 +4422,9 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_PATCH) { if (separator) { - if (options->output_prefix) { - struct strbuf *msg = NULL; - msg = options->output_prefix(options, - options->output_prefix_data); - fwrite(msg->buf, msg->len, 1, stdout); - } - putc(options->line_termination, options->file); + fprintf(options->file, "%s%c", + diff_line_prefix(options), + options->line_termination); if (options->stat_sep) { /* attach patch instead of inline */ fputs(options->stat_sep, options->file); @@ -174,6 +174,9 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix); diff_get_color((o)->use_color, ix) +const char *diff_line_prefix(struct diff_options *); + + extern const char mime_boundary_leader[]; extern void diff_tree_setup_paths(const char **paths, struct diff_options *); diff --git a/git-compat-util.h b/git-compat-util.h index cc2abeea0d..b7eaaa99a8 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -305,13 +305,13 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) /* * Let callers be aware of the constant return value; this can help - * gcc with -Wuninitialized analysis. We have to restrict this trick to - * gcc, though, because of the variadic macro and the magic ## comma pasting - * behavior. But since we're only trying to help gcc, anyway, it's OK; other - * compilers will fall back to using the function as usual. + * gcc with -Wuninitialized analysis. We restrict this trick to gcc, though, + * because some compilers may not support variadic macros. Since we're only + * trying to help gcc, anyway, it's OK; other compilers will fall back to + * using the function as usual. */ #if defined(__GNUC__) && ! defined(__clang__) -#define error(fmt, ...) (error((fmt), ##__VA_ARGS__), -1) +#define error(...) (error(__VA_ARGS__), -1) #endif extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 0a31ebd820..344f1206d1 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -26,6 +26,7 @@ use IO::Socket; use IO::Pipe; use POSIX qw(strftime tzset dup2 ENOENT); use IPC::Open2; +use Git qw(get_tz_offset); $SIG{'PIPE'}="IGNORE"; set_timezone('UTC'); @@ -864,7 +865,9 @@ sub commit { } set_timezone($author_tz); - my $commit_date = strftime("%s %z", localtime($date)); + # $date is in the seconds since epoch format + my $tz_offset = get_tz_offset($date); + my $commit_date = "$date $tz_offset"; set_timezone('UTC'); $ENV{GIT_AUTHOR_NAME} = $author_name; $ENV{GIT_AUTHOR_EMAIL} = $author_email; diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 3d0fe0cd93..b00ed95dba 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -40,7 +40,7 @@ launch_merge_tool () { # the user with the real $MERGED name before launching $merge_tool. if should_prompt then - printf "\nViewing: '$MERGED'\n" + printf "\nViewing: '%s'\n" "$MERGED" if use_ext_cmd then printf "Launch '%s' [Y/n]: " \ diff --git a/git-mergetool.sh b/git-mergetool.sh index c0ee9aaf81..332528ff45 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -403,7 +403,7 @@ then fi printf "Merging:\n" -printf "$files\n" +printf "%s\n" "$files" IFS=' ' diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 8ed7fccc18..048a140a6f 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -80,6 +80,9 @@ rewritten_pending="$state_dir"/rewritten-pending GIT_CHERRY_PICK_HELP="$resolvemsg" export GIT_CHERRY_PICK_HELP +comment_char=$(git config --get core.commentchar 2>/dev/null | cut -c1) +: ${comment_char:=#} + warn () { printf '%s\n' "$*" >&2 } @@ -105,8 +108,8 @@ mark_action_done () { sed -e 1q < "$todo" >> "$done" sed -e 1d < "$todo" >> "$todo".new mv -f "$todo".new "$todo" - new_count=$(sane_grep -c '^[^#]' < "$done") - total=$(($new_count+$(sane_grep -c '^[^#]' < "$todo"))) + new_count=$(git stripspace --strip-comments <"$done" | wc -l) + total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l))) if test "$last_count" != "$new_count" then last_count=$new_count @@ -116,19 +119,19 @@ mark_action_done () { } append_todo_help () { - cat >> "$todo" << EOF -# -# Commands: -# p, pick = use commit -# r, reword = use commit, but edit the commit message -# e, edit = use commit, but stop for amending -# s, squash = use commit, but meld into previous commit -# f, fixup = like "squash", but discard this commit's log message -# x, exec = run command (the rest of the line) using shell -# -# These lines can be re-ordered; they are executed from top to bottom. -# -# If you remove a line here THAT COMMIT WILL BE LOST. + git stripspace --comment-lines >>"$todo" <<\EOF + +Commands: + p, pick = use commit + r, reword = use commit, but edit the commit message + e, edit = use commit, but stop for amending + s, squash = use commit, but meld into previous commit + f, fixup = like "squash", but discard this commit's log message + x, exec = run command (the rest of the line) using shell + +These lines can be re-ordered; they are executed from top to bottom. + +If you remove a line here THAT COMMIT WILL BE LOST. EOF } @@ -179,7 +182,7 @@ die_abort () { } has_action () { - sane_grep '^[^#]' "$1" >/dev/null + test -n "$(git stripspace --strip-comments <"$1")" } is_empty_commit() { @@ -363,10 +366,10 @@ update_squash_messages () { if test -f "$squash_msg"; then mv "$squash_msg" "$squash_msg".bak || exit count=$(($(sed -n \ - -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ + -e "1s/^. This is a combination of \(.*\) commits\./\1/p" \ -e "q" < "$squash_msg".bak)+1)) { - echo "# This is a combination of $count commits." + printf '%s\n' "$comment_char This is a combination of $count commits." sed -e 1d -e '2,/^./{ /^$/d }' <"$squash_msg".bak @@ -375,8 +378,8 @@ update_squash_messages () { commit_message HEAD > "$fixup_msg" || die "Cannot write $fixup_msg" count=2 { - echo "# This is a combination of 2 commits." - echo "# The first commit's message is:" + printf '%s\n' "$comment_char This is a combination of 2 commits." + printf '%s\n' "$comment_char The first commit's message is:" echo cat "$fixup_msg" } >"$squash_msg" @@ -385,21 +388,22 @@ update_squash_messages () { squash) rm -f "$fixup_msg" echo - echo "# This is the $(nth_string $count) commit message:" + printf '%s\n' "$comment_char This is the $(nth_string $count) commit message:" echo commit_message $2 ;; fixup) echo - echo "# The $(nth_string $count) commit message will be skipped:" + printf '%s\n' "$comment_char The $(nth_string $count) commit message will be skipped:" echo - commit_message $2 | sed -e 's/^/# /' + # Change the space after the comment character to TAB: + commit_message $2 | git stripspace --comment-lines | sed -e 's/ / /' ;; esac >>"$squash_msg" } peek_next_command () { - sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$todo" + git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q } # A squash/fixup has failed. Prepare the long version of the squash @@ -464,7 +468,7 @@ do_next () { rm -f "$msg" "$author_script" "$amend" || exit read -r command sha1 rest < "$todo" case "$command" in - '#'*|''|noop) + "$comment_char"*|''|noop) mark_action_done ;; pick|p) @@ -803,15 +807,15 @@ skip) do_rest ;; edit-todo) - sed -e '/^#/d' < "$todo" > "$todo".new + git stripspace --strip-comments <"$todo" >"$todo".new mv -f "$todo".new "$todo" append_todo_help - cat >> "$todo" << EOF -# -# You are editing the todo file of an ongoing interactive rebase. -# To continue rebase after editing, run: -# git rebase --continue -# + git stripspace --comment-lines >>"$todo" <<\EOF + +You are editing the todo file of an ongoing interactive rebase. +To continue rebase after editing, run: + git rebase --continue + EOF git_sequence_editor "$todo" || @@ -881,7 +885,7 @@ do if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1 then - comment_out="# " + comment_out="$comment_char " else comment_out= fi @@ -942,20 +946,20 @@ test -s "$todo" || echo noop >> "$todo" test -n "$autosquash" && rearrange_squash "$todo" test -n "$cmd" && add_exec_commands "$todo" -cat >> "$todo" << EOF +cat >>"$todo" <<EOF -# Rebase $shortrevisions onto $shortonto +$comment_char Rebase $shortrevisions onto $shortonto EOF append_todo_help -cat >> "$todo" << EOF -# -# However, if you remove everything, the rebase will be aborted. -# +git stripspace --comment-lines >>"$todo" <<\EOF + +However, if you remove everything, the rebase will be aborted. + EOF if test -z "$keep_empty" then - echo "# Note that empty commits are commented out" >>"$todo" + printf '%s\n' "$comment_char Note that empty commits are commented out" >>"$todo" fi @@ -1227,6 +1227,16 @@ void graph_show_commit(struct git_graph *graph) if (!graph) return; + /* + * When showing a diff of a merge against each of its parents, we + * are called once for each parent without graph_update having been + * called. In this case, simply output a single padding line. + */ + if (graph_is_commit_finished(graph)) { + graph_show_padding(graph); + shown_commit_line = 1; + } + while (!shown_commit_line && !graph_is_commit_finished(graph)) { shown_commit_line = graph_next_line(graph, &msgbuf); fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); diff --git a/http-push.c b/http-push.c index 3e72e84e84..bd66f6ab6e 100644 --- a/http-push.c +++ b/http-push.c @@ -11,7 +11,11 @@ #include "list-objects.h" #include "sigchain.h" +#ifdef EXPAT_NEEDS_XMLPARSE_H +#include <xmlparse.h> +#else #include <expat.h> +#endif static const char http_push_usage[] = "git http-push [--all] [--dry-run] [--force] [--verbose] <remote> [<head>...]\n"; diff --git a/mergetools/p4merge b/mergetools/p4merge index 52f7c8f705..8a36916567 100644 --- a/mergetools/p4merge +++ b/mergetools/p4merge @@ -30,5 +30,5 @@ create_empty_file () { empty_file="${TMPDIR:-/tmp}/git-difftool-p4merge-empty-file.$$" >"$empty_file" - printf "$empty_file" + printf "%s" "$empty_file" } diff --git a/parse-options.c b/parse-options.c index 67e98a6323..c2cbca25cc 100644 --- a/parse-options.c +++ b/parse-options.c @@ -3,6 +3,7 @@ #include "cache.h" #include "commit.h" #include "color.h" +#include "utf8.h" static int parse_options_usage(struct parse_opt_ctx_t *ctx, const char * const *usagestr, @@ -461,8 +462,11 @@ int parse_options(int argc, const char **argv, const char *prefix, default: /* PARSE_OPT_UNKNOWN */ if (ctx.argv[0][1] == '-') { error("unknown option `%s'", ctx.argv[0] + 2); - } else { + } else if (isascii(*ctx.opt)) { error("unknown switch `%c'", *ctx.opt); + } else { + error("unknown non-ascii option in string: `%s'", + ctx.argv[0]); } usage_with_options(usagestr, options); } @@ -482,7 +486,7 @@ static int usage_argh(const struct option *opts, FILE *outfile) s = literal ? "[%s]" : "[<%s>]"; else s = literal ? " %s" : " <%s>"; - return fprintf(outfile, s, opts->argh ? _(opts->argh) : _("...")); + return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("...")); } #define USAGE_OPTS_WIDTH 24 @@ -541,7 +545,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, if (opts->long_name) pos += fprintf(outfile, "--%s", opts->long_name); if (opts->type == OPTION_NUMBER) - pos += fprintf(outfile, "-NUM"); + pos += utf8_fprintf(outfile, _("-NUM")); if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !(opts->flags & PARSE_OPT_NOARG)) diff --git a/perl/Git.pm b/perl/Git.pm index 931047c51d..a56d1e76f7 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -59,6 +59,7 @@ require Exporter; command_bidi_pipe command_close_bidi_pipe version exec_path html_path hash_object git_cmd_try remote_refs prompt + get_tz_offset temp_acquire temp_release temp_reset temp_path); @@ -102,6 +103,7 @@ use Error qw(:try); use Cwd qw(abs_path cwd); use IPC::Open2 qw(open2); use Fcntl qw(SEEK_SET SEEK_CUR); +use Time::Local qw(timegm); } @@ -511,6 +513,27 @@ C<git --html-path>). Useful mostly only internally. sub html_path { command_oneline('--html-path') } + +=item get_tz_offset ( TIME ) + +Return the time zone offset from GMT in the form +/-HHMM where HH is +the number of hours from GMT and MM is the number of minutes. This is +the equivalent of what strftime("%z", ...) would provide on a GNU +platform. + +If TIME is not supplied, the current local time is used. + +=cut + +sub get_tz_offset { + # some systmes don't handle or mishandle %z, so be creative. + my $t = shift || time; + my $gm = timegm(localtime($t)); + my $sign = qw( + + - )[ $gm <=> $t ]; + return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]); +} + + =item prompt ( PROMPT , ISPASSWORD ) Query user C<PROMPT> and return answer from user. diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm index 490e330380..0ebc68ac7e 100644 --- a/perl/Git/SVN.pm +++ b/perl/Git/SVN.pm @@ -11,7 +11,6 @@ use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; use IPC::Open3; -use Time::Local; use Memoize; # core since 5.8.0, Jul 2002 use Memoize::Storable; use POSIX qw(:signal_h); @@ -22,6 +21,7 @@ use Git qw( command_noisy command_output_pipe command_close_pipe + get_tz_offset ); use Git::SVN::Utils qw( fatal @@ -1311,14 +1311,6 @@ sub get_untracked { \@out; } -sub get_tz { - # some systmes don't handle or mishandle %z, so be creative. - my $t = shift || time; - my $gm = timelocal(gmtime($t)); - my $sign = qw( + + - )[ $t <=> $gm ]; - return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]); -} - # parse_svn_date(DATE) # -------------------- # Given a date (in UTC) from Subversion, return a string in the format @@ -1351,7 +1343,7 @@ sub parse_svn_date { delete $ENV{TZ}; } - my $our_TZ = get_tz(); + my $our_TZ = get_tz_offset(); # This converts $epoch_in_UTC into our local timezone. my ($sec, $min, $hour, $mday, $mon, $year, diff --git a/perl/Git/SVN/Log.pm b/perl/Git/SVN/Log.pm index 3cc1c6f081..3f8350a57d 100644 --- a/perl/Git/SVN/Log.pm +++ b/perl/Git/SVN/Log.pm @@ -2,7 +2,11 @@ package Git::SVN::Log; use strict; use warnings; use Git::SVN::Utils qw(fatal); -use Git qw(command command_oneline command_output_pipe command_close_pipe); +use Git qw(command + command_oneline + command_output_pipe + command_close_pipe + get_tz_offset); use POSIX qw/strftime/; use constant commit_log_separator => ('-' x 72) . "\n"; use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline @@ -119,7 +123,7 @@ sub run_pager { sub format_svn_date { my $t = shift || time; require Git::SVN; - my $gmoff = Git::SVN::get_tz($t); + my $gmoff = get_tz_offset($t); return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t)); } diff --git a/revision.c b/revision.c index d7562ee500..ef60205412 100644 --- a/revision.c +++ b/revision.c @@ -2268,7 +2268,10 @@ static int commit_rewrite_person(struct strbuf *buf, const char *what, struct st static int commit_match(struct commit *commit, struct rev_info *opt) { int retval; + const char *encoding; + char *message; struct strbuf buf = STRBUF_INIT; + if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list) return 1; @@ -2279,13 +2282,23 @@ static int commit_match(struct commit *commit, struct rev_info *opt) strbuf_addch(&buf, '\n'); } + /* + * We grep in the user's output encoding, under the assumption that it + * is the encoding they are most likely to write their grep pattern + * for. In addition, it means we will match the "notes" encoding below, + * so we will not end up with a buffer that has two different encodings + * in it. + */ + encoding = get_log_output_encoding(); + message = logmsg_reencode(commit, encoding); + /* Copy the commit to temporary if we are using "fake" headers */ if (buf.len) - strbuf_addstr(&buf, commit->buffer); + strbuf_addstr(&buf, message); if (opt->grep_filter.header_list && opt->mailmap) { if (!buf.len) - strbuf_addstr(&buf, commit->buffer); + strbuf_addstr(&buf, message); commit_rewrite_person(&buf, "\nauthor ", opt->mailmap); commit_rewrite_person(&buf, "\ncommitter ", opt->mailmap); @@ -2294,18 +2307,18 @@ static int commit_match(struct commit *commit, struct rev_info *opt) /* Append "fake" message parts as needed */ if (opt->show_notes) { if (!buf.len) - strbuf_addstr(&buf, commit->buffer); - format_display_notes(commit->object.sha1, &buf, - get_log_output_encoding(), 1); + strbuf_addstr(&buf, message); + format_display_notes(commit->object.sha1, &buf, encoding, 1); } - /* Find either in the commit object, or in the temporary */ + /* Find either in the original commit message, or in the temporary */ if (buf.len) retval = grep_buffer(&opt->grep_filter, buf.buf, buf.len); else retval = grep_buffer(&opt->grep_filter, - commit->buffer, strlen(commit->buffer)); + message, strlen(message)); strbuf_release(&buf); + logmsg_free(message, commit); return retval; } diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 8462be1db6..15dcbd42d3 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -934,4 +934,18 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' ' test L = $(git cat-file commit HEAD | sed -ne \$p) ' +test_expect_success 'rebase -i respects core.commentchar' ' + git reset --hard && + git checkout E^0 && + git config core.commentchar "\\" && + test_when_finished "git config --unset core.commentchar" && + write_script remove-all-but-first.sh <<-\EOF && + sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" && + mv "$1.tmp" "$1" + EOF + test_set_editor "$(pwd)/remove-all-but-first.sh" && + git rebase -i B && + test B = $(git cat-file commit HEAD^ | sed -ne \$p) +' + test_done diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh new file mode 100755 index 0000000000..52a74729ba --- /dev/null +++ b/t/t4210-log-i18n.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='test log with i18n features' +. ./test-lib.sh + +# two forms of é +utf8_e=$(printf '\303\251') +latin1_e=$(printf '\351') + +test_expect_success 'create commits in different encodings' ' + test_tick && + cat >msg <<-EOF && + utf8 + + t${utf8_e}st + EOF + git add msg && + git -c i18n.commitencoding=utf8 commit -F msg && + cat >msg <<-EOF && + latin1 + + t${latin1_e}st + EOF + git add msg && + git -c i18n.commitencoding=ISO-8859-1 commit -F msg +' + +test_expect_success 'log --grep searches in log output encoding (utf8)' ' + cat >expect <<-\EOF && + latin1 + utf8 + EOF + git log --encoding=utf8 --format=%s --grep=$utf8_e >actual && + test_cmp expect actual +' + +test_expect_success 'log --grep searches in log output encoding (latin1)' ' + cat >expect <<-\EOF && + latin1 + utf8 + EOF + git log --encoding=ISO-8859-1 --format=%s --grep=$latin1_e >actual && + test_cmp expect actual +' + +test_expect_success 'log --grep does not find non-reencoded values (utf8)' ' + >expect && + git log --encoding=utf8 --format=%s --grep=$latin1_e >actual && + test_cmp expect actual +' + +test_expect_success 'log --grep does not find non-reencoded values (latin1)' ' + >expect && + git log --encoding=ISO-8859-1 --format=%s --grep=$utf8_e >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index b3f6eb9c68..51ab89411f 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -73,10 +73,11 @@ test_expect_success 'prepare for rebase conflicts' ' test_expect_success 'status when rebase in progress before resolving conflicts' ' test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently rebasing. + # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) # (use "git rebase --abort" to check out the original branch) @@ -97,12 +98,13 @@ test_expect_success 'status when rebase in progress before resolving conflicts' test_expect_success 'status when rebase in progress before rebase --continue' ' git reset --hard rebase_conflicts && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && echo three >main.txt && git add main.txt && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently rebasing. + # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # # Changes to be committed: @@ -130,10 +132,11 @@ test_expect_success 'prepare for rebase_i_conflicts' ' test_expect_success 'status during rebase -i when conflicts unresolved' ' test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short rebase_i_conflicts) && test_must_fail git rebase -i rebase_i_conflicts && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently rebasing. + # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) # (use "git rebase --abort" to check out the original branch) @@ -154,11 +157,12 @@ test_expect_success 'status during rebase -i when conflicts unresolved' ' test_expect_success 'status during rebase -i after resolving conflicts' ' git reset --hard rebase_i_conflicts_second && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short rebase_i_conflicts) && test_must_fail git rebase -i rebase_i_conflicts && git add main.txt && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently rebasing. + # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # # Changes to be committed: @@ -182,10 +186,11 @@ test_expect_success 'status when rebasing -i in edit mode' ' FAKE_LINES="1 edit 2" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~2) && git rebase -i HEAD~2 && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -206,11 +211,12 @@ test_expect_success 'status when splitting a commit' ' FAKE_LINES="1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently splitting a commit during a rebase. + # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # # Changes not staged for commit: @@ -236,11 +242,12 @@ test_expect_success 'status after editing the last commit with --amend during a FAKE_LINES="1 2 edit 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git commit --amend -m "foo" && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -265,11 +272,12 @@ test_expect_success 'status: (continue first edit) second edit' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git rebase --continue && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -285,12 +293,13 @@ test_expect_success 'status: (continue first edit) second edit and split' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git rebase --continue && git reset HEAD^ && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently splitting a commit during a rebase. + # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # # Changes not staged for commit: @@ -311,12 +320,13 @@ test_expect_success 'status: (continue first edit) second edit and amend' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git rebase --continue && git commit --amend -m "foo" && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -332,12 +342,13 @@ test_expect_success 'status: (amend first edit) second edit' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git commit --amend -m "a" && git rebase --continue && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -353,13 +364,14 @@ test_expect_success 'status: (amend first edit) second edit and split' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git commit --amend -m "b" && git rebase --continue && git reset HEAD^ && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently splitting a commit during a rebase. + # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # # Changes not staged for commit: @@ -380,13 +392,14 @@ test_expect_success 'status: (amend first edit) second edit and amend' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git commit --amend -m "c" && git rebase --continue && git commit --amend -m "d" && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -402,14 +415,15 @@ test_expect_success 'status: (split first edit) second edit' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && git add main.txt && git commit -m "e" && git rebase --continue && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -425,15 +439,16 @@ test_expect_success 'status: (split first edit) second edit and split' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && git add main.txt && git commit --amend -m "f" && git rebase --continue && git reset HEAD^ && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently splitting a commit during a rebase. + # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # # Changes not staged for commit: @@ -454,15 +469,16 @@ test_expect_success 'status: (split first edit) second edit and amend' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && git add main.txt && git commit --amend -m "g" && git rebase --continue && git commit --amend -m "h" && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently editing a commit during a rebase. + # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) # @@ -558,7 +574,7 @@ test_expect_success 'status when bisecting' ' git bisect good one_bisect && cat >expected <<-\EOF && # Not currently on any branch. - # You are currently bisecting. + # You are currently bisecting branch '\''bisect'\''. # (use "git bisect reset" to get back to the original branch) # nothing to commit (use -u to show untracked files) @@ -577,10 +593,11 @@ test_expect_success 'status when rebase conflicts with statushints disabled' ' test_commit two_statushints main.txt two && test_commit three_statushints main.txt three && test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && - cat >expected <<-\EOF && + cat >expected <<-EOF && # Not currently on any branch. - # You are currently rebasing. + # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''. # # Unmerged paths: # both modified: main.txt @@ -430,6 +430,27 @@ int same_encoding(const char *src, const char *dst) } /* + * Wrapper for fprintf and returns the total number of columns required + * for the printed string, assuming that the string is utf8. + */ +int utf8_fprintf(FILE *stream, const char *format, ...) +{ + struct strbuf buf = STRBUF_INIT; + va_list arg; + int columns; + + va_start(arg, format); + strbuf_vaddf(&buf, format, arg); + va_end(arg); + + columns = fputs(buf.buf, stream); + if (0 <= columns) /* keep the error from the I/O */ + columns = utf8_strwidth(buf.buf); + strbuf_release(&buf); + return columns; +} + +/* * Given a buffer and its encoding, return it re-encoded * with iconv. If the conversion fails, returns NULL. */ @@ -8,6 +8,7 @@ int utf8_strwidth(const char *string); int is_utf8(const char *text); int is_encoding_utf8(const char *name); int same_encoding(const char *, const char *); +int utf8_fprintf(FILE *, const char *, ...); void strbuf_add_wrapped_text(struct strbuf *buf, const char *text, int indent, int indent2, int width); diff --git a/wt-status.c b/wt-status.c index aa2734fcbe..ef405d03d9 100644 --- a/wt-status.c +++ b/wt-status.c @@ -874,7 +874,14 @@ static void show_rebase_in_progress(struct wt_status *s, struct stat st; if (has_unmerged(s)) { - status_printf_ln(s, color, _("You are currently rebasing.")); + if (state->branch) + status_printf_ln(s, color, + _("You are currently rebasing branch '%s' on '%s'."), + state->branch, + state->onto); + else + status_printf_ln(s, color, + _("You are currently rebasing.")); if (advice_status_hints) { status_printf_ln(s, color, _(" (fix conflicts and then run \"git rebase --continue\")")); @@ -884,17 +891,38 @@ static void show_rebase_in_progress(struct wt_status *s, _(" (use \"git rebase --abort\" to check out the original branch)")); } } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) { - status_printf_ln(s, color, _("You are currently rebasing.")); + if (state->branch) + status_printf_ln(s, color, + _("You are currently rebasing branch '%s' on '%s'."), + state->branch, + state->onto); + else + status_printf_ln(s, color, + _("You are currently rebasing.")); if (advice_status_hints) status_printf_ln(s, color, _(" (all conflicts fixed: run \"git rebase --continue\")")); } else if (split_commit_in_progress(s)) { - status_printf_ln(s, color, _("You are currently splitting a commit during a rebase.")); + if (state->branch) + status_printf_ln(s, color, + _("You are currently splitting a commit while rebasing branch '%s' on '%s'."), + state->branch, + state->onto); + else + status_printf_ln(s, color, + _("You are currently splitting a commit during a rebase.")); if (advice_status_hints) status_printf_ln(s, color, _(" (Once your working directory is clean, run \"git rebase --continue\")")); } else { - status_printf_ln(s, color, _("You are currently editing a commit during a rebase.")); + if (state->branch) + status_printf_ln(s, color, + _("You are currently editing a commit while rebasing branch '%s' on '%s'."), + state->branch, + state->onto); + else + status_printf_ln(s, color, + _("You are currently editing a commit during a rebase.")); if (advice_status_hints && !s->amend) { status_printf_ln(s, color, _(" (use \"git commit --amend\" to amend the current commit)")); @@ -925,16 +953,57 @@ static void show_bisect_in_progress(struct wt_status *s, struct wt_status_state *state, const char *color) { - status_printf_ln(s, color, _("You are currently bisecting.")); + if (state->branch) + status_printf_ln(s, color, + _("You are currently bisecting branch '%s'."), + state->branch); + else + status_printf_ln(s, color, + _("You are currently bisecting.")); if (advice_status_hints) status_printf_ln(s, color, _(" (use \"git bisect reset\" to get back to the original branch)")); wt_status_print_trailer(s); } +/* + * Extract branch information from rebase/bisect + */ +static void read_and_strip_branch(struct strbuf *sb, + const char **branch, + const char *path) +{ + unsigned char sha1[20]; + + strbuf_reset(sb); + if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0) + return; + + while (sb->len && sb->buf[sb->len - 1] == '\n') + strbuf_setlen(sb, sb->len - 1); + if (!sb->len) + return; + if (!prefixcmp(sb->buf, "refs/heads/")) + *branch = sb->buf + strlen("refs/heads/"); + else if (!prefixcmp(sb->buf, "refs/")) + *branch = sb->buf; + else if (!get_sha1_hex(sb->buf, sha1)) { + const char *abbrev; + abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV); + strbuf_reset(sb); + strbuf_addstr(sb, abbrev); + *branch = sb->buf; + } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */ + ; + else /* bisect */ + *branch = sb->buf; +} + static void wt_status_print_state(struct wt_status *s) { const char *state_color = color(WT_STATUS_HEADER, s); + struct strbuf branch = STRBUF_INIT; + struct strbuf onto = STRBUF_INIT; struct wt_status_state state; struct stat st; @@ -949,17 +1018,28 @@ static void wt_status_print_state(struct wt_status *s) state.am_empty_patch = 1; } else { state.rebase_in_progress = 1; + read_and_strip_branch(&branch, &state.branch, + "rebase-apply/head-name"); + read_and_strip_branch(&onto, &state.onto, + "rebase-apply/onto"); } } else if (!stat(git_path("rebase-merge"), &st)) { if (!stat(git_path("rebase-merge/interactive"), &st)) state.rebase_interactive_in_progress = 1; else state.rebase_in_progress = 1; + read_and_strip_branch(&branch, &state.branch, + "rebase-merge/head-name"); + read_and_strip_branch(&onto, &state.onto, + "rebase-merge/onto"); } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) { state.cherry_pick_in_progress = 1; } - if (!stat(git_path("BISECT_LOG"), &st)) + if (!stat(git_path("BISECT_LOG"), &st)) { state.bisect_in_progress = 1; + read_and_strip_branch(&branch, &state.branch, + "BISECT_START"); + } if (state.merge_in_progress) show_merge_in_progress(s, &state, state_color); @@ -971,6 +1051,8 @@ static void wt_status_print_state(struct wt_status *s) show_cherry_pick_in_progress(s, &state, state_color); if (state.bisect_in_progress) show_bisect_in_progress(s, &state, state_color); + strbuf_release(&branch); + strbuf_release(&onto); } void wt_status_print(struct wt_status *s) diff --git a/wt-status.h b/wt-status.h index 236b41fd34..81e1dcf84d 100644 --- a/wt-status.h +++ b/wt-status.h @@ -79,6 +79,8 @@ struct wt_status_state { int rebase_interactive_in_progress; int cherry_pick_in_progress; int bisect_in_progress; + const char *branch; + const char *onto; }; void wt_status_prepare(struct wt_status *s); |