diff options
69 files changed, 3144 insertions, 585 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile index 5d76a84078..063fa696c9 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -66,12 +66,6 @@ endif -include ../config.mak # -# For asciidoc ... -# -7.1.2, set ASCIIDOC7 -# 8.0-, no extra settings are needed -# - -# # For docbook-xsl ... # -1.68.1, no extra settings are needed? # 1.69.0, set ASCIIDOC_ROFF? @@ -81,9 +75,6 @@ endif # 1.73.0-, no extra settings are needed # -ifndef ASCIIDOC7 -ASCIIDOC_EXTRA += -a asciidoc7compatible -endif ifdef DOCBOOK_XSL_172 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff MANPAGE_XSL = manpage-1.72.xsl @@ -134,15 +125,6 @@ DEFAULT_EDITOR_SQ = $(subst ','\'',$(DEFAULT_EDITOR)) ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)' endif -# -# Please note that there is a minor bug in asciidoc. -# The version after 6.0.3 _will_ include the patch found here: -# http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2 -# -# Until that version is released you may have to apply the patch -# yourself - yes, all 6 characters of it! -# - QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = diff --git a/Documentation/RelNotes/1.7.12.txt b/Documentation/RelNotes/1.7.12.txt new file mode 100644 index 0000000000..f49eef51b7 --- /dev/null +++ b/Documentation/RelNotes/1.7.12.txt @@ -0,0 +1,108 @@ +Git v1.7.12 Release Notes +========================= + +Updates since v1.7.11 +--------------------- + +UI, Workflows & Features + + * "git help" used to always default to "man" format even on platforms + where "man" viewer is not widely available. + + * "git clone --local $path" started its life as an experiment to + optionally use link/copy when cloning a repository on the disk, but + we didn't deprecate it after we made the option a no-op to always + use the optimization. The command learned "--no-local" option to + turn this off, as a more explicit alternative over use of file:// + URL. + + * git native protocol agents learned to show software version over + the wire, so that the server log can be examined to see the vintage + distribution of clients. + + * "git rebase -i" learned "-x <cmd>" to insert "exec <cmd>" after + each commit in the resulting history. + + * "git status" gives finer classification to various states of paths + in conflicted state and offer advice messages in its output. + + * "git submodule" learned to deal with nested submodule structure + where a module is contained within a module whose origin is + specified as a relative URL to its superproject's origin. + + * A rather heavy-ish "git completion" script has been split to create + a separate "git prompting" script, to help lazy-autoloading of the + completion part while making prompting part always available. + + +Foreign Interface + + +Performance, Internal Implementation, etc. (please report possible regressions) + + * Some tests showed false failures caused by a bug in ecryptofs. + + * We no longer use AsciiDoc7 syntax in our documentation and favor a + more modern style. + + * "git index-pack" and "git pack-objects" use streaming API to read + from the object store to avoid having to hold a large blob object + in-core while they are doing their thing. + + * Code to match paths with exclude patterns learned to avoid calling + fnmatch() by comparing fixed leading substring literally when + possible. + + +Also contains minor documentation updates and code clean-ups. + + +Fixes since v1.7.11 +------------------- + +Unless otherwise noted, all the fixes since v1.7.11 in the maintenance +releases are contained in this release (see release notes to them for +details). + + * When "git log" gets "--simplify-merges/by-decoration" together with + "--first-parent", the combination of these options makes the + simplification logic to use in-core commit objects that haven't + been examined for relevance, either producing incorrect result or + taking too long to produce any output. Teach the simplification + logic to ignore commits that the first-parent traversal logic + ignored when both are in effect to work around the issue. + (merge 6e513ba jc/rev-list-simplify-merges-first-parent later to maint). + + * "git add" allows adding a regular file to the path where a + submodule used to exist, but "git update-index" does not allow an + equivalent operation to Porcelain writers. + (merge 242f55f hv/submodule-update-nuke-submodules later to maint). + + * "git diff --no-index" did not work with pagers correctly. + (merge af63b54 jk/diff-no-index-pager later to maint). + + * "git diff COPYING HEAD:COPYING" gave a nonsense error message that + claimed that the treeish HEAD did not have COPYING in it. + (merge 023e37c mm/verify-filename-fix later to maint). + + * The documentation for "git cherry-pick A B..C" was misleading. + (merge b98878e cn/cherry-pick-range-docs later to maint). + + * "git archive" incorrectly computed the header checksum; the symptom + was observed only when using pathnames with hi-bit set. + (merge a5a46eb jc/ustar-checksum-is-unsigned later to maint). + + * Running "git bundle verify" on a bundle that records a complete + history said "it requires these 0 commits". + (merge 8c3710f jc/bundle-complete-notice later to maint). + + * "git ls-files --exclude=t -i" did not consider anything under t/ as + excluded, as it did not pay attention to exclusion of leading paths + while walking the index. Other two users of excluded() are also + updated. + (merge 0d316f0 jc/ls-files-i-dir later to maint). + + * "git request-pull $url dev" when the tip of "dev" branch was tagged + with "ext4-for-linus" used the contents from the tag in the output + but still asked the "dev" branch to be pulled, not the tag. + (merge 682853e jc/request-pull-match-tagname later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 915cb5a547..ccce4f6697 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -159,9 +159,10 @@ advice.*:: specified a refspec that isn't your current branch) and it resulted in a non-fast-forward error. statusHints:: - Directions on how to stage/unstage/add shown in the - output of linkgit:git-status[1] and the template shown - when writing commit messages. + Show directions on how to proceed from the current + state in the output of linkgit:git-status[1] and in + the template shown when writing commit messages in + linkgit:git-commit[1]. commitBeforeMerge:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwriting local changes. diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 9f3dae631e..0e170a51ca 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -47,7 +47,9 @@ OPTIONS linkgit:gitrevisions[7]. Sets of commits can be passed but no traversal is done by default, as if the '--no-walk' option was specified, see - linkgit:git-rev-list[1]. + linkgit:git-rev-list[1]. Note that specifying a range will + feed all <commit>... arguments to a single revision walk + (see a later example that uses 'maint master..next'). -e:: --edit:: @@ -149,6 +151,15 @@ EXAMPLES Apply the changes introduced by all commits that are ancestors of master but not of HEAD to produce new commits. +`git cherry-pick maint next ^master`:: +`git cherry-pick maint master..next`:: + + Apply the changes introduced by all commits that are + ancestors of maint or next, but not master or any of its + ancestors. Note that the latter does not mean `maint` and + everything between `master` and `next`; specifically, + `maint` will not be used if it is included in `master`. + `git cherry-pick master~4 master~2`:: Apply the changes introduced by the fifth and third last diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 6e22522c4f..c1ddd4c2cc 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -46,13 +46,18 @@ OPTIONS mechanism and clones the repository by making a copy of HEAD and everything under objects and refs directories. The files under `.git/objects/` directory are hardlinked - to save space when possible. This is now the default when - the source repository is specified with `/path/to/repo` - syntax, so it essentially is a no-op option. To force - copying instead of hardlinking (which may be desirable - if you are trying to make a back-up of your repository), - but still avoid the usual "git aware" transport - mechanism, `--no-hardlinks` can be used. + to save space when possible. ++ +If the repository is specified as a local path (e.g., `/path/to/repo`), +this is the default, and --local is essentially a no-op. If the +repository is specified as a URL, then this flag is ignored (and we +never use the local optimizations). Specifying `--no-local` will +override the default when `/path/to/repo` is given, using the regular +git transport instead. ++ +To force copying instead of hardlinking (which may be desirable if you +are trying to make a back-up of your repository), but still avoid the +usual "git aware" transport mechanism, `--no-hardlinks` can be used. --no-hardlinks:: Optimize the cloning process from a repository on a diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 2d695f619c..f400835921 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -101,12 +101,16 @@ OPTIONS When doing a dry-run, give the output in the short-format. See linkgit:git-status[1] for details. Implies `--dry-run`. +--branch:: + Show the branch and tracking info even in short-format. + --porcelain:: When doing a dry-run, give the output in a porcelain-ready format. See linkgit:git-status[1] for details. Implies `--dry-run`. -z:: +--null:: When showing `short` or `porcelain` status output, terminate entries in the status output with NUL, instead of LF. If no format is given, implies the `--porcelain` output format. @@ -189,6 +193,10 @@ OPTIONS current tip -- if it was a merge, it will have the parents of the current tip as parents -- so the current top commit is discarded. + +--no-post-rewrite:: + Bypass the post-rewrite hook. + + -- It is a rough equivalent for: diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 147fa1a8e0..2d71e4b975 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,9 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [options] [--onto <newbase>] +'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>] -'git rebase' [-i | --interactive] [options] --onto <newbase> +'git rebase' [-i | --interactive] [options] [--exec <cmd>] --onto <newbase> --root [<branch>] 'git rebase' --continue | --skip | --abort @@ -210,7 +210,7 @@ rebase.autosquash:: OPTIONS ------- -<newbase>:: +--onto <newbase>:: Starting point at which to create the new commits. If the --onto option is not specified, the starting point is <upstream>. May be any valid commit, and not just an @@ -344,6 +344,27 @@ This uses the `--interactive` machinery internally, but combining it with the `--interactive` option explicitly is generally not a good idea unless you know what you are doing (see BUGS below). +-x <cmd>:: +--exec <cmd>:: + Append "exec <cmd>" after each line creating a commit in the + final history. <cmd> will be interpreted as one or more shell + commands. ++ +This option can only be used with the `--interactive` option +(see INTERACTIVE MODE below). ++ +You may execute several commands by either using one instance of `--exec` +with several commands: ++ + git rebase -i --exec "cmd1 && cmd2 && ..." ++ +or by giving more than one `--exec`: ++ + git rebase -i --exec "cmd1" --exec "cmd2" --exec ... ++ +If `--autosquash` is used, "exec" lines will not be appended for +the intermediate commits, and will only appear at the end of each +squash/fixup series. --root:: Rebase all commits reachable from <branch>, instead of @@ -521,6 +542,24 @@ in `$SHELL`, or the default shell if `$SHELL` is not set), so you can use shell features (like "cd", ">", ";" ...). The command is run from the root of the working tree. +---------------------------------- +$ git rebase -i --exec "make test" +---------------------------------- + +This command lets you check that intermediate commits are compilable. +The todo list becomes like that: + +-------------------- +pick 5928aea one +exec make test +pick 04d0fda two +exec make test +pick ba46169 three +exec make test +pick f4593f9 four +exec make test +-------------------- + SPLITTING COMMITS ----------------- diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 1ae3c899ef..84e34b1aba 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -622,6 +622,7 @@ These options are mostly targeted for packing of git repositories. --no-walk:: Only show the given revs, but do not traverse their ancestors. + This has no effect if a range is specified. --do-walk:: diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index a7570decfb..fde74a68d5 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.11.1 +DEF_VER=v1.7.11.GIT LF=' ' @@ -203,8 +203,6 @@ all:: # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks # field that counts the on-disk footprint in 512-byte blocks. # -# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7 -# # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72 # (not v1.73 or v1.71). # @@ -296,6 +294,13 @@ all:: # the diff algorithm. It gives a nice speedup if your processor has # fast unaligned word loads. Does NOT work on big-endian systems! # Enabled by default on x86_64. +# +# Define GIT_USER_AGENT if you want to change how git identifies itself during +# network interactions. The default is "git/$(GIT_VERSION)". +# +# Define DEFAULT_HELP_FORMAT to "man", "info" or "html" +# (defaults to "man") if you want to have a different default when +# "git help" is called without a parameter specifying the format. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -799,6 +804,7 @@ LIB_OBJS += usage.o LIB_OBJS += userdiff.o LIB_OBJS += utf8.o LIB_OBJS += varint.o +LIB_OBJS += version.o LIB_OBJS += walker.o LIB_OBJS += wrapper.o LIB_OBJS += write_or_die.o @@ -904,6 +910,8 @@ BUILTIN_OBJS += builtin/write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = +GIT_USER_AGENT = git/$(GIT_VERSION) + # # Platform specific tweaks # @@ -1236,6 +1244,7 @@ ifeq ($(uname_S),Windows) BLK_SHA1 = YesPlease NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease + DEFAULT_HELP_FORMAT = html CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -1834,10 +1843,6 @@ ifndef V endif endif -ifdef ASCIIDOC7 - export ASCIIDOC7 -endif - ifdef NO_INSTALL_HARDLINKS export NO_INSTALL_HARDLINKS endif @@ -1915,6 +1920,15 @@ SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ)) BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)' endif +GIT_USER_AGENT_SQ = $(subst ','\'',$(GIT_USER_AGENT)) +GIT_USER_AGENT_CQ = "$(subst ",\",$(subst \,\\,$(GIT_USER_AGENT)))" +GIT_USER_AGENT_CQ_SQ = $(subst ','\'',$(GIT_USER_AGENT_CQ)) +BASIC_CFLAGS += -DGIT_USER_AGENT='$(GIT_USER_AGENT_CQ_SQ)' + +ifdef DEFAULT_HELP_FORMAT +BASIC_CFLAGS += -DDEFAULT_HELP_FORMAT='"$(DEFAULT_HELP_FORMAT)"' +endif + ALL_CFLAGS += $(BASIC_CFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS) @@ -1962,7 +1976,7 @@ strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X git.o: common-cmds.h -git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ +git.sp git.s git.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' @@ -1979,6 +1993,9 @@ builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' +version.sp version.s version.o: EXTRA_CPPFLAGS = \ + '-DGIT_VERSION="$(GIT_VERSION)"' + $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && \ ln git$X $@ 2>/dev/null || \ @@ -1996,6 +2013,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@@DIFF@@|$(DIFF_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's|@@GIT_USER_AGENT@@|$(GIT_USER_AGENT_SQ)|g' \ -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e 's/@@USE_GETTEXT_SCHEME@@/$(USE_GETTEXT_SCHEME)/g' \ @@ -2089,7 +2107,7 @@ configure: configure.ac $(RM) $<+ # These can record GIT_VERSION -git.o git.spec http.o \ +version.o git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ : GIT-VERSION-FILE @@ -2259,9 +2277,6 @@ attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \ gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \ -DGIT_LOCALE_PATH='"$(localedir_SQ)"' -http.sp http.s http.o: EXTRA_CPPFLAGS = \ - -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' - ifdef NO_EXPAT http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT endif @@ -1 +1 @@ -Documentation/RelNotes/1.7.11.1.txt
\ No newline at end of file +Documentation/RelNotes/1.7.12.txt
\ No newline at end of file diff --git a/archive-tar.c b/archive-tar.c index dc91c6b50d..0ba3f25cf5 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -139,13 +139,13 @@ static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, static unsigned int ustar_header_chksum(const struct ustar_header *header) { - const char *p = (const char *)header; + const unsigned char *p = (const unsigned char *)header; unsigned int chksum = 0; - while (p < header->chksum) + while (p < (const unsigned char *)header->chksum) chksum += *p++; chksum += sizeof(header->chksum) * ' '; p += sizeof(header->chksum); - while (p < (const char *)header + sizeof(struct ustar_header)) + while (p < (const unsigned char *)header + sizeof(struct ustar_header)) chksum += *p++; return chksum; } @@ -74,25 +74,33 @@ void install_branch_config(int flag, const char *local, const char *origin, cons strbuf_addf(&key, "branch.%s.rebase", local); git_config_set(key.buf, "true"); } + strbuf_release(&key); if (flag & BRANCH_CONFIG_VERBOSE) { - strbuf_reset(&key); - - strbuf_addstr(&key, origin ? "remote" : "local"); - - /* Are we tracking a proper "branch"? */ - if (remote_is_branch) { - strbuf_addf(&key, " branch %s", shortname); - if (origin) - strbuf_addf(&key, " from %s", origin); - } + if (remote_is_branch && origin) + printf(rebasing ? + "Branch %s set up to track remote branch %s from %s by rebasing.\n" : + "Branch %s set up to track remote branch %s from %s.\n", + local, shortname, origin); + else if (remote_is_branch && !origin) + printf(rebasing ? + "Branch %s set up to track local branch %s by rebasing.\n" : + "Branch %s set up to track local branch %s.\n", + local, shortname); + else if (!remote_is_branch && origin) + printf(rebasing ? + "Branch %s set up to track remote ref %s by rebasing.\n" : + "Branch %s set up to track remote ref %s.\n", + local, remote); + else if (!remote_is_branch && !origin) + printf(rebasing ? + "Branch %s set up to track local ref %s by rebasing.\n" : + "Branch %s set up to track local ref %s.\n", + local, remote); else - strbuf_addf(&key, " ref %s", remote); - printf("Branch %s set up to track %s%s.\n", - local, key.buf, - rebasing ? " by rebasing" : ""); + die("BUG: impossible combination of %d and %p", + remote_is_branch, origin); } - strbuf_release(&key); } /* @@ -9,7 +9,6 @@ #define DEFAULT_MERGE_LOG_LEN 20 -extern const char git_version_string[]; extern const char git_usage_string[]; extern const char git_more_info_string[]; @@ -41,6 +40,8 @@ int copy_note_for_rewrite(struct notes_rewrite_cfg *c, void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c); extern int check_pager_config(const char *cmd); +struct diff_options; +extern void setup_diff_pager(struct diff_options *); extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size); @@ -83,7 +84,6 @@ extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix extern int cmd_grep(int argc, const char **argv, const char *prefix); extern int cmd_hash_object(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); -extern int cmd_http_fetch(int argc, const char **argv, const char *prefix); extern int cmd_index_pack(int argc, const char **argv, const char *prefix); extern int cmd_init_db(int argc, const char **argv, const char *prefix); extern int cmd_log(int argc, const char **argv, const char *prefix); @@ -108,7 +108,6 @@ extern int cmd_notes(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix); extern int cmd_patch_id(int argc, const char **argv, const char *prefix); -extern int cmd_pickaxe(int argc, const char **argv, const char *prefix); extern int cmd_prune(int argc, const char **argv, const char *prefix); extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); @@ -141,7 +140,6 @@ extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_update_server_info(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix); -extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_var(int argc, const char **argv, const char *prefix); extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index b79336d712..87446cf92a 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -443,6 +443,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (pathspec) { int i; + struct path_exclude_check check; + + path_exclude_check_init(&check, &dir); if (!seen) seen = find_used_pathspec(pathspec); for (i = 0; pathspec[i]; i++) { @@ -450,7 +453,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) && !file_exists(pathspec[i])) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (excluded(&dir, pathspec[i], &dtype)) + if (path_excluded(&check, pathspec[i], -1, &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else die(_("pathspec '%s' did not match any files"), @@ -458,6 +461,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) } } free(seen); + path_exclude_check_clear(&check); } plug_bulk_checkin(); diff --git a/builtin/clone.c b/builtin/clone.c index a4d8d25ee3..7f3b9823ce 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -38,7 +38,7 @@ static const char * const builtin_clone_usage[] = { }; static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1; -static int option_local, option_no_hardlinks, option_shared, option_recursive; +static int option_local = -1, option_no_hardlinks, option_shared, option_recursive; static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; @@ -70,8 +70,8 @@ static struct option builtin_clone_options[] = { PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_BOOLEAN(0, "mirror", &option_mirror, "create a mirror repository (implies bare)"), - OPT_BOOLEAN('l', "local", &option_local, - "to clone from a local repository"), + OPT_BOOL('l', "local", &option_local, + "to clone from a local repository"), OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks, "don't use local hardlinks, always copy"), OPT_BOOLEAN('s', "shared", &option_shared, @@ -342,7 +342,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; - if (option_local) + if (option_local > 0) die_errno(_("failed to create link '%s'"), dest->buf); option_no_hardlinks = 1; } @@ -668,7 +668,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; - is_local = path && !is_bundle; + is_local = option_local != 0 && path && !is_bundle; if (is_local && option_depth) warning(_("--depth is ignored in local clones; use file:// instead.")); diff --git a/builtin/diff.c b/builtin/diff.c index 9069dc41be..da8f6aac2b 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -304,13 +304,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - /* - * If the user asked for our exit code then don't start a - * pager or we would end up reporting its exit code instead. - */ - if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) && - check_pager_config("diff") != 0) - setup_pager(); + setup_diff_pager(&rev.diffopt); /* * Do we have --cached and not have a pending object, then @@ -421,3 +415,19 @@ int cmd_diff(int argc, const char **argv, const char *prefix) refresh_index_quietly(); return result; } + +void setup_diff_pager(struct diff_options *opt) +{ + /* + * If the user asked for our exit code, then either they want --quiet + * or --exit-code. We should definitely not bother with a pager in the + * former case, as we will generate no output. Since we still properly + * report our exit code even when a pager is run, we _could_ run a + * pager with --exit-code. But since we have not done so historically, + * and because it is easy to find people oneline advising "git diff + * --exit-code" in hooks and other scripts, we do not do so. + */ + if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && + check_pager_config("diff") != 0) + setup_pager(); +} diff --git a/builtin/grep.c b/builtin/grep.c index fe1726f5ef..29adb0ac93 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -928,7 +928,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!seen_dashdash) { int j; for (j = i; j < argc; j++) - verify_filename(prefix, argv[j]); + verify_filename(prefix, argv[j], j == i); } paths = get_pathspec(prefix, argv + i); diff --git a/builtin/help.c b/builtin/help.c index 43d3c84449..8f9cd60548 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -12,6 +12,10 @@ #include "column.h" #include "help.h" +#ifndef DEFAULT_HELP_FORMAT +#define DEFAULT_HELP_FORMAT "man" +#endif + static struct man_viewer_list { struct man_viewer_list *next; char name[FLEX_ARRAY]; @@ -447,6 +451,8 @@ int cmd_help(int argc, const char **argv, const char *prefix) if (parsed_help_format != HELP_FORMAT_NONE) help_format = parsed_help_format; + if (help_format == HELP_FORMAT_NONE) + help_format = parse_help_format(DEFAULT_HELP_FORMAT); alias = alias_lookup(argv[0]); if (alias && !is_git_command(argv[0])) { diff --git a/builtin/index-pack.c b/builtin/index-pack.c index dc2cfe6e6f..8b5c1eb33e 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -9,6 +9,7 @@ #include "progress.h" #include "fsck.h" #include "exec_cmd.h" +#include "streaming.h" #include "thread-utils.h" static const char index_pack_usage[] = @@ -384,30 +385,62 @@ static void unlink_base_data(struct base_data *c) free_base_data(c); } -static void *unpack_entry_data(unsigned long offset, unsigned long size) +static int is_delta_type(enum object_type type) +{ + return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); +} + +static void *unpack_entry_data(unsigned long offset, unsigned long size, + enum object_type type, unsigned char *sha1) { + static char fixed_buf[8192]; int status; git_zstream stream; - void *buf = xmalloc(size); + void *buf; + git_SHA_CTX c; + char hdr[32]; + int hdrlen; + + if (!is_delta_type(type)) { + hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1; + git_SHA1_Init(&c); + git_SHA1_Update(&c, hdr, hdrlen); + } else + sha1 = NULL; + if (type == OBJ_BLOB && size > big_file_threshold) + buf = fixed_buf; + else + buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); stream.next_out = buf; - stream.avail_out = size; + stream.avail_out = buf == fixed_buf ? sizeof(fixed_buf) : size; do { + unsigned char *last_out = stream.next_out; stream.next_in = fill(1); stream.avail_in = input_len; status = git_inflate(&stream, 0); use(input_len - stream.avail_in); + if (sha1) + git_SHA1_Update(&c, last_out, stream.next_out - last_out); + if (buf == fixed_buf) { + stream.next_out = buf; + stream.avail_out = sizeof(fixed_buf); + } } while (status == Z_OK); if (stream.total_out != size || status != Z_STREAM_END) bad_object(offset, _("inflate returned %d"), status); git_inflate_end(&stream); - return buf; + if (sha1) + git_SHA1_Final(sha1, &c); + return buf == fixed_buf ? NULL : buf; } -static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) +static void *unpack_raw_entry(struct object_entry *obj, + union delta_base *delta_base, + unsigned char *sha1) { unsigned char *p; unsigned long size, c; @@ -467,12 +500,14 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ } obj->hdr_size = consumed_bytes - obj->idx.offset; - data = unpack_entry_data(obj->idx.offset, obj->size); + data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1); obj->idx.crc32 = input_crc32; return data; } -static void *get_data_from_pack(struct object_entry *obj) +static void *unpack_data(struct object_entry *obj, + int (*consume)(const unsigned char *, unsigned long, void *), + void *cb_data) { off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; @@ -480,15 +515,16 @@ static void *get_data_from_pack(struct object_entry *obj) git_zstream stream; int status; - data = xmalloc(obj->size); + data = xmalloc(consume ? 64*1024 : obj->size); inbuf = xmalloc((len < 64*1024) ? len : 64*1024); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); stream.next_out = data; - stream.avail_out = obj->size; + stream.avail_out = consume ? 64*1024 : obj->size; do { + unsigned char *last_out = stream.next_out; ssize_t n = (len < 64*1024) ? len : 64*1024; n = pread(pack_fd, inbuf, n, from); if (n < 0) @@ -503,6 +539,15 @@ static void *get_data_from_pack(struct object_entry *obj) stream.next_in = inbuf; stream.avail_in = n; status = git_inflate(&stream, 0); + if (consume) { + if (consume(last_out, stream.next_out - last_out, cb_data)) { + free(inbuf); + free(data); + return NULL; + } + stream.next_out = data; + stream.avail_out = 64*1024; + } } while (len && status == Z_OK && !stream.avail_in); /* This has been inflated OK when first encountered, so... */ @@ -511,9 +556,18 @@ static void *get_data_from_pack(struct object_entry *obj) git_inflate_end(&stream); free(inbuf); + if (consume) { + free(data); + data = NULL; + } return data; } +static void *get_data_from_pack(struct object_entry *obj) +{ + return unpack_data(obj, NULL, NULL); +} + static int compare_delta_bases(const union delta_base *base1, const union delta_base *base2, enum object_type type1, @@ -568,25 +622,102 @@ static void find_delta_children(const union delta_base *base, *last_index = last; } -static void sha1_object(const void *data, unsigned long size, - enum object_type type, unsigned char *sha1) +struct compare_data { + struct object_entry *entry; + struct git_istream *st; + unsigned char *buf; + unsigned long buf_size; +}; + +static int compare_objects(const unsigned char *buf, unsigned long size, + void *cb_data) +{ + struct compare_data *data = cb_data; + + if (data->buf_size < size) { + free(data->buf); + data->buf = xmalloc(size); + data->buf_size = size; + } + + while (size) { + ssize_t len = read_istream(data->st, data->buf, size); + if (len == 0) + die(_("SHA1 COLLISION FOUND WITH %s !"), + sha1_to_hex(data->entry->idx.sha1)); + if (len < 0) + die(_("unable to read %s"), + sha1_to_hex(data->entry->idx.sha1)); + if (memcmp(buf, data->buf, len)) + die(_("SHA1 COLLISION FOUND WITH %s !"), + sha1_to_hex(data->entry->idx.sha1)); + size -= len; + buf += len; + } + return 0; +} + +static int check_collison(struct object_entry *entry) +{ + struct compare_data data; + enum object_type type; + unsigned long size; + + if (entry->size <= big_file_threshold || entry->type != OBJ_BLOB) + return -1; + + memset(&data, 0, sizeof(data)); + data.entry = entry; + data.st = open_istream(entry->idx.sha1, &type, &size, NULL); + if (!data.st) + return -1; + if (size != entry->size || type != entry->type) + die(_("SHA1 COLLISION FOUND WITH %s !"), + sha1_to_hex(entry->idx.sha1)); + unpack_data(entry, compare_objects, &data); + close_istream(data.st); + free(data.buf); + return 0; +} + +static void sha1_object(const void *data, struct object_entry *obj_entry, + unsigned long size, enum object_type type, + const unsigned char *sha1) { - hash_sha1_file(data, size, typename(type), sha1); + void *new_data = NULL; + int collision_test_needed; + + assert(data || obj_entry); + read_lock(); - if (has_sha1_file(sha1)) { + collision_test_needed = has_sha1_file(sha1); + read_unlock(); + + if (collision_test_needed && !data) { + read_lock(); + if (!check_collison(obj_entry)) + collision_test_needed = 0; + read_unlock(); + } + if (collision_test_needed) { void *has_data; enum object_type has_type; unsigned long has_size; + read_lock(); + has_type = sha1_object_info(sha1, &has_size); + if (has_type != type || has_size != size) + die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); has_data = read_sha1_file(sha1, &has_type, &has_size); read_unlock(); + if (!data) + data = new_data = get_data_from_pack(obj_entry); if (!has_data) die(_("cannot read existing object %s"), sha1_to_hex(sha1)); if (size != has_size || type != has_type || memcmp(data, has_data, size) != 0) die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); free(has_data); - } else - read_unlock(); + } if (strict) { read_lock(); @@ -601,6 +732,9 @@ static void sha1_object(const void *data, unsigned long size, int eaten; void *buf = (void *) data; + if (!buf) + buf = new_data = get_data_from_pack(obj_entry); + /* * we do not need to free the memory here, as the * buf is deleted by the caller. @@ -625,11 +759,8 @@ static void sha1_object(const void *data, unsigned long size, } read_unlock(); } -} -static int is_delta_type(enum object_type type) -{ - return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); + free(new_data); } /* @@ -711,7 +842,9 @@ static void resolve_delta(struct object_entry *delta_obj, free(delta_data); if (!result->data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); - sha1_object(result->data, result->size, delta_obj->real_type, + hash_sha1_file(result->data, result->size, + typename(delta_obj->real_type), delta_obj->idx.sha1); + sha1_object(result->data, NULL, result->size, delta_obj->real_type, delta_obj->idx.sha1); counter_lock(); nr_resolved_deltas++; @@ -841,7 +974,7 @@ static void *threaded_second_pass(void *data) */ static void parse_pack_objects(unsigned char *sha1) { - int i; + int i, nr_delays = 0; struct delta_entry *delta = deltas; struct stat st; @@ -851,14 +984,18 @@ static void parse_pack_objects(unsigned char *sha1) nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - void *data = unpack_raw_entry(obj, &delta->base); + void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1); obj->real_type = obj->type; if (is_delta_type(obj->type)) { nr_deltas++; delta->obj_no = i; delta++; + } else if (!data) { + /* large blobs, check later */ + obj->real_type = OBJ_BAD; + nr_delays++; } else - sha1_object(data, obj->size, obj->type, obj->idx.sha1); + sha1_object(data, NULL, obj->size, obj->type, obj->idx.sha1); free(data); display_progress(progress, i+1); } @@ -878,6 +1015,17 @@ static void parse_pack_objects(unsigned char *sha1) if (S_ISREG(st.st_mode) && lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size) die(_("pack has junk at the end")); + + for (i = 0; i < nr_objects; i++) { + struct object_entry *obj = &objects[i]; + if (obj->real_type != OBJ_BAD) + continue; + obj->real_type = obj->type; + sha1_object(NULL, obj, obj->size, obj->type, obj->idx.sha1); + nr_delays--; + } + if (nr_delays) + die(_("confusion beyond insanity in parse_pack_objects()")); } /* diff --git a/builtin/log.c b/builtin/log.c index 906dca475a..4f1b42a685 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -21,6 +21,7 @@ #include "parse-options.h" #include "branch.h" #include "streaming.h" +#include "version.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 7cff175745..31b3f2d900 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -200,9 +200,19 @@ static void show_ru_info(void) } } +static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce) +{ + int dtype = ce_to_dtype(ce); + return path_excluded(check, ce->name, ce_namelen(ce), &dtype); +} + static void show_files(struct dir_struct *dir) { int i; + struct path_exclude_check check; + + if ((dir->flags & DIR_SHOW_IGNORED)) + path_exclude_check_init(&check, dir); /* For cached/deleted files we don't need to even do the readdir */ if (show_others || show_killed) { @@ -215,9 +225,8 @@ static void show_files(struct dir_struct *dir) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(&check, ce)) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -232,9 +241,8 @@ static void show_files(struct dir_struct *dir) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(&check, ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; @@ -247,6 +255,9 @@ static void show_files(struct dir_struct *dir) show_ce_entry(tag_modified, ce); } } + + if ((dir->flags & DIR_SHOW_IGNORED)) + path_exclude_check_clear(&check); } /* diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index ccfcbad146..f3348208d8 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,6 +16,7 @@ #include "list-objects.h" #include "progress.h" #include "refs.h" +#include "streaming.h" #include "thread-utils.h" static const char *pack_usage[] = { @@ -150,6 +151,46 @@ static unsigned long do_compress(void **pptr, unsigned long size) return stream.total_out; } +static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f, + const unsigned char *sha1) +{ + git_zstream stream; + unsigned char ibuf[1024 * 16]; + unsigned char obuf[1024 * 16]; + unsigned long olen = 0; + + memset(&stream, 0, sizeof(stream)); + git_deflate_init(&stream, pack_compression_level); + + for (;;) { + ssize_t readlen; + int zret = Z_OK; + readlen = read_istream(st, ibuf, sizeof(ibuf)); + if (readlen == -1) + die(_("unable to read %s"), sha1_to_hex(sha1)); + + stream.next_in = ibuf; + stream.avail_in = readlen; + while ((stream.avail_in || readlen == 0) && + (zret == Z_OK || zret == Z_BUF_ERROR)) { + stream.next_out = obuf; + stream.avail_out = sizeof(obuf); + zret = git_deflate(&stream, readlen ? 0 : Z_FINISH); + sha1write(f, obuf, stream.next_out - obuf); + olen += stream.next_out - obuf; + } + if (stream.avail_in) + die(_("deflate error (%d)"), zret); + if (readlen == 0) { + if (zret != Z_STREAM_END) + die(_("deflate error (%d)"), zret); + break; + } + } + git_deflate_end(&stream); + return olen; +} + /* * we are going to reuse the existing object data as is. make * sure it is not corrupt. @@ -208,11 +249,18 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent unsigned hdrlen; enum object_type type; void *buf; + struct git_istream *st = NULL; if (!usable_delta) { - buf = read_sha1_file(entry->idx.sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); + if (entry->type == OBJ_BLOB && + entry->size > big_file_threshold && + (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL) + buf = NULL; + else { + buf = read_sha1_file(entry->idx.sha1, &type, &size); + if (!buf) + die(_("unable to read %s"), sha1_to_hex(entry->idx.sha1)); + } /* * make sure no cached delta data remains from a * previous attempt before a pack split occurred. @@ -233,7 +281,9 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent OBJ_OFS_DELTA : OBJ_REF_DELTA; } - if (entry->z_delta_size) + if (st) /* large blob case, just assume we don't compress well */ + datalen = size; + else if (entry->z_delta_size) datalen = entry->z_delta_size; else datalen = do_compress(&buf, size); @@ -256,6 +306,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent while (ofs >>= 7) dheader[--pos] = 128 | (--ofs & 127); if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { + if (st) + close_istream(st); free(buf); return 0; } @@ -268,6 +320,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent * an additional 20 bytes for the base sha1. */ if (limit && hdrlen + 20 + datalen + 20 >= limit) { + if (st) + close_istream(st); free(buf); return 0; } @@ -276,13 +330,20 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent hdrlen += 20; } else { if (limit && hdrlen + datalen + 20 >= limit) { + if (st) + close_istream(st); free(buf); return 0; } sha1write(f, header, hdrlen); } - sha1write(f, buf, datalen); - free(buf); + if (st) { + datalen = write_large_blob_data(st, f, entry->idx.sha1); + close_istream(st); + } else { + sha1write(f, buf, datalen); + free(buf); + } return hdrlen + datalen; } diff --git a/builtin/reflog.c b/builtin/reflog.c index 062d7dad1b..b3c9e27bde 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -330,8 +330,10 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, printf("keep %s", message); return 0; prune: - if (!cb->newlog || cb->cmd->verbose) - printf("%sprune %s", cb->newlog ? "" : "would ", message); + if (!cb->newlog) + printf("would prune %s", message); + else if (cb->cmd->verbose) + printf("prune %s", message); return 0; } diff --git a/builtin/reset.c b/builtin/reset.c index 8c2c1d52a2..4cc34c9084 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -285,7 +285,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) rev = argv[i++]; } else { /* Otherwise we treat this as a filename */ - verify_filename(prefix, argv[i]); + verify_filename(prefix, argv[i], 1); } } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 733f626f6c..13495b88f5 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -486,7 +486,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (as_is) { if (show_file(arg) && as_is < 2) - verify_filename(prefix, arg); + verify_filename(prefix, arg, 0); continue; } if (!strcmp(arg,"-n")) { @@ -734,7 +734,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) as_is = 1; if (!show_file(arg)) continue; - verify_filename(prefix, arg); + verify_filename(prefix, arg, 1); } if (verify) { if (revs_count == 1) { diff --git a/builtin/update-index.c b/builtin/update-index.c index 5f038d64da..5a4e9ea55a 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -211,12 +211,6 @@ static int process_path(const char *path) if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); - /* - * Process a regular file - */ - if (ce && S_ISGITLINK(ce->ce_mode)) - return error("%s is already a gitlink, not replacing", path); - return add_one_path(ce, path, len, &st); } @@ -188,12 +188,16 @@ int verify_bundle(struct bundle_header *header, int verbose) r->nr), r->nr); list_refs(r, 0, NULL); - r = &header->prerequisites; - printf_ln(Q_("The bundle requires this ref", - "The bundle requires these %d refs", - r->nr), - r->nr); - list_refs(r, 0, NULL); + if (!r->nr) { + printf_ln(_("The bundle records a complete history.")); + } else { + r = &header->prerequisites; + printf_ln(Q_("The bundle requires this ref", + "The bundle requires these %d refs", + r->nr), + r->nr); + list_refs(r, 0, NULL); + } } return ret; } @@ -409,7 +409,9 @@ extern const char *setup_git_directory(void); extern char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); extern int check_filename(const char *prefix, const char *name); -extern void verify_filename(const char *prefix, const char *name); +extern void verify_filename(const char *prefix, + const char *name, + int diagnose_misspelt_rev); extern void verify_non_filename(const char *prefix, const char *name); #define INIT_DB_QUIET 0x0001 diff --git a/config.mak.in b/config.mak.in index b2ba7104eb..802d34223a 100644 --- a/config.mak.in +++ b/config.mak.in @@ -28,7 +28,6 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH -ASCIIDOC7=@ASCIIDOC7@ 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 e1255506a6..4e9012f49b 100644 --- a/configure.ac +++ b/configure.ac @@ -437,21 +437,14 @@ if test -n "$ASCIIDOC"; then AC_MSG_CHECKING([for asciidoc version]) asciidoc_version=`$ASCIIDOC --version 2>/dev/null` case "${asciidoc_version}" in - asciidoc' '7*) - ASCIIDOC7=YesPlease - AC_MSG_RESULT([${asciidoc_version} > 7]) - ;; asciidoc' '8*) - ASCIIDOC7= AC_MSG_RESULT([${asciidoc_version}]) ;; *) - ASCIIDOC7= AC_MSG_RESULT([${asciidoc_version} (unknown)]) ;; esac fi -AC_SUBST(ASCIIDOC7) ## Checks for libraries. @@ -536,7 +536,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, * Add support for ssh port: ssh://host.xy:<port>/... */ if (protocol == PROTO_SSH && host != url) - port = get_port(host); + port = get_port(end); if (protocol == PROTO_GIT) { /* These underlying connection commands die() if they diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2e1b5e14b9..ffedce751c 100755..100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -20,46 +20,8 @@ # 1) Copy this file to somewhere (e.g. ~/.git-completion.sh). # 2) Add the following line to your .bashrc/.zshrc: # source ~/.git-completion.sh -# -# 3) Consider changing your PS1 to also show the current branch: -# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' -# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' -# -# The argument to __git_ps1 will be displayed only if you -# are currently in a git repository. The %s token will be -# the name of the current branch. -# -# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty -# value, unstaged (*) and staged (+) changes will be shown next -# to the branch name. You can configure this per-repository -# with the bash.showDirtyState variable, which defaults to true -# once GIT_PS1_SHOWDIRTYSTATE is enabled. -# -# You can also see if currently something is stashed, by setting -# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed, -# then a '$' will be shown next to the branch name. -# -# If you would like to see if there're untracked files, then you can -# set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're -# untracked files, then a '%' will be shown next to the branch name. -# -# If you would like to see the difference between HEAD and its -# upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates -# you are behind, ">" indicates you are ahead, and "<>" -# indicates you have diverged. You can further control -# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated -# list of values: -# verbose show number of commits ahead/behind (+/-) upstream -# legacy don't use the '--count' option available in recent -# versions of git-rev-list -# git always compare HEAD to @{upstream} -# svn always compare HEAD to your SVN upstream -# By default, __git_ps1 will compare HEAD to your SVN upstream -# if it can find one, or @{upstream} otherwise. Once you have -# set GIT_PS1_SHOWUPSTREAM, you can override it on a -# per-repository basis by setting the bash.showUpstream config -# variable. -# +# 3) Consider changing your PS1 to also show the current branch, +# see git-prompt.sh for details. if [[ -n ${ZSH_VERSION-} ]]; then autoload -U +X bashcompinit && bashcompinit @@ -74,9 +36,14 @@ esac # returns location of .git repo __gitdir () { + # Note: this function is duplicated in git-prompt.sh + # When updating it, make sure you update the other one to match. if [ -z "${1-}" ]; then if [ -n "${__git_dir-}" ]; then echo "$__git_dir" + elif [ -n "${GIT_DIR-}" ]; then + test -d "${GIT_DIR-}" || return 1 + echo "$GIT_DIR" elif [ -d .git ]; then echo .git else @@ -89,221 +56,6 @@ __gitdir () fi } -# stores the divergence from upstream in $p -# used by GIT_PS1_SHOWUPSTREAM -__git_ps1_show_upstream () -{ - local key value - local svn_remote svn_url_pattern count n - local upstream=git legacy="" verbose="" - - svn_remote=() - # get some config options from git-config - local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')" - while read -r key value; do - case "$key" in - bash.showupstream) - GIT_PS1_SHOWUPSTREAM="$value" - if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then - p="" - return - fi - ;; - svn-remote.*.url) - svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" - svn_url_pattern+="\\|$value" - upstream=svn+git # default upstream is SVN if available, else git - ;; - esac - done <<< "$output" - - # parse configuration values - for option in ${GIT_PS1_SHOWUPSTREAM}; do - case "$option" in - git|svn) upstream="$option" ;; - verbose) verbose=1 ;; - legacy) legacy=1 ;; - esac - done - - # Find our upstream - case "$upstream" in - git) upstream="@{upstream}" ;; - svn*) - # get the upstream from the "git-svn-id: ..." in a commit message - # (git-svn uses essentially the same procedure internally) - local svn_upstream=($(git log --first-parent -1 \ - --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) - if [[ 0 -ne ${#svn_upstream[@]} ]]; then - svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} - svn_upstream=${svn_upstream%@*} - local n_stop="${#svn_remote[@]}" - for ((n=1; n <= n_stop; n++)); do - svn_upstream=${svn_upstream#${svn_remote[$n]}} - done - - if [[ -z "$svn_upstream" ]]; then - # default branch name for checkouts with no layout: - upstream=${GIT_SVN_ID:-git-svn} - else - upstream=${svn_upstream#/} - fi - elif [[ "svn+git" = "$upstream" ]]; then - upstream="@{upstream}" - fi - ;; - esac - - # Find how many commits we are ahead/behind our upstream - if [[ -z "$legacy" ]]; then - count="$(git rev-list --count --left-right \ - "$upstream"...HEAD 2>/dev/null)" - else - # produce equivalent output to --count for older versions of git - local commits - if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" - then - local commit behind=0 ahead=0 - for commit in $commits - do - case "$commit" in - "<"*) ((behind++)) ;; - *) ((ahead++)) ;; - esac - done - count="$behind $ahead" - else - count="" - fi - fi - - # calculate the result - if [[ -z "$verbose" ]]; then - case "$count" in - "") # no upstream - p="" ;; - "0 0") # equal to upstream - p="=" ;; - "0 "*) # ahead of upstream - p=">" ;; - *" 0") # behind upstream - p="<" ;; - *) # diverged from upstream - p="<>" ;; - esac - else - case "$count" in - "") # no upstream - p="" ;; - "0 0") # equal to upstream - p=" u=" ;; - "0 "*) # ahead of upstream - p=" u+${count#0 }" ;; - *" 0") # behind upstream - p=" u-${count% 0}" ;; - *) # diverged from upstream - p=" u+${count#* }-${count% *}" ;; - esac - fi - -} - - -# __git_ps1 accepts 0 or 1 arguments (i.e., format string) -# returns text to add to bash PS1 prompt (includes branch name) -__git_ps1 () -{ - local g="$(__gitdir)" - if [ -n "$g" ]; then - local r="" - local b="" - if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" - b="$(cat "$g/rebase-merge/head-name")" - elif [ -d "$g/rebase-merge" ]; then - r="|REBASE-m" - b="$(cat "$g/rebase-merge/head-name")" - else - if [ -d "$g/rebase-apply" ]; then - if [ -f "$g/rebase-apply/rebasing" ]; then - r="|REBASE" - elif [ -f "$g/rebase-apply/applying" ]; then - r="|AM" - else - r="|AM/REBASE" - fi - elif [ -f "$g/MERGE_HEAD" ]; then - r="|MERGING" - elif [ -f "$g/CHERRY_PICK_HEAD" ]; then - r="|CHERRY-PICKING" - elif [ -f "$g/BISECT_LOG" ]; then - r="|BISECTING" - fi - - b="$(git symbolic-ref HEAD 2>/dev/null)" || { - - b="$( - case "${GIT_PS1_DESCRIBE_STYLE-}" in - (contains) - git describe --contains HEAD ;; - (branch) - git describe --contains --all HEAD ;; - (describe) - git describe HEAD ;; - (* | default) - git describe --tags --exact-match HEAD ;; - esac 2>/dev/null)" || - - b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || - b="unknown" - b="($b)" - } - fi - - local w="" - local i="" - local s="" - local u="" - local c="" - local p="" - - if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then - if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then - c="BARE:" - else - b="GIT_DIR!" - fi - elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then - if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then - if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then - git diff --no-ext-diff --quiet --exit-code || w="*" - if git rev-parse --quiet --verify HEAD >/dev/null; then - git diff-index --cached --quiet HEAD -- || i="+" - else - i="#" - fi - fi - fi - if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then - git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" - fi - - if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then - if [ -n "$(git ls-files --others --exclude-standard)" ]; then - u="%" - fi - fi - - if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then - __git_ps1_show_upstream - fi - fi - - local f="$w$i$s$u" - printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p" - fi -} - __gitcomp_1 () { local c IFS=$' \t\n' diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh new file mode 100644 index 0000000000..29b1ec9eb1 --- /dev/null +++ b/contrib/completion/git-prompt.sh @@ -0,0 +1,289 @@ +# bash/zsh git prompt support +# +# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org> +# Distributed under the GNU General Public License, version 2.0. +# +# This script allows you to see the current branch in your prompt. +# +# To enable: +# +# 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh). +# 2) Add the following line to your .bashrc/.zshrc: +# source ~/.git-prompt.sh +# 3) Change your PS1 to also show the current branch: +# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' +# +# The argument to __git_ps1 will be displayed only if you are currently +# in a git repository. The %s token will be the name of the current +# branch. +# +# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value, +# unstaged (*) and staged (+) changes will be shown next to the branch +# name. You can configure this per-repository with the +# bash.showDirtyState variable, which defaults to true once +# GIT_PS1_SHOWDIRTYSTATE is enabled. +# +# You can also see if currently something is stashed, by setting +# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed, +# then a '$' will be shown next to the branch name. +# +# If you would like to see if there're untracked files, then you can set +# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked +# files, then a '%' will be shown next to the branch name. +# +# If you would like to see the difference between HEAD and its upstream, +# set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">" +# indicates you are ahead, and "<>" indicates you have diverged. You +# can further control behaviour by setting GIT_PS1_SHOWUPSTREAM to a +# space-separated list of values: +# +# verbose show number of commits ahead/behind (+/-) upstream +# legacy don't use the '--count' option available in recent +# versions of git-rev-list +# git always compare HEAD to @{upstream} +# svn always compare HEAD to your SVN upstream +# +# By default, __git_ps1 will compare HEAD to your SVN upstream if it can +# find one, or @{upstream} otherwise. Once you have set +# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by +# setting the bash.showUpstream config variable. + +# __gitdir accepts 0 or 1 arguments (i.e., location) +# returns location of .git repo +__gitdir () +{ + # Note: this function is duplicated in git-completion.bash + # When updating it, make sure you update the other one to match. + if [ -z "${1-}" ]; then + if [ -n "${__git_dir-}" ]; then + echo "$__git_dir" + elif [ -n "${GIT_DIR-}" ]; then + test -d "${GIT_DIR-}" || return 1 + echo "$GIT_DIR" + elif [ -d .git ]; then + echo .git + else + git rev-parse --git-dir 2>/dev/null + fi + elif [ -d "$1/.git" ]; then + echo "$1/.git" + else + echo "$1" + fi +} + +# stores the divergence from upstream in $p +# used by GIT_PS1_SHOWUPSTREAM +__git_ps1_show_upstream () +{ + local key value + local svn_remote svn_url_pattern count n + local upstream=git legacy="" verbose="" + + svn_remote=() + # get some config options from git-config + local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')" + while read -r key value; do + case "$key" in + bash.showupstream) + GIT_PS1_SHOWUPSTREAM="$value" + if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then + p="" + return + fi + ;; + svn-remote.*.url) + svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" + svn_url_pattern+="\\|$value" + upstream=svn+git # default upstream is SVN if available, else git + ;; + esac + done <<< "$output" + + # parse configuration values + for option in ${GIT_PS1_SHOWUPSTREAM}; do + case "$option" in + git|svn) upstream="$option" ;; + verbose) verbose=1 ;; + legacy) legacy=1 ;; + esac + done + + # Find our upstream + case "$upstream" in + git) upstream="@{upstream}" ;; + svn*) + # get the upstream from the "git-svn-id: ..." in a commit message + # (git-svn uses essentially the same procedure internally) + local svn_upstream=($(git log --first-parent -1 \ + --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) + if [[ 0 -ne ${#svn_upstream[@]} ]]; then + svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} + svn_upstream=${svn_upstream%@*} + local n_stop="${#svn_remote[@]}" + for ((n=1; n <= n_stop; n++)); do + svn_upstream=${svn_upstream#${svn_remote[$n]}} + done + + if [[ -z "$svn_upstream" ]]; then + # default branch name for checkouts with no layout: + upstream=${GIT_SVN_ID:-git-svn} + else + upstream=${svn_upstream#/} + fi + elif [[ "svn+git" = "$upstream" ]]; then + upstream="@{upstream}" + fi + ;; + esac + + # Find how many commits we are ahead/behind our upstream + if [[ -z "$legacy" ]]; then + count="$(git rev-list --count --left-right \ + "$upstream"...HEAD 2>/dev/null)" + else + # produce equivalent output to --count for older versions of git + local commits + if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" + then + local commit behind=0 ahead=0 + for commit in $commits + do + case "$commit" in + "<"*) ((behind++)) ;; + *) ((ahead++)) ;; + esac + done + count="$behind $ahead" + else + count="" + fi + fi + + # calculate the result + if [[ -z "$verbose" ]]; then + case "$count" in + "") # no upstream + p="" ;; + "0 0") # equal to upstream + p="=" ;; + "0 "*) # ahead of upstream + p=">" ;; + *" 0") # behind upstream + p="<" ;; + *) # diverged from upstream + p="<>" ;; + esac + else + case "$count" in + "") # no upstream + p="" ;; + "0 0") # equal to upstream + p=" u=" ;; + "0 "*) # ahead of upstream + p=" u+${count#0 }" ;; + *" 0") # behind upstream + p=" u-${count% 0}" ;; + *) # diverged from upstream + p=" u+${count#* }-${count% *}" ;; + esac + fi + +} + + +# __git_ps1 accepts 0 or 1 arguments (i.e., format string) +# returns text to add to bash PS1 prompt (includes branch name) +__git_ps1 () +{ + local g="$(__gitdir)" + if [ -n "$g" ]; then + local r="" + local b="" + if [ -f "$g/rebase-merge/interactive" ]; then + r="|REBASE-i" + b="$(cat "$g/rebase-merge/head-name")" + elif [ -d "$g/rebase-merge" ]; then + r="|REBASE-m" + b="$(cat "$g/rebase-merge/head-name")" + else + if [ -d "$g/rebase-apply" ]; then + if [ -f "$g/rebase-apply/rebasing" ]; then + r="|REBASE" + elif [ -f "$g/rebase-apply/applying" ]; then + r="|AM" + else + r="|AM/REBASE" + fi + elif [ -f "$g/MERGE_HEAD" ]; then + r="|MERGING" + elif [ -f "$g/CHERRY_PICK_HEAD" ]; then + r="|CHERRY-PICKING" + elif [ -f "$g/BISECT_LOG" ]; then + r="|BISECTING" + fi + + b="$(git symbolic-ref HEAD 2>/dev/null)" || { + + b="$( + case "${GIT_PS1_DESCRIBE_STYLE-}" in + (contains) + git describe --contains HEAD ;; + (branch) + git describe --contains --all HEAD ;; + (describe) + git describe HEAD ;; + (* | default) + git describe --tags --exact-match HEAD ;; + esac 2>/dev/null)" || + + b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || + b="unknown" + b="($b)" + } + fi + + local w="" + local i="" + local s="" + local u="" + local c="" + local p="" + + if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then + if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then + c="BARE:" + else + b="GIT_DIR!" + fi + elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then + if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then + if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then + git diff --no-ext-diff --quiet --exit-code || w="*" + if git rev-parse --quiet --verify HEAD >/dev/null; then + git diff-index --cached --quiet HEAD -- || i="+" + else + i="#" + fi + fi + fi + if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then + git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" + fi + + if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then + if [ -n "$(git ls-files --others --exclude-standard)" ]; then + u="%" + fi + fi + + if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then + __git_ps1_show_upstream + fi + fi + + local f="$w$i$s$u" + printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p" + fi +} diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el index d351cfb6e7..e671f6c1c6 100644 --- a/contrib/emacs/git-blame.el +++ b/contrib/emacs/git-blame.el @@ -304,7 +304,7 @@ See also function `git-blame-mode'." (defun git-blame-cleanup () "Remove all blame properties" - (mapcar 'delete-overlay git-blame-overlays) + (mapc 'delete-overlay git-blame-overlays) (setq git-blame-overlays nil) (remove-git-blame-text-properties (point-min) (point-max))) @@ -337,16 +337,16 @@ See also function `git-blame-mode'." (defvar in-blame-filter nil) (defun git-blame-filter (proc str) - (save-excursion - (set-buffer (process-buffer proc)) - (goto-char (process-mark proc)) - (insert-before-markers str) - (goto-char 0) - (unless in-blame-filter - (let ((more t) - (in-blame-filter t)) - (while more - (setq more (git-blame-parse))))))) + (with-current-buffer (process-buffer proc) + (save-excursion + (goto-char (process-mark proc)) + (insert-before-markers str) + (goto-char (point-min)) + (unless in-blame-filter + (let ((more t) + (in-blame-filter t)) + (while more + (setq more (git-blame-parse)))))))) (defun git-blame-parse () (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n") @@ -385,32 +385,33 @@ See also function `git-blame-mode'." info)))) (defun git-blame-create-overlay (info start-line num-lines) - (save-excursion - (set-buffer git-blame-file) - (let ((inhibit-point-motion-hooks t) - (inhibit-modification-hooks t)) - (goto-line start-line) - (let* ((start (point)) - (end (progn (forward-line num-lines) (point))) - (ovl (make-overlay start end)) - (hash (car info)) - (spec `((?h . ,(substring hash 0 6)) - (?H . ,hash) - (?a . ,(git-blame-get-info info 'author)) - (?A . ,(git-blame-get-info info 'author-mail)) - (?c . ,(git-blame-get-info info 'committer)) - (?C . ,(git-blame-get-info info 'committer-mail)) - (?s . ,(git-blame-get-info info 'summary))))) - (push ovl git-blame-overlays) - (overlay-put ovl 'git-blame info) - (overlay-put ovl 'help-echo - (format-spec git-blame-mouseover-format spec)) - (if git-blame-use-colors - (overlay-put ovl 'face (list :background - (cdr (assq 'color (cdr info)))))) - (overlay-put ovl 'line-prefix - (propertize (format-spec git-blame-prefix-format spec) - 'face 'git-blame-prefix-face)))))) + (with-current-buffer git-blame-file + (save-excursion + (let ((inhibit-point-motion-hooks t) + (inhibit-modification-hooks t)) + (goto-char (point-min)) + (forward-line (1- start-line)) + (let* ((start (point)) + (end (progn (forward-line num-lines) (point))) + (ovl (make-overlay start end)) + (hash (car info)) + (spec `((?h . ,(substring hash 0 6)) + (?H . ,hash) + (?a . ,(git-blame-get-info info 'author)) + (?A . ,(git-blame-get-info info 'author-mail)) + (?c . ,(git-blame-get-info info 'committer)) + (?C . ,(git-blame-get-info info 'committer-mail)) + (?s . ,(git-blame-get-info info 'summary))))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo + (format-spec git-blame-mouseover-format spec)) + (if git-blame-use-colors + (overlay-put ovl 'face (list :background + (cdr (assq 'color (cdr info)))))) + (overlay-put ovl 'line-prefix + (propertize (format-spec git-blame-prefix-format spec) + 'face 'git-blame-prefix-face))))))) (defun git-blame-add-info (info key value) (nconc info (list (cons (intern key) value)))) diff --git a/diff-no-index.c b/diff-no-index.c index f0b0010aed..77667b810d 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -224,13 +224,6 @@ void diff_no_index(struct rev_info *revs, } } - /* - * If the user asked for our exit code then don't start a - * pager or we would end up reporting its exit code instead. - */ - if (!DIFF_OPT_TST(&revs->diffopt, EXIT_WITH_STATUS)) - setup_pager(); - if (prefix) { int len = strlen(prefix); const char *paths[3]; @@ -255,13 +248,15 @@ void diff_no_index(struct rev_info *revs, if (!revs->diffopt.output_format) revs->diffopt.output_format = DIFF_FORMAT_PATCH; - DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS); DIFF_OPT_SET(&revs->diffopt, NO_INDEX); revs->max_count = -2; if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); + setup_diff_pager(&revs->diffopt); + DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS); + if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0], revs->diffopt.pathspec.raw[1])) exit(1); @@ -288,9 +288,24 @@ int match_pathspec_depth(const struct pathspec *ps, return retval; } +/* + * Return the length of the "simple" part of a path match limiter. + */ +static int simple_length(const char *match) +{ + int len = -1; + + for (;;) { + unsigned char c = *match++; + len++; + if (c == '\0' || is_glob_special(c)) + return len; + } +} + static int no_wildcard(const char *string) { - return string[strcspn(string, "*?[{\\")] == '\0'; + return string[simple_length(string)] == '\0'; } void add_exclude(const char *string, const char *base, @@ -326,8 +341,7 @@ void add_exclude(const char *string, const char *base, x->flags = flags; if (!strchr(string, '/')) x->flags |= EXC_FLAG_NODIR; - if (no_wildcard(string)) - x->flags |= EXC_FLAG_NOWILDCARD; + x->nowildcardlen = simple_length(string); if (*string == '*' && no_wildcard(string+1)) x->flags |= EXC_FLAG_ENDSWITH; ALLOC_GROW(which->excludes, which->nr + 1, which->alloc); @@ -498,62 +512,74 @@ int excluded_from_list(const char *pathname, { int i; - if (el->nr) { - for (i = el->nr - 1; 0 <= i; i--) { - struct exclude *x = el->excludes[i]; - const char *exclude = x->pattern; - int to_exclude = x->to_exclude; - - if (x->flags & EXC_FLAG_MUSTBEDIR) { - if (*dtype == DT_UNKNOWN) - *dtype = get_dtype(NULL, pathname, pathlen); - if (*dtype != DT_DIR) - continue; - } + if (!el->nr) + return -1; /* undefined */ - if (x->flags & EXC_FLAG_NODIR) { - /* match basename */ - if (x->flags & EXC_FLAG_NOWILDCARD) { - if (!strcmp_icase(exclude, basename)) - return to_exclude; - } else if (x->flags & EXC_FLAG_ENDSWITH) { - if (x->patternlen - 1 <= pathlen && - !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1)) - return to_exclude; - } else { - if (fnmatch_icase(exclude, basename, 0) == 0) - return to_exclude; - } - } - else { - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly - * in front of it. - */ - int baselen = x->baselen; - if (*exclude == '/') - exclude++; - - if (pathlen < baselen || - (baselen && pathname[baselen-1] != '/') || - strncmp_icase(pathname, x->base, baselen)) - continue; - - if (x->flags & EXC_FLAG_NOWILDCARD) { - if (!strcmp_icase(exclude, pathname + baselen)) - return to_exclude; - } else { - if (fnmatch_icase(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; - } + for (i = el->nr - 1; 0 <= i; i--) { + struct exclude *x = el->excludes[i]; + const char *name, *exclude = x->pattern; + int to_exclude = x->to_exclude; + int namelen, prefix = x->nowildcardlen; + + if (x->flags & EXC_FLAG_MUSTBEDIR) { + if (*dtype == DT_UNKNOWN) + *dtype = get_dtype(NULL, pathname, pathlen); + if (*dtype != DT_DIR) + continue; + } + + if (x->flags & EXC_FLAG_NODIR) { + /* match basename */ + if (prefix == x->patternlen) { + if (!strcmp_icase(exclude, basename)) + return to_exclude; + } else if (x->flags & EXC_FLAG_ENDSWITH) { + if (x->patternlen - 1 <= pathlen && + !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1)) + return to_exclude; + } else { + if (fnmatch_icase(exclude, basename, 0) == 0) + return to_exclude; } + continue; + } + + /* match with FNM_PATHNAME: + * exclude has base (baselen long) implicitly in front of it. + */ + if (*exclude == '/') { + exclude++; + prefix--; + } + + if (pathlen < x->baselen || + (x->baselen && pathname[x->baselen-1] != '/') || + strncmp_icase(pathname, x->base, x->baselen)) + continue; + + namelen = x->baselen ? pathlen - x->baselen : pathlen; + name = pathname + pathlen - namelen; + + /* if the non-wildcard part is longer than the + remaining pathname, surely it cannot match */ + if (prefix > namelen) + continue; + + if (prefix) { + if (strncmp_icase(exclude, name, prefix)) + continue; + exclude += prefix; + name += prefix; + namelen -= prefix; } + + if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME)) + return to_exclude; } return -1; /* undecided */ } -int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) +static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) { int pathlen = strlen(pathname); int st; @@ -573,6 +599,64 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) return 0; } +void path_exclude_check_init(struct path_exclude_check *check, + struct dir_struct *dir) +{ + check->dir = dir; + strbuf_init(&check->path, 256); +} + +void path_exclude_check_clear(struct path_exclude_check *check) +{ + strbuf_release(&check->path); +} + +/* + * Is this name excluded? This is for a caller like show_files() that + * do not honor directory hierarchy and iterate through paths that are + * possibly in an ignored directory. + * + * A path to a directory known to be excluded is left in check->path to + * optimize for repeated checks for files in the same excluded directory. + */ +int path_excluded(struct path_exclude_check *check, + const char *name, int namelen, int *dtype) +{ + int i; + struct strbuf *path = &check->path; + + /* + * we allow the caller to pass namelen as an optimization; it + * must match the length of the name, as we eventually call + * excluded() on the whole name string. + */ + if (namelen < 0) + namelen = strlen(name); + + if (path->len && + path->len <= namelen && + !memcmp(name, path->buf, path->len) && + (!name[path->len] || name[path->len] == '/')) + return 1; + + strbuf_setlen(path, 0); + for (i = 0; name[i]; i++) { + int ch = name[i]; + + if (ch == '/') { + int dt = DT_DIR; + if (excluded(check->dir, path->buf, &dt)) + return 1; + } + strbuf_addch(path, ch); + } + + /* An entry in the index; cannot be a directory with subentries */ + strbuf_setlen(path, 0); + + return excluded(check->dir, name, dtype); +} + static struct dir_entry *dir_entry_new(const char *pathname, int len) { struct dir_entry *ent; @@ -997,21 +1081,6 @@ static int cmp_name(const void *p1, const void *p2) e2->name, e2->len); } -/* - * Return the length of the "simple" part of a path match limiter. - */ -static int simple_length(const char *match) -{ - int len = -1; - - for (;;) { - unsigned char c = *match++; - len++; - if (c == '\0' || is_glob_special(c)) - return len; - } -} - static struct path_simplify *create_simplify(const char **pathspec) { int nr, alloc = 0; @@ -1,13 +1,14 @@ #ifndef DIR_H #define DIR_H +#include "strbuf.h" + struct dir_entry { unsigned int len; char name[FLEX_ARRAY]; /* more */ }; #define EXC_FLAG_NODIR 1 -#define EXC_FLAG_NOWILDCARD 2 #define EXC_FLAG_ENDSWITH 4 #define EXC_FLAG_MUSTBEDIR 8 @@ -17,6 +18,7 @@ struct exclude_list { struct exclude { const char *pattern; int patternlen; + int nowildcardlen; const char *base; int baselen; int to_exclude; @@ -76,8 +78,22 @@ extern int read_directory(struct dir_struct *, const char *path, int len, const extern int excluded_from_list(const char *pathname, int pathlen, const char *basename, int *dtype, struct exclude_list *el); -extern int excluded(struct dir_struct *, const char *, int *); struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len); + +/* + * The excluded() API is meant for callers that check each level of leading + * directory hierarchies with excluded() to avoid recursing into excluded + * directories. Callers that do not do so should use this API instead. + */ +struct path_exclude_check { + struct dir_struct *dir; + struct strbuf path; +}; +extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *); +extern void path_exclude_check_clear(struct path_exclude_check *); +extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype); + + extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 0c19b7c753..5f566726ab 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -684,6 +684,27 @@ rearrange_squash () { rm -f "$1.sq" "$1.rearranged" } +# Add commands after a pick or after a squash/fixup serie +# in the todo list. +add_exec_commands () { + { + first=t + while read -r insn rest + do + case $insn in + pick) + test -n "$first" || + printf "%s" "$cmd" + ;; + esac + printf "%s %s\n" "$insn" "$rest" + first= + done + printf "%s" "$cmd" + } <"$1" >"$1.new" && + mv "$1.new" "$1" +} + case "$action" in continue) # do we have anything to commit? @@ -857,6 +878,8 @@ fi test -s "$todo" || echo noop >> "$todo" test -n "$autosquash" && rearrange_squash "$todo" +test -n "$cmd" && add_exec_commands "$todo" + cat >> "$todo" << EOF # Rebase $shortrevisions onto $shortonto diff --git a/git-rebase.sh b/git-rebase.sh index e616737444..6bd8eae648 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,8 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]' +USAGE='[--interactive | -i] [--exec | -x <cmd>] [-v] [--force-rebase | -f] + [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -30,8 +31,8 @@ Example: git-rebase master~1 topic SUBDIRECTORY_OK=Yes OPTIONS_KEEPDASHDASH= OPTIONS_SPEC="\ -git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>] -git rebase [-i] [options] --onto <newbase> --root [<branch>] +git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>] +git rebase [-i] [options] [--exec <cmd>] --onto <newbase> --root [<branch>] git-rebase [-i] --continue | --abort | --skip -- Available options are @@ -43,6 +44,7 @@ s,strategy=! use the given merge strategy no-ff! cherry-pick all commits, even if unchanged m,merge! use merging strategies to rebase i,interactive! let the user edit the list of commits to rebase +x,exec=! add exec lines after each commit of the editable list k,keep-empty preserve empty commits during rebase f,force-rebase! force rebase even if branch is up to date X,strategy-option=! pass the argument through to the merge strategy @@ -76,6 +78,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To check out the original branch and stop rebasing run \"git rebase --abort\". " unset onto +cmd= strategy= strategy_opts= do_merge= @@ -220,6 +223,11 @@ do onto="$2" shift ;; + -x) + test 2 -le "$#" || usage + cmd="${cmd}exec $2${LF}" + shift + ;; -i) interactive_rebase=explicit ;; @@ -305,6 +313,12 @@ do done test $# -gt 2 && usage +if test -n "$cmd" && + test "$interactive_rebase" != explicit +then + die "--exec option must be used with --interactive option" +fi + if test -n "$action" then test -z "$in_progress" && die "No rebase in progress?" diff --git a/git-request-pull.sh b/git-request-pull.sh index e6438e24c7..d566015975 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -57,9 +57,13 @@ headrev=$(git rev-parse --verify "$head"^0) || exit merge_base=$(git merge-base $baserev $headrev) || die "fatal: No commits in common between $base and $head" -# $head is the token given from the command line. If a ref with that -# name exists at the remote and their values match, we should use it. -# Otherwise find a ref that matches $headrev. +# $head is the token given from the command line, and $tag_name, if +# exists, is the tag we are going to show the commit information for. +# If that tag exists at the remote and it points at the commit, use it. +# Otherwise, if a branch with the same name as $head exists at the remote +# and their values match, use that instead. +# +# Otherwise find a random ref that matches $headrev. find_matching_ref=' sub abbr { my $ref = shift; @@ -70,24 +74,29 @@ find_matching_ref=' } } - my ($exact, $found); + my ($tagged, $branch, $found); while (<STDIN>) { my ($sha1, $ref, $deref) = /^(\S+)\s+(\S+?)(\^\{\})?$/; next unless ($sha1 eq $ARGV[1]); $found = abbr($ref); + if ($deref && $ref eq "tags/$ARGV[2]") { + $tagged = $found; + last; + } if ($ref =~ m|/\Q$ARGV[0]\E$|) { $exact = $found; - last; } } - if ($exact) { + if ($tagged) { + print "$tagged\n"; + } elsif ($exact) { print "$exact\n"; } elsif ($found) { print "$found\n"; } ' -ref=$(git ls-remote "$url" | perl -e "$find_matching_ref" "$head" "$headrev") +ref=$(git ls-remote "$url" | perl -e "$find_matching_ref" "$head" "$headrev" "$tag_name") url=$(git ls-remote --get-url "$url") @@ -114,6 +123,12 @@ fi && if test -n "$tag_name" then + if test -z "$ref" || test "$ref" != "tags/$tag_name" + then + echo >&2 "warn: You locally have $tag_name but it does not (yet)" + echo >&2 "warn: appear to be at $url" + echo >&2 "warn: Do you want to push it there, perhaps?" + fi git cat-file tag "$tag_name" | sed -n -e '1,/^$/d' -e '/^-----BEGIN PGP /q' -e p echo diff --git a/git-submodule.sh b/git-submodule.sh index fbf2fafaaf..5629d875e6 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -30,7 +30,22 @@ nofetch= update= prefix= -# Resolve relative url by appending to parent's url +# The function takes at most 2 arguments. The first argument is the +# URL that navigates to the submodule origin repo. When relative, this URL +# is relative to the superproject origin URL repo. The second up_path +# argument, if specified, is the relative path that navigates +# from the submodule working tree to the superproject working tree. +# +# The output of the function is the origin URL of the submodule. +# +# The output will either be an absolute URL or filesystem path (if the +# superproject origin URL is an absolute URL or filesystem path, +# respectively) or a relative file system path (if the superproject +# origin URL is a relative file system path). +# +# When the output is a relative file system path, the path is either +# relative to the submodule working tree, if up_path is specified, or to +# the superproject working tree otherwise. resolve_relative_url () { remote=$(get_default_remote) @@ -39,6 +54,21 @@ resolve_relative_url () url="$1" remoteurl=${remoteurl%/} sep=/ + up_path="$2" + + case "$remoteurl" in + *:*|/*) + is_relative= + ;; + ./*|../*) + is_relative=t + ;; + *) + is_relative=t + remoteurl="./$remoteurl" + ;; + esac + while test -n "$url" do case "$url" in @@ -53,7 +83,12 @@ resolve_relative_url () sep=: ;; *) - die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")" + if test -z "$is_relative" || test "." = "$remoteurl" + then + die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")" + else + remoteurl=. + fi ;; esac ;; @@ -64,7 +99,8 @@ resolve_relative_url () break;; esac done - echo "$remoteurl$sep${url%/}" + remoteurl="$remoteurl$sep${url%/}" + echo "${is_relative:+${up_path}}${remoteurl#./}" } # @@ -965,14 +1001,26 @@ cmd_sync() # Possibly a url relative to parent case "$url" in ./*|../*) - url=$(resolve_relative_url "$url") || exit + # rewrite foo/bar as ../.. to find path from + # submodule work tree to superproject work tree + up_path="$(echo "$sm_path" | sed "s/[^/][^/]*/../g")" && + # guarantee a trailing / + up_path=${up_path%/}/ && + # path from submodule work tree to submodule origin repo + sub_origin_url=$(resolve_relative_url "$url" "$up_path") && + # path from superproject work tree to submodule origin repo + super_config_url=$(resolve_relative_url "$url") || exit + ;; + *) + sub_origin_url="$url" + super_config_url="$url" ;; esac if git config "submodule.$name.url" >/dev/null 2>/dev/null then say "$(eval_gettext "Synchronizing submodule url for '\$name'")" - git config submodule."$name".url "$url" + git config submodule."$name".url "$super_config_url" if test -e "$sm_path"/.git then @@ -980,7 +1028,7 @@ cmd_sync() clear_local_git_env cd "$sm_path" remote=$(get_default_remote) - git config remote."$remote".url "$url" + git config remote."$remote".url "$sub_origin_url" ) fi fi @@ -256,8 +256,6 @@ static int handle_alias(int *argcp, const char ***argv) return ret; } -const char git_version_string[] = GIT_VERSION; - #define RUN_SETUP (1<<0) #define RUN_SETUP_GENTLY (1<<1) #define USE_PAGER (1<<2) @@ -6,6 +6,7 @@ #include "common-cmds.h" #include "string-list.h" #include "column.h" +#include "version.h" void add_cmdname(struct cmdnames *cmds, const char *name, int len) { @@ -4,6 +4,7 @@ #include "run-command.h" #include "url.h" #include "credential.h" +#include "version.h" int active_requests; int http_is_verbose; @@ -299,7 +300,7 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_VERBOSE, 1); curl_easy_setopt(result, CURLOPT_USERAGENT, - user_agent ? user_agent : GIT_HTTP_USER_AGENT); + user_agent ? user_agent : git_user_agent()); if (curl_ftp_no_epsv) curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); diff --git a/notes-merge.c b/notes-merge.c index 74aa77ce4b..29c6411fc6 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -524,8 +524,10 @@ static int merge_from_diffs(struct notes_merge_options *o, free(changes); if (o->verbosity >= 4) - printf("Merge result: %i unmerged notes and a %s notes tree\n", - conflicts, t->dirty ? "dirty" : "clean"); + printf(t->dirty ? + "Merge result: %i unmerged notes and a dirty notes tree\n" : + "Merge result: %i unmerged notes and a clean notes tree\n", + conflicts); return conflicts ? -1 : 1; } @@ -544,13 +544,13 @@ static int do_plain_rerere(struct string_list *rr, int fd) if (has_rerere_resolution(name)) { if (!merge(name, path)) { - if (rerere_autoupdate) + const char *msg; + if (rerere_autoupdate) { string_list_insert(&update, path); - fprintf(stderr, - "%s '%s' using previous resolution.\n", - rerere_autoupdate - ? "Staged" : "Resolved", - path); + msg = "Staged '%s' using previous resolution.\n"; + } else + msg = "Resolved '%s' using previous resolution.\n"; + fprintf(stderr, msg, path); goto mark_resolved; } } diff --git a/revision.c b/revision.c index 935e7a7ba4..5b81a92e3a 100644 --- a/revision.c +++ b/revision.c @@ -1358,11 +1358,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->topo_order = 1; } else if (!strcmp(arg, "--simplify-merges")) { revs->simplify_merges = 1; + revs->topo_order = 1; revs->rewrite_parents = 1; revs->simplify_history = 0; revs->limited = 1; } else if (!strcmp(arg, "--simplify-by-decoration")) { revs->simplify_merges = 1; + revs->topo_order = 1; revs->rewrite_parents = 1; revs->simplify_history = 0; revs->simplify_by_decoration = 1; @@ -1781,7 +1783,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s * but the latter we have checked in the main loop. */ for (j = i; j < argc; j++) - verify_filename(revs->prefix, argv[j]); + verify_filename(revs->prefix, argv[j], j == i); append_prune_data(&prune_data, argv + i); break; @@ -1947,8 +1949,9 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c } /* - * Do we know what commit all of our parents should be rewritten to? - * Otherwise we are not ready to rewrite this one yet. + * Do we know what commit all of our parents that matter + * should be rewritten to? Otherwise we are not ready to + * rewrite this one yet. */ for (cnt = 0, p = commit->parents; p; p = p->next) { pst = locate_simplify_state(revs, p->item); @@ -1956,6 +1959,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c tail = &commit_list_insert(p->item, tail)->next; cnt++; } + if (revs->first_parent_only) + break; } if (cnt) { tail = &commit_list_insert(commit, tail)->next; @@ -1968,8 +1973,13 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c for (p = commit->parents; p; p = p->next) { pst = locate_simplify_state(revs, p->item); p->item = pst->simplified; + if (revs->first_parent_only) + break; } - cnt = remove_duplicate_parents(commit); + if (!revs->first_parent_only) + cnt = remove_duplicate_parents(commit); + else + cnt = 1; /* * It is possible that we are a merge and one side branch @@ -2013,25 +2023,31 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c static void simplify_merges(struct rev_info *revs) { - struct commit_list *list; + struct commit_list *list, *next; struct commit_list *yet_to_do, **tail; + struct commit *commit; - if (!revs->topo_order) - sort_in_topological_order(&revs->commits, revs->lifo); if (!revs->prune) return; /* feed the list reversed */ yet_to_do = NULL; - for (list = revs->commits; list; list = list->next) - commit_list_insert(list->item, &yet_to_do); + for (list = revs->commits; list; list = next) { + commit = list->item; + next = list->next; + /* + * Do not free(list) here yet; the original list + * is used later in this function. + */ + commit_list_insert(commit, &yet_to_do); + } while (yet_to_do) { list = yet_to_do; yet_to_do = NULL; tail = &yet_to_do; while (list) { - struct commit *commit = list->item; - struct commit_list *next = list->next; + commit = list->item; + next = list->next; free(list); list = next; tail = simplify_one(revs, commit, tail); @@ -2043,9 +2059,10 @@ static void simplify_merges(struct rev_info *revs) revs->commits = NULL; tail = &revs->commits; while (list) { - struct commit *commit = list->item; - struct commit_list *next = list->next; struct merge_simplify_state *st; + + commit = list->item; + next = list->next; free(list); list = next; st = locate_simplify_state(revs, commit); @@ -53,11 +53,17 @@ int check_filename(const char *prefix, const char *arg) die_errno("failed to stat '%s'", arg); } -static void NORETURN die_verify_filename(const char *prefix, const char *arg) +static void NORETURN die_verify_filename(const char *prefix, + const char *arg, + int diagnose_misspelt_rev) { unsigned char sha1[20]; unsigned mode; + if (!diagnose_misspelt_rev) + die("%s: no such path in the working tree.\n" + "Use '-- <path>...' to specify paths that do not exist locally.", + arg); /* * Saying "'(icase)foo' does not exist in the index" when the * user gave us ":(icase)foo" is just stupid. A magic pathspec @@ -80,14 +86,29 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg) * as true, because even if such a filename were to exist, we want * it to be preceded by the "--" marker (or we want the user to * use a format like "./-filename") + * + * The "diagnose_misspelt_rev" is used to provide a user-friendly + * diagnosis when dying upon finding that "name" is not a pathname. + * If set to 1, the diagnosis will try to diagnose "name" as an + * invalid object name (e.g. HEAD:foo). If set to 0, the diagnosis + * will only complain about an inexisting file. + * + * This function is typically called to check that a "file or rev" + * argument is unambiguous. In this case, the caller will want + * diagnose_misspelt_rev == 1 when verifying the first non-rev + * argument (which could have been a revision), and + * diagnose_misspelt_rev == 0 for the next ones (because we already + * saw a filename, there's not ambiguity anymore). */ -void verify_filename(const char *prefix, const char *arg) +void verify_filename(const char *prefix, + const char *arg, + int diagnose_misspelt_rev) { if (*arg == '-') die("bad flag '%s' used after filename", arg); if (check_filename(prefix, arg)) return; - die_verify_filename(prefix, arg); + die_verify_filename(prefix, arg, diagnose_misspelt_rev); } /* diff --git a/sha1_name.c b/sha1_name.c index c6331136d1..5d81ea0564 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1127,7 +1127,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, if (new_filename) filename = new_filename; ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode); - if (only_to_die) { + if (ret && only_to_die) { diagnose_invalid_sha1_path(prefix, filename, tree_sha1, object_name); free(object_name); diff --git a/t/lib-bash.sh b/t/lib-bash.sh new file mode 100644 index 0000000000..11397f747b --- /dev/null +++ b/t/lib-bash.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# +# Ensures that tests are run under Bash; primarily intended for running tests +# of the completion script. + +if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then + # we are in full-on bash mode + true +elif type bash >/dev/null 2>&1; then + # execute in full-on bash mode + unset POSIXLY_CORRECT + exec bash "$0" "$@" +else + echo '1..0 #SKIP skipping bash completion tests; bash not available' + exit 0 +fi + +. ./test-lib.sh diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 55ed955cef..fd10528009 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -130,10 +130,27 @@ test_expect_success 'git-show a large file' ' ' +test_expect_success 'index-pack' ' + git clone file://"`pwd`"/.git foo && + GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack +' + test_expect_success 'repack' ' git repack -ad ' +test_expect_success 'pack-objects with large loose object' ' + SHA1=`git hash-object huge` && + test_create_repo loose && + echo $SHA1 | git pack-objects --stdout | + GIT_ALLOC_LIMIT=0 GIT_DIR=loose/.git git unpack-objects && + echo $SHA1 | GIT_DIR=loose/.git git pack-objects pack && + test_create_repo packed && + mv pack-* packed/.git/objects/pack && + GIT_DIR=packed/.git git cat-file blob $SHA1 >actual && + cmp huge actual +' + test_expect_success 'tar achiving' ' git archive --format=tar HEAD >/dev/null ' diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index 2b962cfda7..79045abb51 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -14,16 +14,15 @@ umask 077 # We need an arbitrary other user give permission to using ACLs. root # is a good candidate: exists on all unices, and it has permission # anyway, so we don't create a security hole running the testsuite. - -setfacl_out="$(setfacl -m u:root:rwx . 2>&1)" -setfacl_ret=$? - -if test $setfacl_ret != 0 -then - say "Unable to use setfacl (output: '$setfacl_out'; return code: '$setfacl_ret')" -else - test_set_prereq SETFACL -fi +test_expect_success 'checking for a working acl setup' ' + if setfacl -m d:m:rwx -m u:root:rwx . && + getfacl . | grep user:root:rwx && + touch should-have-readable-acl && + getfacl should-have-readable-acl | egrep "mask::?rw-" + then + test_set_prereq SETFACL + fi +' if test -z "$LOGNAME" then diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh index 0843a1c13b..c5cb77a0e1 100755 --- a/t/t1506-rev-parse-diagnosis.sh +++ b/t/t1506-rev-parse-diagnosis.sh @@ -171,4 +171,15 @@ test_expect_success 'relative path when startup_info is NULL' ' grep "BUG: startup_info struct is not initialized." error ' +test_expect_success '<commit>:file correctly diagnosed after a pathname' ' + test_must_fail git rev-parse file.txt HEAD:file.txt 1>actual 2>error && + test_i18ngrep ! "exists on disk" error && + test_i18ngrep "no such path in the working tree" error && + cat >expect <<-\EOF && + file.txt + HEAD:file.txt + EOF + test_cmp expect actual +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 025c1c610e..68d61480fb 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -755,4 +755,121 @@ test_expect_success 'rebase-i history with funny messages' ' test_cmp expect actual ' + +test_expect_success 'prepare for rebase -i --exec' ' + git checkout master && + git checkout -b execute && + test_commit one_exec main.txt one_exec && + test_commit two_exec main.txt two_exec && + test_commit three_exec main.txt three_exec +' + + +test_expect_success 'running "git rebase -i --exec git show HEAD"' ' + git rebase -i --exec "git show HEAD" HEAD~2 >actual && + ( + FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && + export FAKE_LINES && + git rebase -i HEAD~2 >expect + ) && + sed -e "1,9d" expect >expected && + test_cmp expected actual +' + + +test_expect_success 'running "git rebase --exec git show HEAD -i"' ' + git reset --hard execute && + git rebase --exec "git show HEAD" -i HEAD~2 >actual && + ( + FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && + export FAKE_LINES && + git rebase -i HEAD~2 >expect + ) && + sed -e "1,9d" expect >expected && + test_cmp expected actual +' + + +test_expect_success 'running "git rebase -ix git show HEAD"' ' + git reset --hard execute && + git rebase -ix "git show HEAD" HEAD~2 >actual && + ( + FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && + export FAKE_LINES && + git rebase -i HEAD~2 >expect + ) && + sed -e "1,9d" expect >expected && + test_cmp expected actual +' + + +test_expect_success 'rebase -ix with several <CMD>' ' + git reset --hard execute && + git rebase -ix "git show HEAD; pwd" HEAD~2 >actual && + ( + FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" && + export FAKE_LINES && + git rebase -i HEAD~2 >expect + ) && + sed -e "1,9d" expect >expected && + test_cmp expected actual +' + + +test_expect_success 'rebase -ix with several instances of --exec' ' + git reset --hard execute && + git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual && + ( + FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2 + exec_git_show_HEAD exec_pwd" && + export FAKE_LINES && + git rebase -i HEAD~2 >expect + ) && + sed -e "1,11d" expect >expected && + test_cmp expected actual +' + + +test_expect_success 'rebase -ix with --autosquash' ' + git reset --hard execute && + git checkout -b autosquash && + echo second >second.txt && + git add second.txt && + git commit -m "fixup! two_exec" && + echo bis >bis.txt && + git add bis.txt && + git commit -m "fixup! two_exec" && + ( + git checkout -b autosquash_actual && + git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual + ) && + git checkout autosquash && + ( + git checkout -b autosquash_expected && + FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" && + export FAKE_LINES && + git rebase -i HEAD~4 >expect + ) && + sed -e "1,13d" expect >expected && + test_cmp expected actual +' + + +test_expect_success 'rebase --exec without -i shows error message' ' + git reset --hard execute && + test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual && + echo "--exec option must be used with --interactive option" >expected && + test_i18ncmp expected actual +' + + +test_expect_success 'rebase -i --exec without <CMD>' ' + git reset --hard execute && + test_must_fail git rebase -i --exec 2>tmp && + sed -e "1d" tmp >actual && + test_must_fail git rebase -h >expected && + test_cmp expected actual && + git checkout master +' + test_done diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index d9d856b87b..300ed910a5 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -418,4 +418,9 @@ test_expect_success \ 'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg && grep "SHA1 COLLISION FOUND" msg' +test_expect_success \ + 'make sure index-pack detects the SHA1 collision (large blobs)' \ + 'test_must_fail git -c core.bigfilethreshold=1 index-pack -o bad.idx test-3.pack 2>msg && + grep "SHA1 COLLISION FOUND" msg' + test_done diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index c6feca44e3..7ff6e0e16c 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -124,4 +124,14 @@ test_expect_success 'cloning non-git directory fails' ' test_must_fail git clone not-a-git-repo not-a-git-repo-clone ' +test_expect_success 'cloning file:// does not hardlink' ' + git clone --bare file://"$(pwd)"/a non-local && + ! repo_is_hardlinked non-local +' + +test_expect_success 'cloning a local path with --no-local does not hardlink' ' + git clone --bare --no-local a force-nonlocal && + ! repo_is_hardlinked force-nonlocal +' + test_done diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index b8cb4906aa..f4f38a5e73 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -30,6 +30,9 @@ test_expect_success 'Report new path with conflict' ' cat >expect <<EOF # On branch side +# You have unmerged paths. +# (fix conflicts and run "git commit") +# # Unmerged paths: # (use "git add/rm <file>..." as appropriate to mark resolution) # @@ -118,4 +121,97 @@ test_expect_success 'git diff-index --cached -C shows 2 copies + 1 unmerged' ' test_cmp expected actual ' + +test_expect_success 'status when conflicts with add and rm advice (deleted by them)' ' + git reset --hard && + git checkout master && + test_commit init main.txt init && + git checkout -b second_branch && + git rm main.txt && + git commit -m "main.txt deleted on second_branch" && + test_commit second conflict.txt second && + git checkout master && + test_commit on_second main.txt on_second && + test_commit master conflict.txt master && + test_must_fail git merge second_branch && + cat >expected <<-\EOF && + # On branch master + # You have unmerged paths. + # (fix conflicts and run "git commit") + # + # Unmerged paths: + # (use "git add/rm <file>..." as appropriate to mark resolution) + # + # both added: conflict.txt + # deleted by them: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'prepare for conflicts' ' + git reset --hard && + git checkout -b conflict && + test_commit one main.txt one && + git branch conflict_second && + git mv main.txt sub_master.txt && + git commit -m "main.txt renamed in sub_master.txt" && + git checkout conflict_second && + git mv main.txt sub_second.txt && + git commit -m "main.txt renamed in sub_second.txt" +' + + +test_expect_success 'status when conflicts with add and rm advice (both deleted)' ' + test_must_fail git merge conflict && + cat >expected <<-\EOF && + # On branch conflict_second + # You have unmerged paths. + # (fix conflicts and run "git commit") + # + # Unmerged paths: + # (use "git add/rm <file>..." as appropriate to mark resolution) + # + # both deleted: main.txt + # added by them: sub_master.txt + # added by us: sub_second.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when conflicts with only rm advice (both deleted)' ' + git reset --hard conflict_second && + test_must_fail git merge conflict && + git add sub_master.txt && + git add sub_second.txt && + cat >expected <<-\EOF && + # On branch conflict_second + # You have unmerged paths. + # (fix conflicts and run "git commit") + # + # Changes to be committed: + # + # new file: sub_master.txt + # + # Unmerged paths: + # (use "git rm <file>..." to mark resolution) + # + # both deleted: main.txt + # + # Untracked files not listed (use -u option to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual && + git reset --hard && + git checkout master +' + + test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 81827e696f..c73bec9551 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -483,21 +483,72 @@ test_expect_success 'set up for relative path tests' ' git add sub && git config -f .gitmodules submodule.sub.path sub && git config -f .gitmodules submodule.sub.url ../subrepo && - cp .git/config pristine-.git-config + cp .git/config pristine-.git-config && + cp .gitmodules pristine-.gitmodules ) ' -test_expect_success 'relative path works with URL' ' +test_expect_success '../subrepo works with URL - ssh://hostname/repo' ' ( cd reltest && cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && git config remote.origin.url ssh://hostname/repo && git submodule init && test "$(git config submodule.sub.url)" = ssh://hostname/subrepo ) ' -test_expect_success 'relative path works with user@host:path' ' +test_expect_success '../subrepo works with port-qualified URL - ssh://hostname:22/repo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url ssh://hostname:22/repo && + git submodule init && + test "$(git config submodule.sub.url)" = ssh://hostname:22/subrepo + ) +' + +# About the choice of the path in the next test: +# - double-slash side-steps path mangling issues on Windows +# - it is still an absolute local path +# - there cannot be a server with a blank in its name just in case the +# path is used erroneously to access a //server/share style path +test_expect_success '../subrepo path works with local path - //somewhere else/repo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url "//somewhere else/repo" && + git submodule init && + test "$(git config submodule.sub.url)" = "//somewhere else/subrepo" + ) +' + +test_expect_success '../subrepo works with file URL - file:///tmp/repo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url file:///tmp/repo && + git submodule init && + test "$(git config submodule.sub.url)" = file:///tmp/subrepo + ) +' + +test_expect_success '../subrepo works with helper URL- helper:://hostname/repo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url helper:://hostname/repo && + git submodule init && + test "$(git config submodule.sub.url)" = helper:://hostname/subrepo + ) +' + +test_expect_success '../subrepo works with scp-style URL - user@host:repo' ' ( cd reltest && cp pristine-.git-config .git/config && @@ -507,6 +558,98 @@ test_expect_success 'relative path works with user@host:path' ' ) ' +test_expect_success '../subrepo works with scp-style URL - user@host:path/to/repo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url user@host:path/to/repo && + git submodule init && + test "$(git config submodule.sub.url)" = user@host:path/to/subrepo + ) +' + +test_expect_success '../subrepo works with relative local path - foo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url foo && + # actual: fails with an error + git submodule init && + test "$(git config submodule.sub.url)" = subrepo + ) +' + +test_expect_success '../subrepo works with relative local path - foo/bar' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url foo/bar && + git submodule init && + test "$(git config submodule.sub.url)" = foo/subrepo + ) +' + +test_expect_success '../subrepo works with relative local path - ./foo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url ./foo && + git submodule init && + test "$(git config submodule.sub.url)" = subrepo + ) +' + +test_expect_success '../subrepo works with relative local path - ./foo/bar' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url ./foo/bar && + git submodule init && + test "$(git config submodule.sub.url)" = foo/subrepo + ) +' + +test_expect_success '../subrepo works with relative local path - ../foo' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url ../foo && + git submodule init && + test "$(git config submodule.sub.url)" = ../subrepo + ) +' + +test_expect_success '../subrepo works with relative local path - ../foo/bar' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + git config remote.origin.url ../foo/bar && + git submodule init && + test "$(git config submodule.sub.url)" = ../foo/subrepo + ) +' + +test_expect_success '../bar/a/b/c works with relative local path - ../foo/bar.git' ' + ( + cd reltest && + cp pristine-.git-config .git/config && + cp pristine-.gitmodules .gitmodules && + mkdir -p a/b/c && + (cd a/b/c; git init) && + git config remote.origin.url ../foo/bar.git && + git submodule add ../bar/a/b/c ./a/b/c && + git submodule init && + test "$(git config submodule.a/b/c.url)" = ../foo/bar/a/b/c + ) +' + test_expect_success 'moving the superproject does not break submodules' ' ( cd addtest && diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 3620215c1f..524d5c1b21 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -26,7 +26,9 @@ test_expect_success setup ' (cd super-clone && git submodule update --init) && git clone super empty-clone && (cd empty-clone && git submodule init) && - git clone super top-only-clone + git clone super top-only-clone && + git clone super relative-clone && + (cd relative-clone && git submodule update --init) ' test_expect_success 'change submodule' ' @@ -86,4 +88,90 @@ test_expect_success '"git submodule sync" should not vivify uninteresting submod ) ' +test_expect_success '"git submodule sync" handles origin URL of the form foo' ' + (cd relative-clone && + git remote set-url origin foo && + git submodule sync && + (cd submodule && + #actual fails with: "cannot strip off url foo + test "$(git config remote.origin.url)" = "../submodule" + ) + ) +' + +test_expect_success '"git submodule sync" handles origin URL of the form foo/bar' ' + (cd relative-clone && + git remote set-url origin foo/bar && + git submodule sync && + (cd submodule && + #actual foo/submodule + test "$(git config remote.origin.url)" = "../foo/submodule" + ) + ) +' + +test_expect_success '"git submodule sync" handles origin URL of the form ./foo' ' + (cd relative-clone && + git remote set-url origin ./foo && + git submodule sync && + (cd submodule && + #actual ./submodule + test "$(git config remote.origin.url)" = "../submodule" + ) + ) +' + +test_expect_success '"git submodule sync" handles origin URL of the form ./foo/bar' ' + (cd relative-clone && + git remote set-url origin ./foo/bar && + git submodule sync && + (cd submodule && + #actual ./foo/submodule + test "$(git config remote.origin.url)" = "../foo/submodule" + ) + ) +' + +test_expect_success '"git submodule sync" handles origin URL of the form ../foo' ' + (cd relative-clone && + git remote set-url origin ../foo && + git submodule sync && + (cd submodule && + #actual ../submodule + test "$(git config remote.origin.url)" = "../../submodule" + ) + ) +' + +test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar' ' + (cd relative-clone && + git remote set-url origin ../foo/bar && + git submodule sync && + (cd submodule && + #actual ../foo/submodule + test "$(git config remote.origin.url)" = "../../foo/submodule" + ) + ) +' + +test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar with deeply nested submodule' ' + (cd relative-clone && + git remote set-url origin ../foo/bar && + mkdir -p a/b/c && + ( cd a/b/c && + git init && + :> .gitignore && + git add .gitignore && + test_tick && + git commit -m "initial commit" ) && + git submodule add ../bar/a/b/c ./a/b/c && + git submodule sync && + (cd a/b/c && + #actual ../foo/bar/a/b/c + test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c" + ) + ) +' + + test_done diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh new file mode 100755 index 0000000000..b3f6eb9c68 --- /dev/null +++ b/t/t7512-status-help.sh @@ -0,0 +1,649 @@ +#!/bin/sh +# +# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas, +# Thomas Nguy, Khoi Nguyen +# Grenoble INP Ensimag +# + +test_description='git status advices' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +set_fake_editor + +test_expect_success 'prepare for conflicts' ' + test_commit init main.txt init && + git branch conflicts && + test_commit on_master main.txt on_master && + git checkout conflicts && + test_commit on_conflicts main.txt on_conflicts +' + + +test_expect_success 'status when conflicts unresolved' ' + test_must_fail git merge master && + cat >expected <<-\EOF && + # On branch conflicts + # You have unmerged paths. + # (fix conflicts and run "git commit") + # + # Unmerged paths: + # (use "git add <file>..." to mark resolution) + # + # both modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when conflicts resolved before commit' ' + git reset --hard conflicts && + test_must_fail git merge master && + echo one >main.txt && + git add main.txt && + cat >expected <<-\EOF && + # On branch conflicts + # All conflicts fixed but you are still merging. + # (use "git commit" to conclude merge) + # + # Changes to be committed: + # + # modified: main.txt + # + # Untracked files not listed (use -u option to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'prepare for rebase conflicts' ' + git reset --hard master && + git checkout -b rebase_conflicts && + test_commit one_rebase main.txt one && + test_commit two_rebase main.txt two && + test_commit three_rebase main.txt three +' + + +test_expect_success 'status when rebase in progress before resolving conflicts' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase HEAD^ --onto HEAD^^ && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently rebasing. + # (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) + # + # Unmerged paths: + # (use "git reset HEAD <file>..." to unstage) + # (use "git add <file>..." to mark resolution) + # + # both modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when rebase in progress before rebase --continue' ' + git reset --hard rebase_conflicts && + test_when_finished "git rebase --abort" && + test_must_fail git rebase HEAD^ --onto HEAD^^ && + echo three >main.txt && + git add main.txt && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently rebasing. + # (all conflicts fixed: run "git rebase --continue") + # + # Changes to be committed: + # (use "git reset HEAD <file>..." to unstage) + # + # modified: main.txt + # + # Untracked files not listed (use -u option to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'prepare for rebase_i_conflicts' ' + git reset --hard master && + git checkout -b rebase_i_conflicts && + test_commit one_unmerge main.txt one_unmerge && + git branch rebase_i_conflicts_second && + test_commit one_master main.txt one_master && + git checkout rebase_i_conflicts_second && + test_commit one_second main.txt one_second +' + + +test_expect_success 'status during rebase -i when conflicts unresolved' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase -i rebase_i_conflicts && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently rebasing. + # (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) + # + # Unmerged paths: + # (use "git reset HEAD <file>..." to unstage) + # (use "git add <file>..." to mark resolution) + # + # both modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status during rebase -i after resolving conflicts' ' + git reset --hard rebase_i_conflicts_second && + test_when_finished "git rebase --abort" && + test_must_fail git rebase -i rebase_i_conflicts && + git add main.txt && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently rebasing. + # (all conflicts fixed: run "git rebase --continue") + # + # Changes to be committed: + # (use "git reset HEAD <file>..." to unstage) + # + # modified: main.txt + # + # Untracked files not listed (use -u option to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when rebasing -i in edit mode' ' + git reset --hard master && + git checkout -b rebase_i_edit && + test_commit one_rebase_i main.txt one && + test_commit two_rebase_i main.txt two && + test_commit three_rebase_i main.txt three && + FAKE_LINES="1 edit 2" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~2 && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when splitting a commit' ' + git reset --hard master && + git checkout -b split_commit && + test_commit one_split main.txt one && + test_commit two_split main.txt two && + test_commit three_split main.txt three && + test_commit four_split main.txt four && + FAKE_LINES="1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git reset HEAD^ && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently splitting a commit during a rebase. + # (Once your working directory is clean, run "git rebase --continue") + # + # Changes not staged for commit: + # (use "git add <file>..." to update what will be committed) + # (use "git checkout -- <file>..." to discard changes in working directory) + # + # modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status after editing the last commit with --amend during a rebase -i' ' + git reset --hard master && + git checkout -b amend_last && + test_commit one_amend main.txt one && + test_commit two_amend main.txt two && + test_commit three_amend main.txt three && + test_commit four_amend main.txt four && + FAKE_LINES="1 2 edit 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git commit --amend -m "foo" && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'prepare for several edits' ' + git reset --hard master && + git checkout -b several_edits && + test_commit one_edits main.txt one && + test_commit two_edits main.txt two && + test_commit three_edits main.txt three && + test_commit four_edits main.txt four +' + + +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" && + git rebase -i HEAD~3 && + git rebase --continue && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (continue first edit) second edit and split' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git rebase --continue && + git reset HEAD^ && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently splitting a commit during a rebase. + # (Once your working directory is clean, run "git rebase --continue") + # + # Changes not staged for commit: + # (use "git add <file>..." to update what will be committed) + # (use "git checkout -- <file>..." to discard changes in working directory) + # + # modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (continue first edit) second edit and amend' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git rebase --continue && + git commit --amend -m "foo" && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (amend first edit) second edit' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git commit --amend -m "a" && + git rebase --continue && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (amend first edit) second edit and split' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git commit --amend -m "b" && + git rebase --continue && + git reset HEAD^ && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently splitting a commit during a rebase. + # (Once your working directory is clean, run "git rebase --continue") + # + # Changes not staged for commit: + # (use "git add <file>..." to update what will be committed) + # (use "git checkout -- <file>..." to discard changes in working directory) + # + # modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (amend first edit) second edit and amend' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git commit --amend -m "c" && + git rebase --continue && + git commit --amend -m "d" && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (split first edit) second edit' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + git rebase -i HEAD~3 && + git reset HEAD^ && + git add main.txt && + git commit -m "e" && + git rebase --continue && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (split first edit) second edit and split' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + 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 && + # Not currently on any branch. + # You are currently splitting a commit during a rebase. + # (Once your working directory is clean, run "git rebase --continue") + # + # Changes not staged for commit: + # (use "git add <file>..." to update what will be committed) + # (use "git checkout -- <file>..." to discard changes in working directory) + # + # modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status: (split first edit) second edit and amend' ' + git reset --hard several_edits && + FAKE_LINES="edit 1 edit 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + 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 && + # Not currently on any branch. + # You are currently editing a commit during a rebase. + # (use "git commit --amend" to amend the current commit) + # (use "git rebase --continue" once you are satisfied with your changes) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'prepare am_session' ' + git reset --hard master && + git checkout -b am_session && + test_commit one_am one.txt "one" && + test_commit two_am two.txt "two" && + test_commit three_am three.txt "three" +' + + +test_expect_success 'status in an am session: file already exists' ' + git checkout -b am_already_exists && + test_when_finished "rm Maildir/* && git am --abort" && + git format-patch -1 -oMaildir && + test_must_fail git am Maildir/*.patch && + cat >expected <<-\EOF && + # On branch am_already_exists + # You are in the middle of an am session. + # (fix conflicts and then run "git am --resolved") + # (use "git am --skip" to skip this patch) + # (use "git am --abort" to restore the original branch) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status in an am session: file does not exist' ' + git reset --hard am_session && + git checkout -b am_not_exists && + git rm three.txt && + git commit -m "delete three.txt" && + test_when_finished "rm Maildir/* && git am --abort" && + git format-patch -1 -oMaildir && + test_must_fail git am Maildir/*.patch && + cat >expected <<-\EOF && + # On branch am_not_exists + # You are in the middle of an am session. + # (fix conflicts and then run "git am --resolved") + # (use "git am --skip" to skip this patch) + # (use "git am --abort" to restore the original branch) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status in an am session: empty patch' ' + git reset --hard am_session && + git checkout -b am_empty && + test_when_finished "rm Maildir/* && git am --abort" && + git format-patch -3 -oMaildir && + git rm one.txt two.txt three.txt && + git commit -m "delete all am_empty" && + echo error >Maildir/0002-two_am.patch && + test_must_fail git am Maildir/*.patch && + cat >expected <<-\EOF && + # On branch am_empty + # You are in the middle of an am session. + # The current patch is empty. + # (use "git am --skip" to skip this patch) + # (use "git am --abort" to restore the original branch) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when bisecting' ' + git reset --hard master && + git checkout -b bisect && + test_commit one_bisect main.txt one && + test_commit two_bisect main.txt two && + test_commit three_bisect main.txt three && + test_when_finished "git bisect reset" && + git bisect start && + git bisect bad && + git bisect good one_bisect && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently bisecting. + # (use "git bisect reset" to get back to the original branch) + # + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when rebase conflicts with statushints disabled' ' + git reset --hard master && + git checkout -b statushints_disabled && + test_when_finished "git config --local advice.statushints true" && + git config --local advice.statushints false && + test_commit one_statushints main.txt one && + test_commit two_statushints main.txt two && + test_commit three_statushints main.txt three && + test_when_finished "git rebase --abort" && + test_must_fail git rebase HEAD^ --onto HEAD^^ && + cat >expected <<-\EOF && + # Not currently on any branch. + # You are currently rebasing. + # + # Unmerged paths: + # both modified: main.txt + # + no changes added to commit + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'prepare for cherry-pick conflicts' ' + git reset --hard master && + git checkout -b cherry_branch && + test_commit one_cherry main.txt one && + test_commit two_cherries main.txt two && + git checkout -b cherry_branch_second && + test_commit second_cherry main.txt second && + git checkout cherry_branch && + test_commit three_cherries main.txt three +' + + +test_expect_success 'status when cherry-picking before resolving conflicts' ' + test_when_finished "git cherry-pick --abort" && + test_must_fail git cherry-pick cherry_branch_second && + cat >expected <<-\EOF && + # On branch cherry_branch + # You are currently cherry-picking. + # (fix conflicts and run "git commit") + # + # Unmerged paths: + # (use "git add <file>..." to mark resolution) + # + # both modified: main.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_expect_success 'status when cherry-picking after resolving conflicts' ' + git reset --hard cherry_branch && + test_when_finished "git cherry-pick --abort" && + test_must_fail git cherry-pick cherry_branch_second && + echo end >main.txt && + git add main.txt && + cat >expected <<-\EOF && + # On branch cherry_branch + # You are currently cherry-picking. + # (all conflicts fixed: run "git commit") + # + # Changes to be committed: + # + # modified: main.txt + # + # Untracked files not listed (use -u option to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 256e6a0b3f..92d7eb47c2 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -3,21 +3,9 @@ # Copyright (c) 2012 Felipe Contreras # -if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then - # we are in full-on bash mode - true -elif type bash >/dev/null 2>&1; then - # execute in full-on bash mode - unset POSIXLY_CORRECT - exec bash "$0" "$@" -else - echo '1..0 #SKIP skipping bash completion tests; bash not available' - exit 0 -fi - test_description='test bash completion' -. ./test-lib.sh +. ./lib-bash.sh complete () { diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh new file mode 100755 index 0000000000..f17c1f8b85 --- /dev/null +++ b/t/t9903-bash-prompt.sh @@ -0,0 +1,456 @@ +#!/bin/sh +# +# Copyright (c) 2012 SZEDER Gábor +# + +test_description='test git-specific bash prompt functions' + +. ./lib-bash.sh + +. "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh" + +actual="$TRASH_DIRECTORY/actual" + +test_expect_success 'setup for prompt tests' ' + mkdir -p subdir/subsubdir && + git init otherrepo && + echo 1 > file && + git add file && + test_tick && + git commit -m initial && + git tag -a -m msg1 t1 && + git checkout -b b1 && + echo 2 > file && + git commit -m "second b1" file && + echo 3 > file && + git commit -m "third b1" file && + git tag -a -m msg2 t2 && + git checkout -b b2 master && + echo 0 > file && + git commit -m "second b2" file && + git checkout master +' + +test_expect_success 'gitdir - from command line (through $__git_dir)' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + ( + __git_dir="$TRASH_DIRECTORY/otherrepo/.git" && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - repo as argument' ' + echo "otherrepo/.git" > expected && + __gitdir "otherrepo" > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - remote as argument' ' + echo "remote" > expected && + __gitdir "remote" > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - .git directory in cwd' ' + echo ".git" > expected && + __gitdir > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - .git directory in parent' ' + echo "$TRASH_DIRECTORY/.git" > expected && + ( + cd subdir/subsubdir && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - cwd is a .git directory' ' + echo "." > expected && + ( + cd .git && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - parent is a .git directory' ' + echo "$TRASH_DIRECTORY/.git" > expected && + ( + cd .git/refs/heads && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + ( + GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + export GIT_DIR && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - $GIT_DIR set while .git directory in parent' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + ( + GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + export GIT_DIR && + cd subdir && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - non-existing $GIT_DIR' ' + ( + GIT_DIR="$TRASH_DIRECTORY/non-existing" && + export GIT_DIR && + test_must_fail __gitdir + ) +' + +test_expect_success 'gitdir - gitfile in cwd' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && + test_when_finished "rm -f subdir/.git" && + ( + cd subdir && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - gitfile in parent' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && + test_when_finished "rm -f subdir/.git" && + ( + cd subdir/subsubdir && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' ' + echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + mkdir otherrepo/dir && + test_when_finished "rm -rf otherrepo/dir" && + ln -s otherrepo/dir link && + test_when_finished "rm -f link" && + ( + cd link && + __gitdir > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'gitdir - not a git repository' ' + ( + cd subdir/subsubdir && + GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" && + export GIT_CEILING_DIRECTORIES && + test_must_fail __gitdir + ) +' + +test_expect_success 'prompt - branch name' ' + printf " (master)" > expected && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - detached head' ' + printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected && + git checkout b1^ && + test_when_finished "git checkout master" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - describe detached head - contains' ' + printf " ((t2~1))" > expected && + git checkout b1^ && + test_when_finished "git checkout master" && + ( + GIT_PS1_DESCRIBE_STYLE=contains && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - describe detached head - branch' ' + printf " ((b1~1))" > expected && + git checkout b1^ && + test_when_finished "git checkout master" && + ( + GIT_PS1_DESCRIBE_STYLE=branch && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - describe detached head - describe' ' + printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) > expected && + git checkout b1^ && + test_when_finished "git checkout master" && + ( + GIT_PS1_DESCRIBE_STYLE=describe && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - describe detached head - default' ' + printf " ((t2))" > expected && + git checkout --detach b1 && + test_when_finished "git checkout master" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - inside .git directory' ' + printf " (GIT_DIR!)" > expected && + ( + cd .git && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - deep inside .git directory' ' + printf " (GIT_DIR!)" > expected && + ( + cd .git/refs/heads && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - inside bare repository' ' + printf " (BARE:master)" > expected && + git init --bare bare.git && + test_when_finished "rm -rf bare.git" && + ( + cd bare.git && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - interactive rebase' ' + printf " (b1|REBASE-i)" > expected + echo "#!$SHELL_PATH" >fake_editor.sh && + cat >>fake_editor.sh <<\EOF && +echo "edit $(git log -1 --format="%h")" > "$1" +EOF + test_when_finished "rm -f fake_editor.sh" && + chmod a+x fake_editor.sh && + test_set_editor "$TRASH_DIRECTORY/fake_editor.sh" && + git checkout b1 && + test_when_finished "git checkout master" && + git rebase -i HEAD^ && + test_when_finished "git rebase --abort" + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - rebase merge' ' + printf " (b2|REBASE-m)" > expected && + git checkout b2 && + test_when_finished "git checkout master" && + test_must_fail git rebase --merge b1 b2 && + test_when_finished "git rebase --abort" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - rebase' ' + printf " ((t2)|REBASE)" > expected && + git checkout b2 && + test_when_finished "git checkout master" && + test_must_fail git rebase b1 b2 && + test_when_finished "git rebase --abort" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - merge' ' + printf " (b1|MERGING)" > expected && + git checkout b1 && + test_when_finished "git checkout master" && + test_must_fail git merge b2 && + test_when_finished "git reset --hard" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - cherry-pick' ' + printf " (master|CHERRY-PICKING)" > expected && + test_must_fail git cherry-pick b1 && + test_when_finished "git reset --hard" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - bisect' ' + printf " (master|BISECTING)" > expected && + git bisect start && + test_when_finished "git bisect reset" && + __git_ps1 > "$actual" && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - clean' ' + printf " (master)" > expected && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - dirty worktree' ' + printf " (master *)" > expected && + echo "dirty" > file && + test_when_finished "git reset --hard" && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - dirty index' ' + printf " (master +)" > expected && + echo "dirty" > file && + test_when_finished "git reset --hard" && + git add -u && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - dirty index and worktree' ' + printf " (master *+)" > expected && + echo "dirty index" > file && + test_when_finished "git reset --hard" && + git add -u && + echo "dirty worktree" > file && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - before root commit' ' + printf " (master #)" > expected && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + cd otherrepo && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - disabled by config' ' + printf " (master)" > expected && + echo "dirty" > file && + test_when_finished "git reset --hard" && + test_config bash.showDirtyState false && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - not shown inside .git directory' ' + printf " (GIT_DIR!)" > expected && + echo "dirty" > file && + test_when_finished "git reset --hard" && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + cd .git && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - stash status indicator - no stash' ' + printf " (master)" > expected && + ( + GIT_PS1_SHOWSTASHSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - stash status indicator - stash' ' + printf " (master $)" > expected && + echo 2 >file && + git stash && + test_when_finished "git stash drop" && + ( + GIT_PS1_SHOWSTASHSTATE=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - stash status indicator - not shown inside .git directory' ' + printf " (GIT_DIR!)" > expected && + echo 2 >file && + git stash && + test_when_finished "git stash drop" && + ( + GIT_PS1_SHOWSTASHSTATE=y && + cd .git && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - untracked files status indicator - no untracked files' ' + printf " (master)" > expected && + ( + GIT_PS1_SHOWUNTRACKEDFILES=y && + cd otherrepo && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - untracked files status indicator - untracked files' ' + printf " (master %%)" > expected && + ( + GIT_PS1_SHOWUNTRACKEDFILES=y && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' ' + printf " (GIT_DIR!)" > expected && + ( + GIT_PS1_SHOWUNTRACKEDFILES=y && + cd .git && + __git_ps1 > "$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - format string starting with dash' ' + printf -- "-master" > expected && + __git_ps1 "-%s" > "$actual" && + test_cmp expected "$actual" +' + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index ad40109432..33a581924e 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1023,6 +1023,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->el = ⪙ } + if (o->dir) { + o->path_exclude_check = xmalloc(sizeof(struct path_exclude_check)); + path_exclude_check_init(o->path_exclude_check, o->dir); + } memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; o->result.timestamp.sec = o->src_index->timestamp.sec; @@ -1148,6 +1152,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options done: free_excludes(&el); + if (o->path_exclude_check) { + path_exclude_check_clear(o->path_exclude_check); + free(o->path_exclude_check); + } return ret; return_failed: @@ -1363,7 +1371,8 @@ static int check_ok_to_remove(const char *name, int len, int dtype, if (ignore_case && icase_exists(o, name, len, st)) return 0; - if (o->dir && excluded(o->dir, name, &dtype)) + if (o->dir && + path_excluded(o->path_exclude_check, name, -1, &dtype)) /* * ce->name is explicitly excluded, so it is Ok to * overwrite it. diff --git a/unpack-trees.h b/unpack-trees.h index 5e432f576e..ec74a9f19a 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -52,6 +52,7 @@ struct unpack_trees_options { const char *prefix; int cache_bottom; struct dir_struct *dir; + struct path_exclude_check *path_exclude_check; struct pathspec *pathspec; merge_fn_t fn; const char *msgs[NB_UNPACK_TREES_ERROR_TYPES]; diff --git a/version.c b/version.c new file mode 100644 index 0000000000..f98d5a654d --- /dev/null +++ b/version.c @@ -0,0 +1,17 @@ +#include "git-compat-util.h" +#include "version.h" + +const char git_version_string[] = GIT_VERSION; + +const char *git_user_agent(void) +{ + static const char *agent = NULL; + + if (!agent) { + agent = getenv("GIT_USER_AGENT"); + if (!agent) + agent = GIT_USER_AGENT; + } + + return agent; +} diff --git a/version.h b/version.h new file mode 100644 index 0000000000..fd9cdd6316 --- /dev/null +++ b/version.h @@ -0,0 +1,8 @@ +#ifndef VERSION_H +#define VERSION_H + +extern const char git_version_string[]; + +const char *git_user_agent(void); + +#endif /* VERSION_H */ diff --git a/wt-status.c b/wt-status.c index dd6d8c4106..c749267c95 100644 --- a/wt-status.c +++ b/wt-status.c @@ -12,6 +12,7 @@ #include "refs.h" #include "submodule.h" #include "column.h" +#include "strbuf.h" static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ @@ -23,6 +24,7 @@ static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_GREEN, /* WT_STATUS_LOCAL_BRANCH */ GIT_COLOR_RED, /* WT_STATUS_REMOTE_BRANCH */ GIT_COLOR_NIL, /* WT_STATUS_ONBRANCH */ + GIT_COLOR_NORMAL, /* WT_STATUS_IN_PROGRESS */ }; static const char *color(int slot, struct wt_status *s) @@ -130,9 +132,34 @@ void wt_status_prepare(struct wt_status *s) static void wt_status_print_unmerged_header(struct wt_status *s) { + int i; + int del_mod_conflict = 0; + int both_deleted = 0; + int not_deleted = 0; const char *c = color(WT_STATUS_HEADER, s); status_printf_ln(s, c, _("Unmerged paths:")); + + for (i = 0; i < s->change.nr; i++) { + struct string_list_item *it = &(s->change.items[i]); + struct wt_status_change_data *d = it->util; + + switch (d->stagemask) { + case 0: + break; + case 1: + both_deleted = 1; + break; + case 3: + case 5: + del_mod_conflict = 1; + break; + default: + not_deleted = 1; + break; + } + } + if (!advice_status_hints) return; if (s->whence != FROM_COMMIT) @@ -141,7 +168,17 @@ static void wt_status_print_unmerged_header(struct wt_status *s) status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference); else status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)")); - status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)")); + + if (!both_deleted) { + if (!del_mod_conflict) + status_printf_ln(s, c, _(" (use \"git add <file>...\" to mark resolution)")); + else + status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)")); + } else if (!del_mod_conflict && !not_deleted) { + status_printf_ln(s, c, _(" (use \"git rm <file>...\" to mark resolution)")); + } else { + status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)")); + } status_printf_ln(s, c, ""); } @@ -728,6 +765,211 @@ static void wt_status_print_tracking(struct wt_status *s) color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } +static int has_unmerged(struct wt_status *s) +{ + int i; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + d = s->change.items[i].util; + if (d->stagemask) + return 1; + } + return 0; +} + +static void show_merge_in_progress(struct wt_status *s, + struct wt_status_state *state, + const char *color) +{ + if (has_unmerged(s)) { + status_printf_ln(s, color, _("You have unmerged paths.")); + if (advice_status_hints) + status_printf_ln(s, color, + _(" (fix conflicts and run \"git commit\")")); + } else { + status_printf_ln(s, color, + _("All conflicts fixed but you are still merging.")); + if (advice_status_hints) + status_printf_ln(s, color, + _(" (use \"git commit\" to conclude merge)")); + } + wt_status_print_trailer(s); +} + +static void show_am_in_progress(struct wt_status *s, + struct wt_status_state *state, + const char *color) +{ + status_printf_ln(s, color, + _("You are in the middle of an am session.")); + if (state->am_empty_patch) + status_printf_ln(s, color, + _("The current patch is empty.")); + if (advice_status_hints) { + if (!state->am_empty_patch) + status_printf_ln(s, color, + _(" (fix conflicts and then run \"git am --resolved\")")); + status_printf_ln(s, color, + _(" (use \"git am --skip\" to skip this patch)")); + status_printf_ln(s, color, + _(" (use \"git am --abort\" to restore the original branch)")); + } + wt_status_print_trailer(s); +} + +static char *read_line_from_git_path(const char *filename) +{ + struct strbuf buf = STRBUF_INIT; + FILE *fp = fopen(git_path("%s", filename), "r"); + if (!fp) { + strbuf_release(&buf); + return NULL; + } + strbuf_getline(&buf, fp, '\n'); + if (!fclose(fp)) { + return strbuf_detach(&buf, NULL); + } else { + strbuf_release(&buf); + return NULL; + } +} + +static int split_commit_in_progress(struct wt_status *s) +{ + int split_in_progress = 0; + char *head = read_line_from_git_path("HEAD"); + char *orig_head = read_line_from_git_path("ORIG_HEAD"); + char *rebase_amend = read_line_from_git_path("rebase-merge/amend"); + char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head"); + + if (!head || !orig_head || !rebase_amend || !rebase_orig_head || + !s->branch || strcmp(s->branch, "HEAD")) + return split_in_progress; + + if (!strcmp(rebase_amend, rebase_orig_head)) { + if (strcmp(head, rebase_amend)) + split_in_progress = 1; + } else if (strcmp(orig_head, rebase_orig_head)) { + split_in_progress = 1; + } + + if (!s->amend && !s->nowarn && !s->workdir_dirty) + split_in_progress = 0; + + free(head); + free(orig_head); + free(rebase_amend); + free(rebase_orig_head); + return split_in_progress; +} + +static void show_rebase_in_progress(struct wt_status *s, + struct wt_status_state *state, + const char *color) +{ + struct stat st; + + if (has_unmerged(s)) { + 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\")")); + status_printf_ln(s, color, + _(" (use \"git rebase --skip\" to skip this patch)")); + status_printf_ln(s, color, + _(" (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 (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 (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 (advice_status_hints && !s->amend) { + status_printf_ln(s, color, + _(" (use \"git commit --amend\" to amend the current commit)")); + status_printf_ln(s, color, + _(" (use \"git rebase --continue\" once you are satisfied with your changes)")); + } + } + wt_status_print_trailer(s); +} + +static void show_cherry_pick_in_progress(struct wt_status *s, + struct wt_status_state *state, + const char *color) +{ + status_printf_ln(s, color, _("You are currently cherry-picking.")); + if (advice_status_hints) { + if (has_unmerged(s)) + status_printf_ln(s, color, + _(" (fix conflicts and run \"git commit\")")); + else + status_printf_ln(s, color, + _(" (all conflicts fixed: run \"git commit\")")); + } + wt_status_print_trailer(s); +} + +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 (advice_status_hints) + status_printf_ln(s, color, + _(" (use \"git bisect reset\" to get back to the original branch)")); + wt_status_print_trailer(s); +} + +static void wt_status_print_state(struct wt_status *s) +{ + const char *state_color = color(WT_STATUS_IN_PROGRESS, s); + struct wt_status_state state; + struct stat st; + + memset(&state, 0, sizeof(state)); + + if (!stat(git_path("MERGE_HEAD"), &st)) { + state.merge_in_progress = 1; + } else if (!stat(git_path("rebase-apply"), &st)) { + if (!stat(git_path("rebase-apply/applying"), &st)) { + state.am_in_progress = 1; + if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size) + state.am_empty_patch = 1; + } else { + state.rebase_in_progress = 1; + } + } 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; + } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) { + state.cherry_pick_in_progress = 1; + } + if (!stat(git_path("BISECT_LOG"), &st)) + state.bisect_in_progress = 1; + + if (state.merge_in_progress) + show_merge_in_progress(s, &state, state_color); + else if (state.am_in_progress) + show_am_in_progress(s, &state, state_color); + else if (state.rebase_in_progress || state.rebase_interactive_in_progress) + show_rebase_in_progress(s, &state, state_color); + else if (state.cherry_pick_in_progress) + show_cherry_pick_in_progress(s, &state, state_color); + if (state.bisect_in_progress) + show_bisect_in_progress(s, &state, state_color); +} + void wt_status_print(struct wt_status *s) { const char *branch_color = color(WT_STATUS_ONBRANCH, s); @@ -750,6 +992,7 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } + wt_status_print_state(s); if (s->is_initial) { status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit")); diff --git a/wt-status.h b/wt-status.h index 14aa9f7e13..c1066a0ec6 100644 --- a/wt-status.h +++ b/wt-status.h @@ -15,6 +15,7 @@ enum color_wt_status { WT_STATUS_LOCAL_BRANCH, WT_STATUS_REMOTE_BRANCH, WT_STATUS_ONBRANCH, + WT_STATUS_IN_PROGRESS, WT_STATUS_MAXSLOT }; @@ -71,6 +72,16 @@ struct wt_status { struct string_list ignored; }; +struct wt_status_state { + int merge_in_progress; + int am_in_progress; + int am_empty_patch; + int rebase_in_progress; + int rebase_interactive_in_progress; + int cherry_pick_in_progress; + int bisect_in_progress; +}; + void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); |