diff --git a/Documentation/RelNotes-1.6.5.8.txt b/Documentation/RelNotes-1.6.5.8.txt new file mode 100644 index 0000000000..8b24bebb96 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.8.txt @@ -0,0 +1,28 @@ +Git v1.6.5.8 Release Notes +========================== + +Fixes since v1.6.5.7 +-------------------- + +* "git count-objects" did not handle packfiles that are bigger than 4G on + platforms with 32-bit off_t. + +* "git rebase -i" did not abort cleanly if it failed to launch the editor. + +* "git blame" did not work well when commit lacked the author name. + +* "git fast-import" choked when handling a tag that points at an object + that is not a commit. + +* "git reset --hard" did not work correctly when GIT_WORK_TREE environment + variable is used to point at the root of the true work tree. + +* "git grep" fed a buffer that is not NUL-terminated to underlying + regexec(). + +* "git checkout -m other" while on a branch that does not have any commit + segfaulted, instead of failing. + +* "git branch -a other" should have diagnosed the command as an error. + +Other minor documentation updates are also included. diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt index 4c88bebb90..f1d0a4ae2d 100644 --- a/Documentation/RelNotes-1.6.6.1.txt +++ b/Documentation/RelNotes-1.6.6.1.txt @@ -4,12 +4,34 @@ Git v1.6.6.1 Release Notes Fixes since v1.6.6 ------------------ + * "git blame" did not work well when commit lacked the author name. + + * "git branch -a name" wasn't diagnosed as an error. + + * "git count-objects" did not handle packfiles that are bigger than 4G on + platforms with 32-bit off_t. + + * "git checkout -m other" while on a branch that does not have any commit + segfaulted, instead of failing. + + * "git fast-import" choked when fed a tag that do not point at a + commit. + + * "git grep" finding from work tree files could have fed garbage to + the underlying regexec(3). + + * "git grep -L" didn't show empty files (they should never match, and + they should always appear in -L output as unmatching). + + * "git rebase -i" did not abort cleanly if it failed to launch the editor. + + * "git reset --hard" did not work correctly when GIT_WORK_TREE environment + variable is used to point at the root of the true work tree. + * http-backend was not listed in the command list in the documentation. -Other minor documentation updates are included. + * Building on FreeBSD (both 7 and 8) needs OLD_ICONV set in the Makefile --- -exec >/var/tmp/1 -O=v1.6.6-4-gd828fdb -echo O=$(git describe maint) -git shortlog --no-merges $O..maint + * "git checkout -m some-branch" while on an unborn branch crashed. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt index 321ae973b7..7a49b475da 100644 --- a/Documentation/RelNotes-1.7.0.txt +++ b/Documentation/RelNotes-1.7.0.txt @@ -50,8 +50,14 @@ Updates since v1.6.6 (portability) + * Some more MSVC portability patches for msysgit port. + + * Minimum Pthreads emulation for msysgit port. + (performance) + * More performance improvement patches for msysgit port. + (usability, bells and whistles) * More commands learned "--quiet" and "--[no-]progress" options. @@ -66,6 +72,11 @@ Updates since v1.6.6 * HTTP transfer can use authentication scheme other than basic (i.e./e.g. digest). + * Switching from a version of superproject that used to have a submodule + to another version of superproject that no longer has it did not remove + the submodule directory when it should (namely, when you are not + interested in the submodule at all and didn't clone/checkout). + * "git checkout A...B" is a way to detach HEAD at the merge base between A and B. @@ -87,6 +98,10 @@ Updates since v1.6.6 * Use of "git reset --merge" has become easier when resetting away a conflicted mess left in the work tree. + * "git rerere" had rerere.autoupdate configuration but there was no way + to countermand it from the command line; --no-rerere-autoupdate option + given to "merge", "revert", etc. fixes this. + * "git status" learned "-s(hort)" output format. (developers) @@ -102,6 +117,6 @@ release, unless otherwise noted. -- exec >/var/tmp/1 -O=v1.6.6-242-gf287c65 +O=v1.6.6-263-ge33fd3c echo O=$(git describe master) git shortlog --no-merges $O..master ^maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 2d6775c135..d40b83f85a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -130,6 +130,14 @@ advice.*:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwritting local changes. Default: true. + resolveConflict:: + Advices shown by various commands when conflicts + prevent the operation from being performed. + Default: true. + implicitIdentity:: + Advice on how to set your identity configuration when + your information is guessed from the system username and + domain name. Default: true. -- core.fileMode:: @@ -656,14 +664,6 @@ color.grep:: `never`), never. When set to `true` or `auto`, use color only when the output is written to the terminal. Defaults to `false`. -color.grep.external:: - The string value of this variable is passed to an external 'grep' - command as a command line option if match highlighting is turned - on. If set to an empty string, no option is passed at all, - turning off coloring for external 'grep' calls; this is the default. - For GNU grep, set it to `--color=always` to highlight matches even - when a pager is used. - color.grep.match:: Use customized color for matches. The value of this variable may be specified as in color.branch.. It is passed using diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 0ac711230e..723a64872f 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -157,6 +157,10 @@ $ git diff -R <2> rewrites (very expensive). <2> Output diff in reverse. +SEE ALSO +-------- +linkgit:git-difftool[1]:: + Show changes using common diff tools Author ------ diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 8e9aed67d7..5c68cff905 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -7,7 +7,7 @@ git-difftool - Show changes using common diff tools SYNOPSIS -------- -'git difftool' [--tool=] [-y|--no-prompt|--prompt] [<'git diff' options>] +'git difftool' [] {0,2} [--] [...] DESCRIPTION ----------- @@ -58,6 +58,18 @@ is set to the name of the temporary file containing the contents of the diff post-image. `$BASE` is provided for compatibility with custom merge tool commands and has the same value as `$LOCAL`. +-x :: +--extcmd=:: + Specify a custom command for viewing diffs. + 'git-difftool' ignores the configured defaults and runs + `$command $LOCAL $REMOTE` when this option is specified. + +-g:: +--gui:: + When 'git-difftool' is invoked with the `-g` or `--gui` option + the default diff tool will be read from the configured + `diff.guitool` variable instead of `diff.tool`. + See linkgit:git-diff[1] for the full list of supported options. CONFIG VARIABLES @@ -68,6 +80,9 @@ difftool equivalents have not been defined. diff.tool:: The default diff tool to use. +diff.guitool:: + The default diff tool to use when `--gui` is specified. + difftool..path:: Override the path for the given tool. This is useful in case your tool is not in the PATH. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index e886c2ef54..67470311e2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s ]... - [-m ] ... + [--[no-]rerere-autoupdate] [-m ] ... 'git merge' HEAD ... DESCRIPTION @@ -33,6 +33,11 @@ include::merge-options.txt[] used to give a good default for automated 'git merge' invocations. +--rerere-autoupdate:: +--no-rerere-autoupdate:: + Allow the rerere mechanism to update the index with the + result of auto-conflict resolution if possible. + ...:: Other branch heads to merge into our branch. You need at least one . Specifying more than one diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index e3eb1e8f19..2a5394b832 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=] - [--repo=] [-f | --force] [-v | --verbose] + [--repo=] [-f | --force] [-v | --verbose] [-u | --set-upstream] [ ...] DESCRIPTION @@ -122,6 +122,13 @@ nor in any Push line of the corresponding remotes file---see below). the name "origin" is used. For this latter case, this option can be used to override the name "origin". In other words, the difference between these two commands + +-u:: +--set-upstream:: + For every branch that is up to date or successfully pushed, add + upstream (tracking) reference, used by argument-less + linkgit:git-pull[1] and other commands. For more information, + see 'branch..merge' in linkgit:git-config[1]. + -------------------------- git push public #1 diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index ca5e1e8653..e2e61d3642 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -308,6 +308,16 @@ which makes little sense. root commits will be rewritten to have as parent instead. +--autosquash:: + When the commit log message begins with "squash! ..." (or + "fixup! ..."), and there is a commit whose title begins with + the same ..., automatically modify the todo list of rebase -i + so that the commit marked for quashing come right after the + commit to be modified, and change the action of the moved + commit from `pick` to `squash` (or `fixup`). ++ +This option is only valid when '--interactive' option is used. + include::merge-strategies.txt[] NOTES @@ -382,9 +392,12 @@ If you just want to edit the commit message for a commit, replace the command "pick" with the command "reword". If you want to fold two or more commits into one, replace the command -"pick" with "squash" for the second and subsequent commit. If the -commits had different authors, it will attribute the squashed commit to -the author of the first commit. +"pick" for the second and subsequent commits with "squash" or "fixup". +If the commits had different authors, the folded commit will be +attributed to the author of the first commit. The suggested commit +message for the folded commit is the concatenation of the commit +messages of the first commit and of those with the "squash" command, +but omits the commit messages of commits with the "fixup" command. 'git-rebase' will stop when "pick" has been replaced with "edit" or when a command fails due to merge errors. When you are done editing @@ -512,8 +525,8 @@ Easy case: The changes are literally the same.:: Hard case: The changes are not the same.:: This happens if the 'subsystem' rebase had conflicts, or used - `\--interactive` to omit, edit, or squash commits; or if the - upstream used one of `commit \--amend`, `reset`, or + `\--interactive` to omit, edit, squash, or fixup commits; or + if the upstream used one of `commit \--amend`, `reset`, or `filter-branch`. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 82045a2522..dc829b333d 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -112,6 +112,9 @@ OPTIONS --remotes:: Show tag refs found in `$GIT_DIR/refs/remotes`. +--show-toplevel:: + Show the absolute path of the top-level directory. + --show-prefix:: When the command is invoked from a subdirectory, show the path of the current directory relative to the top-level diff --git a/Documentation/git.txt b/Documentation/git.txt index 352c23019f..b6df39ba36 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,14 +43,16 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.6/git.html[documentation for release 1.6.6] +* link:v1.6.6.1/git.html[documentation for release 1.6.6.1] * release notes for + link:RelNotes-1.6.6.1.txt[1.6.6.1], link:RelNotes-1.6.6.txt[1.6.6]. -* link:v1.6.5.7/git.html[documentation for release 1.6.5.7] +* link:v1.6.5.8/git.html[documentation for release 1.6.5.8] * release notes for + link:RelNotes-1.6.5.8.txt[1.6.5.8], link:RelNotes-1.6.5.7.txt[1.6.5.7], link:RelNotes-1.6.5.6.txt[1.6.5.6], link:RelNotes-1.6.5.5.txt[1.6.5.5], diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index f762dca440..cfc26c63f5 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -27,6 +27,14 @@ interfaces on top of it called "porcelain". You may not want to use the plumbing directly very often, but it can be good to know what the plumbing does for when the porcelain isn't flushing. +Back when this document was originally written, many porcelain +commands were shell scripts. For simplicity, it still uses them as +examples to illustrate how plumbing is fit together to form the +porcelain commands. The source tree includes some of these scripts in +contrib/examples/ for reference. Although these are not implemented as +shell scripts anymore, the description of what the plumbing layer +commands do is still valid. + [NOTE] Deeper technical details are often marked as Notes, which you can skip on your first reading. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 53a9168ba7..1686a54d22 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -134,6 +134,7 @@ The placeholders are: - '%C(...)': color specification, as described in color.branch.* config option - '%m': left, right or boundary mark - '%n': newline +- '%%': a raw '%' - '%x00': print a byte from a hex code - '%w([[,[,]]])': switch line wrapping, like the -w option of linkgit:git-shortlog[1]. diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index a0e0f850f8..afe2759951 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -199,6 +199,10 @@ character if the letter `n` appears after a `%`. The function returns the length of the placeholder recognized and `strbuf_expand()` skips over it. + +The format `%%` is automatically expanded to a single `%` as a quoting +mechanism; callers do not need to handle the `%` placeholder themselves, +and the callback function will not be invoked for this placeholder. ++ All other characters (non-percent and not skipped ones) are copied verbatim to the strbuf. If the callback returned zero, meaning that the placeholder is unknown, then the percent sign is copied, too. @@ -214,6 +218,13 @@ which can be used by the programmer of the callback as she sees fit. placeholder and replacement string. The array needs to be terminated by an entry with placeholder set to NULL. +`strbuf_addbuf_percentquote`:: + + Append the contents of one strbuf to another, quoting any + percent signs ("%") into double-percents ("%%") in the + destination. This is useful for literal data to be fed to either + strbuf_expand or to the *printf family of functions. + `strbuf_addf`:: Add a formatted string to the buffer. diff --git a/Makefile b/Makefile index 7632db77e7..ad890ec925 100644 --- a/Makefile +++ b/Makefile @@ -187,10 +187,6 @@ all:: # is a simplified version of the merge sort used in glibc. This is # recommended if Git triggers O(n^2) behavior in your platform's qsort(). # -# Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call -# your external grep (e.g., if your system lacks grep, if its grep is -# broken, or spawning external process is slower than built-in grep git has). -# # Define UNRELIABLE_FSTAT if your system's fstat does not return the same # information on a not yet closed file that lstat would return for the same # file after it was closed. @@ -424,16 +420,6 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-whatchanged$X -ifdef NO_CURL -REMOTE_CURL_PRIMARY = -REMOTE_CURL_ALIASES = -REMOTE_CURL_NAMES = -else -REMOTE_CURL_PRIMARY = git-remote-http$X -REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X -REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) -endif - # what 'all' will build and 'install' will install in gitexecdir, # excluding programs for built-in commands ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) @@ -478,6 +464,7 @@ LIB_H += commit.h LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h +LIB_H += compat/win32/pthread.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -510,6 +497,7 @@ LIB_H += reflog-walk.h LIB_H += refs.h LIB_H += remote.h LIB_H += rerere.h +LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h LIB_H += sha1-lookup.h @@ -605,6 +593,7 @@ LIB_OBJS += refs.o LIB_OBJS += remote.o LIB_OBJS += replace_object.o LIB_OBJS += rerere.o +LIB_OBJS += resolve-undo.o LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o @@ -804,7 +793,6 @@ ifeq ($(uname_S),SunOS) NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease - NO_EXTERNAL_GREP = YesPlease THREADED_DELTA_SEARCH = YesPlease ifeq ($(uname_R),5.7) NEEDS_RESOLV = YesPlease @@ -923,7 +911,6 @@ ifeq ($(uname_S),IRIX) # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease - NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH = /usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease @@ -943,7 +930,6 @@ ifeq ($(uname_S),IRIX64) # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease - NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH=/usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease @@ -995,15 +981,16 @@ ifeq ($(uname_S),Windows) OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease NO_CURL = YesPlease - NO_PTHREADS = YesPlease + NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + THREADED_DELTA_SEARCH = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl CFLAGS = BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib lib = @@ -1045,10 +1032,13 @@ ifneq (,$(findstring MINGW,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease + NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + THREADED_DELTA_SEARCH = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o + COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \ + compat/win32/pthread.o EXTLIBS += -lws2_32 X = .exe ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) @@ -1058,10 +1048,8 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) EXTLIBS += /mingw/lib/libz.a NO_R_TO_GCC_LINKER = YesPlease INTERNAL_QSORT = YesPlease - THREADED_DELTA_SEARCH = YesPlease else NO_CURL = YesPlease - NO_PTHREADS = YesPlease endif endif @@ -1109,6 +1097,9 @@ endif ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL + REMOTE_CURL_PRIMARY = + REMOTE_CURL_ALIASES = + REMOTE_CURL_NAMES = else ifdef CURLDIR # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case. @@ -1117,6 +1108,9 @@ else else CURL_LIBCURL = -lcurl endif + REMOTE_CURL_PRIMARY = git-remote-http$X + REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X + REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" @@ -1350,9 +1344,6 @@ endif ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif -ifdef NO_EXTERNAL_GREP - BASIC_CFLAGS += -DNO_EXTERNAL_GREP -endif ifdef UNRELIABLE_FSTAT BASIC_CFLAGS += -DUNRELIABLE_FSTAT endif @@ -1789,6 +1780,7 @@ TEST_PROGRAMS_NEED_X += test-genrandom TEST_PROGRAMS_NEED_X += test-match-trees TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-path-utils +TEST_PROGRAMS_NEED_X += test-run-command TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-index-version diff --git a/advice.c b/advice.c index cb666acc3c..936d98ba2b 100644 --- a/advice.c +++ b/advice.c @@ -3,6 +3,8 @@ int advice_push_nonfastforward = 1; int advice_status_hints = 1; int advice_commit_before_merge = 1; +int advice_resolve_conflict = 1; +int advice_implicit_identity = 1; static struct { const char *name; @@ -11,6 +13,8 @@ static struct { { "pushnonfastforward", &advice_push_nonfastforward }, { "statushints", &advice_status_hints }, { "commitbeforemerge", &advice_commit_before_merge }, + { "resolveconflict", &advice_resolve_conflict }, + { "implicitidentity", &advice_implicit_identity }, }; int git_default_advice_config(const char *var, const char *value) @@ -27,3 +31,17 @@ int git_default_advice_config(const char *var, const char *value) return 0; } + +void NORETURN die_resolve_conflict(const char *me) +{ + if (advice_resolve_conflict) + /* + * Message used both when 'git commit' fails and when + * other commands doing a merge do. + */ + die("'%s' is not possible because you have unmerged files.\n" + "Please, fix them up in the work tree, and then use 'git add/rm ' as\n" + "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me); + else + die("'%s' is not possible because you have unmerged files.", me); +} diff --git a/advice.h b/advice.h index 3de5000d8c..9b7a3ad1ca 100644 --- a/advice.h +++ b/advice.h @@ -1,10 +1,16 @@ #ifndef ADVICE_H #define ADVICE_H +#include "git-compat-util.h" + extern int advice_push_nonfastforward; extern int advice_status_hints; extern int advice_commit_before_merge; +extern int advice_resolve_conflict; +extern int advice_implicit_identity; int git_default_advice_config(const char *var, const char *value); +extern void NORETURN die_resolve_conflict(const char *me); + #endif /* ADVICE_H */ diff --git a/bisect.c b/bisect.c index f1a1f84aa0..6dc27ee7a6 100644 --- a/bisect.c +++ b/bisect.c @@ -593,7 +593,7 @@ struct commit_list *filter_skipped(struct commit_list *list, * is increased by one between each call, but that should not matter * for this application. */ -int get_prn(int count) { +static int get_prn(int count) { count = count * 1103515245 + 12345; return ((unsigned)(count/65536) % PRN_MODULO); } @@ -956,7 +956,7 @@ int bisect_next_all(const char *prefix) { struct rev_info revs; struct commit_list *tried; - int reaches = 0, all = 0, nr; + int reaches = 0, all = 0, nr, steps; const unsigned char *bisect_rev; char bisect_rev_hex[41]; @@ -998,8 +998,10 @@ int bisect_next_all(const char *prefix) } nr = all - reaches - 1; - printf("Bisecting: %d revisions left to test after this " - "(roughly %d steps)\n", nr, estimate_bisect_steps(all)); + steps = estimate_bisect_steps(all); + printf("Bisecting: %d revision%s left to test after this " + "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"), + steps, (steps == 1 ? "" : "s")); return bisect_checkout(bisect_rev_hex); } diff --git a/bisect.h b/bisect.h index 82f8fc1910..0862ce56d7 100644 --- a/bisect.h +++ b/bisect.h @@ -27,8 +27,6 @@ struct rev_list_info { const char *header_prefix; }; -extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); - extern int bisect_next_all(const char *prefix); extern int estimate_bisect_steps(int all); diff --git a/blob.c b/blob.c index bd7d078e1a..ae320bd8fa 100644 --- a/blob.c +++ b/blob.c @@ -23,24 +23,3 @@ int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size) item->object.parsed = 1; return 0; } - -int parse_blob(struct blob *item) -{ - enum object_type type; - void *buffer; - unsigned long size; - int ret; - - if (item->object.parsed) - return 0; - buffer = read_sha1_file(item->object.sha1, &type, &size); - if (!buffer) - return error("Could not read %s", - sha1_to_hex(item->object.sha1)); - if (type != OBJ_BLOB) - return error("Object %s not a blob", - sha1_to_hex(item->object.sha1)); - ret = parse_blob_buffer(item, buffer, size); - free(buffer); - return ret; -} diff --git a/blob.h b/blob.h index ea5d9e9f8b..59b394eea3 100644 --- a/blob.h +++ b/blob.h @@ -13,6 +13,13 @@ struct blob *lookup_blob(const unsigned char *sha1); int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size); -int parse_blob(struct blob *item); +/** + * Blobs do not contain references to other objects and do not have + * structured data that needs parsing. However, code may use the + * "parsed" bit in the struct object for a blob to determine whether + * its content has been found to actually be available, so + * parse_blob_buffer() is used (by object.c) to flag that the object + * has been read successfully from the database. + **/ #endif /* BLOB_H */ diff --git a/builtin-checkout.c b/builtin-checkout.c index e44e238c39..d0b1a728cb 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -17,6 +17,7 @@ #include "blob.h" #include "xdiff-interface.h" #include "ll-merge.h" +#include "resolve-undo.h" static const char * const checkout_usage[] = { "git checkout [options] ", @@ -234,6 +235,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (report_path_error(ps_matched, pathspec, 0)) return 1; + /* "checkout -m path" to recreate conflicted state */ + if (opts->merge) + unmerge_cache(pathspec); + /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -370,6 +375,7 @@ static int merge_working_tree(struct checkout_opts *opts, if (read_cache_preload(NULL) < 0) return error("corrupt index file"); + resolve_undo_clear(); if (opts->force) { ret = reset_tree(new->commit->tree, opts, 1); if (ret) diff --git a/builtin-commit.c b/builtin-commit.c index e644871210..fedcda09d0 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -36,7 +36,20 @@ static const char * const builtin_status_usage[] = { NULL }; +static const char implicit_ident_advice[] = +"Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly:\n" +"\n" +" git config --global user.name Your Name\n" +" git config --global user.email you@example.com\n" +"\n" +"If the identity used for this commit is wrong, you can fix it with:\n" +"\n" +" git commit --amend --author='Your Name '\n"; + static unsigned char head_sha1[20]; + static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file index_lock; /* real index */ @@ -245,6 +258,16 @@ static void create_base_index(void) exit(128); /* We've already reported the error, finish dying */ } +static void refresh_cache_or_die(int refresh_flags) +{ + /* + * refresh_flags contains REFRESH_QUIET, so the only errors + * are for unmerged entries. + */ + if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN)) + die_resolve_conflict("commit"); +} + static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) { int fd; @@ -284,7 +307,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int if (all || (also && pathspec && *pathspec)) { int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache(refresh_flags); + refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) die("unable to write new_index file"); @@ -303,7 +326,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ if (!pathspec || !*pathspec) { fd = hold_locked_index(&index_lock, 1); - refresh_cache(refresh_flags); + refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) die("unable to write new_index file"); @@ -633,7 +656,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, author_ident); free(author_ident); - if (!user_ident_explicitly_given) + if (!user_ident_sufficiently_given()) fprintf(fp, "%s" "# Committer: %s\n", @@ -1055,9 +1078,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1) { struct rev_info rev; struct commit *commit; - static const char *format = "format:%h] %s"; + struct strbuf format = STRBUF_INIT; unsigned char junk_sha1[20]; const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); + struct pretty_print_context pctx = {0}; + struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; commit = lookup_commit(sha1); if (!commit) @@ -1065,6 +1091,25 @@ static void print_summary(const char *prefix, const unsigned char *sha1) if (!commit || parse_commit(commit)) die("could not parse newly created commit"); + strbuf_addstr(&format, "format:%h] %s"); + + format_commit_message(commit, "%an <%ae>", &author_ident, &pctx); + format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx); + if (strbuf_cmp(&author_ident, &committer_ident)) { + strbuf_addstr(&format, "\n Author: "); + strbuf_addbuf_percentquote(&format, &author_ident); + } + if (!user_ident_sufficiently_given()) { + strbuf_addstr(&format, "\n Committer: "); + strbuf_addbuf_percentquote(&format, &committer_ident); + if (advice_implicit_identity) { + strbuf_addch(&format, '\n'); + strbuf_addstr(&format, implicit_ident_advice); + } + } + strbuf_release(&author_ident); + strbuf_release(&committer_ident); + init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); @@ -1075,7 +1120,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; - get_commit_format(format, &rev); + get_commit_format(format.buf, &rev); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; @@ -1094,10 +1139,11 @@ static void print_summary(const char *prefix, const unsigned char *sha1) struct pretty_print_context ctx = {0}; struct strbuf buf = STRBUF_INIT; ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, format + 7, &buf, &ctx); + format_commit_message(commit, format.buf + 7, &buf, &ctx); printf("%s\n", buf.buf); strbuf_release(&buf); } + strbuf_release(&format); } static int git_commit_config(const char *k, const char *v, void *cb) @@ -1255,7 +1301,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "new_index file. Check that disk is not full or quota is\n" "not exceeded, and then \"git reset HEAD\" to recover."); - rerere(); + rerere(0); run_hook(get_index_file(), "post-commit", NULL); if (!quiet) print_summary(prefix, commit_sha1); diff --git a/builtin-grep.c b/builtin-grep.c index 529461fb8f..da854fa94f 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -14,14 +14,7 @@ #include "userdiff.h" #include "grep.h" #include "quote.h" - -#ifndef NO_EXTERNAL_GREP -#ifdef __unix__ -#define NO_EXTERNAL_GREP 0 -#else -#define NO_EXTERNAL_GREP 1 -#endif -#endif +#include "dir.h" static char const * const grep_usage[] = { "git grep [options] [-e] [...] [[--] path...]", @@ -42,8 +35,6 @@ static int grep_config(const char *var, const char *value, void *cb) opt->color = git_config_colorbool(var, value, -1); return 0; } - if (!strcmp(var, "color.grep.external")) - return git_config_string(&(opt->color_external), var, value); if (!strcmp(var, "color.grep.match")) { if (!value) return config_error_nonbool(var); @@ -205,6 +196,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) return 0; } close(i); + data[sz] = 0; if (opt->relative && opt->prefix_length) filename = quote_path_relative(filename, -1, &buf, opt->prefix); i = grep_buffer(opt, filename, data, sz); @@ -213,309 +205,12 @@ static int grep_file(struct grep_opt *opt, const char *filename) return i; } -#if !NO_EXTERNAL_GREP -static int exec_grep(int argc, const char **argv) -{ - pid_t pid; - int status; - - argv[argc] = NULL; - trace_argv_printf(argv, "trace: grep:"); - pid = fork(); - if (pid < 0) - return pid; - if (!pid) { - execvp("grep", (char **) argv); - exit(255); - } - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - return -1; - } - if (WIFEXITED(status)) { - if (!WEXITSTATUS(status)) - return 1; - return 0; - } - return -1; -} - -#define MAXARGS 1000 -#define ARGBUF 4096 -#define push_arg(a) do { \ - if (nr < MAXARGS) argv[nr++] = (a); \ - else die("maximum number of args exceeded"); \ - } while (0) - -/* - * If you send a singleton filename to grep, it does not give - * the name of the file. GNU grep has "-H" but we would want - * that behaviour in a portable way. - * - * So we keep two pathnames in argv buffer unsent to grep in - * the main loop if we need to do more than one grep. - */ -static int flush_grep(struct grep_opt *opt, - int argc, int arg0, const char **argv, int *kept) -{ - int status; - int count = argc - arg0; - const char *kept_0 = NULL; - - if (count <= 2) { - /* - * Because we keep at least 2 paths in the call from - * the main loop (i.e. kept != NULL), and MAXARGS is - * far greater than 2, this usually is a call to - * conclude the grep. However, the user could attempt - * to overflow the argv buffer by giving too many - * options to leave very small number of real - * arguments even for the call in the main loop. - */ - if (kept) - die("insanely many options to grep"); - - /* - * If we have two or more paths, we do not have to do - * anything special, but we need to push /dev/null to - * get "-H" behaviour of GNU grep portably but when we - * are not doing "-l" nor "-L" nor "-c". - */ - if (count == 1 && - !opt->name_only && - !opt->unmatch_name_only && - !opt->count) { - argv[argc++] = "/dev/null"; - argv[argc] = NULL; - } - } - - else if (kept) { - /* - * Called because we found many paths and haven't finished - * iterating over the cache yet. We keep two paths - * for the concluding call. argv[argc-2] and argv[argc-1] - * has the last two paths, so save the first one away, - * replace it with NULL while sending the list to grep, - * and recover them after we are done. - */ - *kept = 2; - kept_0 = argv[argc-2]; - argv[argc-2] = NULL; - argc -= 2; - } - - if (opt->pre_context || opt->post_context) { - /* - * grep handles hunk marks between files, but we need to - * do that ourselves between multiple calls. - */ - if (opt->show_hunk_mark) - write_or_die(1, "--\n", 3); - else - opt->show_hunk_mark = 1; - } - - status = exec_grep(argc, argv); - - if (kept_0) { - /* - * Then recover them. Now the last arg is beyond the - * terminating NULL which is at argc, and the second - * from the last is what we saved away in kept_0 - */ - argv[arg0++] = kept_0; - argv[arg0] = argv[argc+1]; - } - return status; -} - -static void grep_add_color(struct strbuf *sb, const char *escape_seq) -{ - size_t orig_len = sb->len; - - while (*escape_seq) { - if (*escape_seq == 'm') - strbuf_addch(sb, ';'); - else if (*escape_seq != '\033' && *escape_seq != '[') - strbuf_addch(sb, *escape_seq); - escape_seq++; - } - if (sb->len > orig_len && sb->buf[sb->len - 1] == ';') - strbuf_setlen(sb, sb->len - 1); -} - -static int has_skip_worktree_entry(struct grep_opt *opt, const char **paths) -{ - int nr; - for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) - continue; - if (ce_skip_worktree(ce)) - return 1; - } - return 0; -} - -static int external_grep(struct grep_opt *opt, const char **paths, int cached) -{ - int i, nr, argc, hit, len, status; - const char *argv[MAXARGS+1]; - char randarg[ARGBUF]; - char *argptr = randarg; - struct grep_pat *p; - - if (opt->extended || (opt->relative && opt->prefix_length) - || has_skip_worktree_entry(opt, paths)) - return -1; - len = nr = 0; - push_arg("grep"); - if (opt->fixed) - push_arg("-F"); - if (opt->linenum) - push_arg("-n"); - if (!opt->pathname) - push_arg("-h"); - if (opt->regflags & REG_EXTENDED) - push_arg("-E"); - if (opt->ignore_case) - push_arg("-i"); - if (opt->binary == GREP_BINARY_NOMATCH) - push_arg("-I"); - if (opt->word_regexp) - push_arg("-w"); - if (opt->name_only) - push_arg("-l"); - if (opt->unmatch_name_only) - push_arg("-L"); - if (opt->null_following_name) - /* in GNU grep git's "-z" translates to "-Z" */ - push_arg("-Z"); - if (opt->count) - push_arg("-c"); - if (opt->post_context || opt->pre_context) { - if (opt->post_context != opt->pre_context) { - if (opt->pre_context) { - push_arg("-B"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->pre_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - if (opt->post_context) { - push_arg("-A"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->post_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - } - else { - push_arg("-C"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->post_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - } - for (p = opt->pattern_list; p; p = p->next) { - push_arg("-e"); - push_arg(p->pattern); - } - if (opt->color) { - struct strbuf sb = STRBUF_INIT; - - grep_add_color(&sb, opt->color_match); - setenv("GREP_COLOR", sb.buf, 1); - - strbuf_reset(&sb); - strbuf_addstr(&sb, "mt="); - grep_add_color(&sb, opt->color_match); - strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se="); - setenv("GREP_COLORS", sb.buf, 1); - - strbuf_release(&sb); - - if (opt->color_external && strlen(opt->color_external) > 0) - push_arg(opt->color_external); - } else { - unsetenv("GREP_COLOR"); - unsetenv("GREP_COLORS"); - } - unsetenv("GREP_OPTIONS"); - - hit = 0; - argc = nr; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - char *name; - int kept; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) - continue; - name = ce->name; - if (name[0] == '-') { - int len = ce_namelen(ce); - name = xmalloc(len + 3); - memcpy(name, "./", 2); - memcpy(name + 2, ce->name, len + 1); - } - argv[argc++] = name; - if (MAXARGS <= argc) { - status = flush_grep(opt, argc, nr, argv, &kept); - if (0 < status) - hit = 1; - argc = nr + kept; - } - if (ce_stage(ce)) { - do { - i++; - } while (i < active_nr && - !strcmp(ce->name, active_cache[i]->name)); - i--; /* compensate for loop control */ - } - } - if (argc > nr) { - status = flush_grep(opt, argc, nr, argv, NULL); - if (0 < status) - hit = 1; - } - return hit; -} -#endif - -static int grep_cache(struct grep_opt *opt, const char **paths, int cached, - int external_grep_allowed) +static int grep_cache(struct grep_opt *opt, const char **paths, int cached) { int hit = 0; int nr; read_cache(); -#if !NO_EXTERNAL_GREP - /* - * Use the external "grep" command for the case where - * we grep through the checked-out files. It tends to - * be a lot more optimized - */ - if (!cached && external_grep_allowed) { - hit = external_grep(opt, paths, cached); - if (hit >= 0) - return hit; - hit = 0; - } -#endif - for (nr = 0; nr < active_nr; nr++) { struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) @@ -625,6 +320,21 @@ static int grep_object(struct grep_opt *opt, const char **paths, die("unable to grep from object of type %s", typename(obj->type)); } +static int grep_directory(struct grep_opt *opt, const char **paths) +{ + struct dir_struct dir; + int i, hit = 0; + + memset(&dir, 0, sizeof(dir)); + setup_standard_excludes(&dir); + + fill_directory(&dir, paths); + for (i = 0; i < dir.nr; i++) + hit |= grep_file(opt, dir.entries[i]->name); + free_grep_patterns(opt); + return hit; +} + static int context_callback(const struct option *opt, const char *arg, int unset) { @@ -712,16 +422,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; int cached = 0; - int external_grep_allowed = 1; int seen_dashdash = 0; + int external_grep_allowed__ignored; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; int i; int dummy; + int nongit = 0, use_index = 1; struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), + OPT_BOOLEAN(0, "index", &use_index, + "--no-index finds in contents not managed by git"), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, "show non-matching lines"), @@ -795,18 +508,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), -#if NO_EXTERNAL_GREP - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, - "allow calling of grep(1) (ignored by this build)"), -#else - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, - "allow calling of grep(1) (default)"), -#endif + OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, + "allow calling of grep(1) (ignored by this build)"), { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; + prefix = setup_git_directory_gently(&nongit); + /* * 'git grep -h', unlike 'git grep -h ', is a request * to show usage information and exit. @@ -844,6 +554,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + if (use_index && nongit) + /* die the same way as if we did it at the beginning */ + setup_git_directory(); + /* First unrecognized non-option token */ if (argc > 0 && !opt.pattern_list) { append_grep_pattern(&opt, argv[0], "command line", 0, @@ -852,8 +566,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc--; } - if ((opt.color && !opt.color_external) || opt.funcname) - external_grep_allowed = 0; if (!opt.pattern_list) die("no pattern given."); if (!opt.fixed && opt.ignore_case) @@ -896,10 +608,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) paths[1] = NULL; } + if (!use_index) { + if (cached) + die("--cached cannot be used with --no-index."); + if (list.nr) + die("--no-index cannot be used with revs."); + return !grep_directory(&opt, paths); + } + if (!list.nr) { if (!cached) setup_work_tree(); - return !grep_cache(&opt, paths, cached, external_grep_allowed); + return !grep_cache(&opt, paths, cached); } if (cached) diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 738215768e..b065061392 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -11,6 +11,8 @@ #include "builtin.h" #include "tree.h" #include "parse-options.h" +#include "resolve-undo.h" +#include "string-list.h" static int abbrev; static int show_deleted; @@ -18,6 +20,7 @@ static int show_cached; static int show_others; static int show_stage; static int show_unmerged; +static int show_resolve_undo; static int show_modified; static int show_killed; static int show_valid_bit; @@ -38,6 +41,7 @@ static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; +static const char *tag_resolve_undo = ""; static void show_dir_entry(const char *tag, struct dir_entry *ent) { @@ -156,6 +160,38 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) write_name_quoted(ce->name + offset, stdout, line_terminator); } +static int show_one_ru(struct string_list_item *item, void *cbdata) +{ + int offset = prefix_offset; + const char *path = item->string; + struct resolve_undo_info *ui = item->util; + int i, len; + + len = strlen(path); + if (len < prefix_len) + return 0; /* outside of the prefix */ + if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) + return 0; /* uninterested */ + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], + abbrev + ? find_unique_abbrev(ui->sha1[i], abbrev) + : sha1_to_hex(ui->sha1[i]), + i + 1); + write_name_quoted(path + offset, stdout, line_terminator); + } + return 0; +} + +static void show_ru_info(const char *prefix) +{ + if (!the_index.resolve_undo) + return; + for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); +} + static void show_files(struct dir_struct *dir, const char *prefix) { int i; @@ -458,6 +494,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) DIR_HIDE_EMPTY_DIRECTORIES), OPT_BOOLEAN('u', "unmerged", &show_unmerged, "show unmerged files in the output"), + OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, + "show resolve-undo information"), { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern", "skip files matching pattern", 0, option_parse_exclude }, @@ -498,6 +536,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) tag_other = "? "; tag_killed = "K "; tag_skip_worktree = "S "; + tag_resolve_undo = "U "; } if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) require_work_tree = 1; @@ -536,7 +575,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) /* With no flags, we default to showing the cached files */ if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified)) + show_killed | show_modified | show_resolve_undo)) show_cached = 1; if (prefix) @@ -551,6 +590,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) overlay_tree_on_cache(with_tree, prefix); } show_files(&dir, prefix); + if (show_resolve_undo) + show_ru_info(prefix); if (ps_matched) { int bad; diff --git a/builtin-merge.c b/builtin-merge.c index f1c84d759d..9f60ffa2cd 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -24,6 +24,7 @@ #include "rerere.h" #include "help.h" #include "merge-recursive.h" +#include "resolve-undo.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -52,6 +53,7 @@ static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; static const char *branch; static int verbosity; +static int allow_rerere_auto; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -170,6 +172,7 @@ static struct option builtin_merge_options[] = { "allow fast-forward (default)"), OPT_BOOLEAN(0, "ff-only", &fast_forward_only, "abort if fast-forward is not possible"), + OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", "merge strategy to use", option_parse_strategy), OPT_CALLBACK('m', "message", &merge_msg, "message", @@ -604,6 +607,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, discard_cache(); if (read_cache() < 0) die("failed to read the cache"); + resolve_undo_clear(); return ret; } } @@ -618,11 +622,10 @@ static void count_diff_files(struct diff_queue_struct *q, static int count_unmerged_entries(void) { - const struct index_state *state = &the_index; int i, ret = 0; - for (i = 0; i < state->cache_nr; i++) - if (ce_stage(state->cache[i])) + for (i = 0; i < active_nr; i++) + if (ce_stage(active_cache[i])) ret++; return ret; @@ -790,7 +793,7 @@ static int suggest_conflicts(void) } } fclose(fp); - rerere(); + rerere(allow_rerere_auto); printf("Automatic merge failed; " "fix conflicts and then commit the result.\n"); return 1; @@ -847,12 +850,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - if (file_exists(git_path("MERGE_HEAD"))) - die("You have not concluded your merge. (MERGE_HEAD exists)"); - if (read_cache_unmerged()) - die("You are in the middle of a conflicted merge." - " (index unmerged)"); + if (read_cache_unmerged()) { + die_resolve_conflict("merge"); + } + if (file_exists(git_path("MERGE_HEAD"))) { + /* + * There is no unmerged entry, don't advise 'git + * add/rm ', just 'git commit'. + */ + if (advice_resolve_conflict) + die("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge."); + else + die("You have not concluded your merge (MERGE_HEAD exists)."); + } + resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 4429d53a1e..890f45cf20 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1256,15 +1256,15 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, #ifdef THREADED_DELTA_SEARCH -static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t read_mutex; #define read_lock() pthread_mutex_lock(&read_mutex) #define read_unlock() pthread_mutex_unlock(&read_mutex) -static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t cache_mutex; #define cache_lock() pthread_mutex_lock(&cache_mutex) #define cache_unlock() pthread_mutex_unlock(&cache_mutex) -static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t progress_mutex; #define progress_lock() pthread_mutex_lock(&progress_mutex) #define progress_unlock() pthread_mutex_unlock(&progress_mutex) @@ -1591,7 +1591,26 @@ struct thread_params { unsigned *processed; }; -static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t progress_cond; + +/* + * Mutex and conditional variable can't be statically-initialized on Windows. + */ +static void init_threaded_search(void) +{ + pthread_mutex_init(&read_mutex, NULL); + pthread_mutex_init(&cache_mutex, NULL); + pthread_mutex_init(&progress_mutex, NULL); + pthread_cond_init(&progress_cond, NULL); +} + +static void cleanup_threaded_search(void) +{ + pthread_cond_destroy(&progress_cond); + pthread_mutex_destroy(&read_mutex); + pthread_mutex_destroy(&cache_mutex); + pthread_mutex_destroy(&progress_mutex); +} static void *threaded_find_deltas(void *arg) { @@ -1630,10 +1649,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, struct thread_params *p; int i, ret, active_threads = 0; + init_threaded_search(); + if (!delta_search_threads) /* --threads=0 means autodetect */ delta_search_threads = online_cpus(); if (delta_search_threads <= 1) { find_deltas(list, &list_size, window, depth, processed); + cleanup_threaded_search(); return; } if (progress > pack_to_stdout) @@ -1748,6 +1770,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, active_threads--; } } + cleanup_threaded_search(); free(p); } diff --git a/builtin-push.c b/builtin-push.c index 28a26e7db2..5df66081a6 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -218,6 +218,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), + OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", + TRANSPORT_PUSH_SET_UPSTREAM), OPT_END() }; diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 50413ca17d..5fda9905fc 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -13,6 +13,7 @@ #include "dir.h" #include "builtin.h" #include "parse-options.h" +#include "resolve-undo.h" static int nr_trees; static struct tree *trees[MAX_UNPACK_TREES]; @@ -124,6 +125,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("You need to resolve your current index first"); stage = opts.merge = 1; } + resolve_undo_clear(); for (i = 0; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin-rerere.c b/builtin-rerere.c index 2be9ffb77b..25f507a2f1 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -103,15 +103,27 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { struct string_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd; + int i, fd, flags = 0; + if (2 < argc) { + if (!strcmp(argv[1], "-h")) + usage(git_rerere_usage); + if (!strcmp(argv[1], "--rerere-autoupdate")) + flags = RERERE_AUTOUPDATE; + else if (!strcmp(argv[1], "--no-rerere-autoupdate")) + flags = RERERE_NOAUTOUPDATE; + if (flags) { + argc--; + argv++; + } + } if (argc < 2) - return rerere(); + return rerere(flags); - if (!strcmp(argv[1], "-h")) - usage(git_rerere_usage); + if (!strcmp(argv[1], "forget")) + return rerere_forget(argv + 2); - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; diff --git a/builtin-rev-list.c b/builtin-rev-list.c index cd97ded4d2..c924b3a2c7 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -253,7 +253,7 @@ static void print_var_int(const char *var, int val) printf("%s=%d\n", var, val); } -int show_bisect_vars(struct rev_list_info *info, int reaches, int all) +static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { int cnt, flags = info->bisect_show_flags; char hex[41] = ""; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 37d0233521..cbe5b428ad 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -581,6 +581,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_remote_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--show-toplevel")) { + const char *work_tree = get_git_work_tree(); + if (work_tree) + puts(work_tree); + continue; + } if (!strcmp(arg, "--show-prefix")) { if (prefix) puts(prefix); diff --git a/builtin-revert.c b/builtin-revert.c index 151aa6a981..8ac86f0943 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -38,6 +38,7 @@ static const char * const cherry_pick_usage[] = { static int edit, no_replay, no_commit, mainline, signoff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; +static int allow_rerere_auto; static const char *me; @@ -57,6 +58,7 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_END(), }; @@ -233,6 +235,19 @@ static struct tree *empty_tree(void) return tree; } +static NORETURN void die_dirty_index(const char *me) +{ + if (read_cache_unmerged()) { + die_resolve_conflict(me); + } else { + if (advice_commit_before_merge) + die("Your local changes would be overwritten by %s.\n" + "Please, commit your changes or stash them to proceed.", me); + else + die("Your local changes would be overwritten by %s.\n", me); + } +} + static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; @@ -269,7 +284,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); if (index_differs_from("HEAD", 0)) - die ("Dirty index: cannot %s", me); + die_dirty_index(me); } discard_cache(); @@ -395,7 +410,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed.%s\n", me, help_msg(commit->object.sha1)); - rerere(); + rerere(allow_rerere_auto); exit(1); } if (commit_lock_file(&msg_file) < 0) diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 8fffdbf200..76c72065de 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -406,52 +406,22 @@ int send_pack(struct send_pack_args *args, */ new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { - - if (ref->peer_ref) - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!args->send_mirror) + if (!ref->peer_ref && !args->send_mirror) continue; - ref->deletion = is_null_sha1(ref->new_sha1); + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: + continue; + default: + ; /* do nothing */ + } + if (ref->deletion && !allow_deleting_refs) { ref->status = REF_STATUS_REJECT_NODELETE; continue; } - if (!ref->deletion && - !hashcmp(ref->old_sha1, ref->new_sha1)) { - ref->status = REF_STATUS_UPTODATE; - continue; - } - - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. - * - * (1) if the old thing does not exist, it is OK. - * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. - * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. - * - * (4) regardless of all of the above, removing :B is - * always allowed. - */ - - ref->nonfastforward = - !ref->deletion && - !is_null_sha1(ref->old_sha1) && - (!has_sha1_file(ref->old_sha1) - || !ref_newer(ref->new_sha1, ref->old_sha1)); - - if (ref->nonfastforward && !ref->force && !args->force_update) { - ref->status = REF_STATUS_REJECT_NONFASTFORWARD; - continue; - } if (!ref->deletion) new_refs++; @@ -673,6 +643,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; + set_ref_status_for_push(remote_refs, args.send_mirror, + args.force_update); + ret = send_pack(&args, fd, conn, remote_refs, &extra_have); if (helper_status) diff --git a/builtin-update-index.c b/builtin-update-index.c index a64a752990..3ab214d24e 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -9,6 +9,7 @@ #include "tree-walk.h" #include "builtin.h" #include "refs.h" +#include "resolve-undo.h" /* * Default to not allowing changes to the list of files. The @@ -440,7 +441,18 @@ static int unresolve_one(const char *path) /* See if there is such entry in the index. */ pos = cache_name_pos(path, namelen); - if (pos < 0) { + if (0 <= pos) { + /* already merged */ + pos = unmerge_cache_entry_at(pos); + if (pos < active_nr) { + struct cache_entry *ce = active_cache[pos]; + if (ce_stage(ce) && + ce_namelen(ce) == namelen && + !memcmp(ce->name, path, namelen)) + return 0; + } + /* no resolve-undo information; fall back */ + } else { /* If there isn't, either it is unmerged, or * resolved as "removed" by mistake. We do not * want to do anything in the former case. @@ -719,6 +731,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) verbose = 1; continue; } + if (!strcmp(path, "--clear-resolve-undo")) { + resolve_undo_clear(); + continue; + } if (!strcmp(path, "-h") || !strcmp(path, "--help")) usage(update_index_usage); die("unknown option %s", path); diff --git a/cache.h b/cache.h index cf36c8160e..5abcb09fcf 100644 --- a/cache.h +++ b/cache.h @@ -288,6 +288,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce) struct index_state { struct cache_entry **cache; unsigned int cache_nr, cache_alloc, cache_changed; + struct string_list *resolve_undo; struct cache_tree *cache_tree; struct cache_time timestamp; void *alloc; @@ -342,6 +343,9 @@ static inline void remove_name_hash(struct cache_entry *ce) #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen)) +#define resolve_undo_clear() resolve_undo_clear_index(&the_index) +#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at) +#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec) #endif enum object_type { @@ -451,7 +455,6 @@ extern int index_name_pos(const struct index_state *, const char *name, int name #define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ #define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); -extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); extern int remove_index_entry_at(struct index_state *, int pos); extern void remove_marked_cache_entries(struct index_state *istate); @@ -481,9 +484,6 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); -/* "careful lstat()" */ -extern int check_path(const char *path, int len, struct stat *st, int skiplen); - #define REFRESH_REALLY 0x0001 /* ignore_valid */ #define REFRESH_UNMERGED 0x0002 /* allow unmerged */ #define REFRESH_QUIET 0x0004 /* be quiet about it */ @@ -627,7 +627,6 @@ static inline void hashclr(unsigned char *hash) { memset(hash, 0, 20); } -extern int is_empty_blob_sha1(const unsigned char *sha1); #define EMPTY_TREE_SHA1_HEX \ "4b825dc642cb6eb9a060e54bf8d69288fbee4904" @@ -697,7 +696,6 @@ extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); extern int has_loose_object_nonlocal(const unsigned char *sha1); -extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); extern const signed char hexval_table[256]; @@ -798,8 +796,6 @@ extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); extern int has_symlink_or_noent_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); -extern void invalidate_lstat_cache(const char *name, int len); -extern void clear_lstat_cache(void); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); @@ -939,7 +935,11 @@ extern const char *config_exclusive_filename; #define MAX_GITNAME (1000) extern char git_default_email[MAX_GITNAME]; extern char git_default_name[MAX_GITNAME]; +#define IDENT_NAME_GIVEN 01 +#define IDENT_MAIL_GIVEN 02 +#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN) extern int user_ident_explicitly_given; +extern int user_ident_sufficiently_given(void); extern const char *git_commit_encoding; extern const char *git_log_output_encoding; diff --git a/commit.h b/commit.h index e5332efcfc..24128d7a2a 100644 --- a/commit.h +++ b/commit.h @@ -73,7 +73,6 @@ struct pretty_print_context struct reflog_walk_info *reflog_info; }; -extern int non_ascii(int); extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern char *reencode_commit_message(const struct commit *commit, diff --git a/compat/mingw.c b/compat/mingw.c index 0d73f15fa8..ab65f77ab9 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -3,9 +3,7 @@ #include #include "../strbuf.h" -#include - -static int err_win_to_posix(DWORD winerr) +int err_win_to_posix(DWORD winerr) { int error = ENOSYS; switch(winerr) { @@ -142,12 +140,20 @@ int mingw_open (const char *filename, int oflags, ...) return fd; } -static inline time_t filetime_to_time_t(const FILETIME *ft) +/* + * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. + * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. + */ +static inline long long filetime_to_hnsec(const FILETIME *ft) { long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ - winTime /= 10000000; /* Nano to seconds resolution */ - return (time_t)winTime; + /* Windows to Unix Epoch conversion */ + return winTime - 116444736000000000LL; +} + +static inline time_t filetime_to_time_t(const FILETIME *ft) +{ + return (time_t)(filetime_to_hnsec(ft) / 10000000); } /* We keep the do_lstat code in a separate function to avoid recursion. @@ -283,64 +289,37 @@ int mkstemp(char *template) int gettimeofday(struct timeval *tv, void *tz) { - SYSTEMTIME st; - struct tm tm; - GetSystemTime(&st); - tm.tm_year = st.wYear-1900; - tm.tm_mon = st.wMonth-1; - tm.tm_mday = st.wDay; - tm.tm_hour = st.wHour; - tm.tm_min = st.wMinute; - tm.tm_sec = st.wSecond; - tv->tv_sec = tm_to_time_t(&tm); - if (tv->tv_sec < 0) - return -1; - tv->tv_usec = st.wMilliseconds*1000; + FILETIME ft; + long long hnsec; + + GetSystemTimeAsFileTime(&ft); + hnsec = filetime_to_hnsec(&ft); + tv->tv_sec = hnsec / 10000000; + tv->tv_usec = (hnsec % 10000000) / 10; return 0; } int pipe(int filedes[2]) { - int fd; - HANDLE h[2], parent; + HANDLE h[2]; - if (_pipe(filedes, 8192, 0) < 0) - return -1; - - parent = GetCurrentProcess(); - - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]), - parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); + /* this creates non-inheritable handles */ + if (!CreatePipe(&h[0], &h[1], NULL, 8192)) { + errno = err_win_to_posix(GetLastError()); return -1; } - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]), - parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); - CloseHandle(h[0]); - return -1; - } - fd = _open_osfhandle((int)h[0], O_NOINHERIT); - if (fd < 0) { - close(filedes[0]); - close(filedes[1]); + filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT); + if (filedes[0] < 0) { CloseHandle(h[0]); CloseHandle(h[1]); return -1; } - close(filedes[0]); - filedes[0] = fd; - fd = _open_osfhandle((int)h[1], O_NOINHERIT); - if (fd < 0) { + filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT); + if (filedes[0] < 0) { close(filedes[0]); - close(filedes[1]); CloseHandle(h[1]); return -1; } - close(filedes[1]); - filedes[1] = fd; return 0; } @@ -638,8 +617,8 @@ static int env_compare(const void *a, const void *b) return strcasecmp(*ea, *eb); } -static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, - int prepend_cmd) +static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, + int prepend_cmd, int fhin, int fhout, int fherr) { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -675,9 +654,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = (HANDLE) _get_osfhandle(0); - si.hStdOutput = (HANDLE) _get_osfhandle(1); - si.hStdError = (HANDLE) _get_osfhandle(2); + si.hStdInput = (HANDLE) _get_osfhandle(fhin); + si.hStdOutput = (HANDLE) _get_osfhandle(fhout); + si.hStdError = (HANDLE) _get_osfhandle(fherr); /* concatenate argv, quoting args as we go */ strbuf_init(&args, 0); @@ -732,7 +711,14 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, return (pid_t)pi.hProcess; } -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) +static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, + int prepend_cmd) +{ + return mingw_spawnve_fd(cmd, argv, env, prepend_cmd, 0, 1, 2); +} + +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + int fhin, int fhout, int fherr) { pid_t pid; char **path = get_path_split(); @@ -754,13 +740,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) pid = -1; } else { - pid = mingw_spawnve(iprog, argv, env, 1); + pid = mingw_spawnve_fd(iprog, argv, env, 1, + fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve(prog, argv, env, 0); + pid = mingw_spawnve_fd(prog, argv, env, 0, + fhin, fhout, fherr); free(prog); } free_path_split(path); @@ -1338,8 +1326,22 @@ static const char *make_backslash_path(const char *path) void mingw_open_html(const char *unixpath) { const char *htmlpath = make_backslash_path(unixpath); + typedef HINSTANCE (WINAPI *T)(HWND, const char *, + const char *, const char *, const char *, INT); + T ShellExecute; + HMODULE shell32; + + shell32 = LoadLibrary("shell32.dll"); + if (!shell32) + die("cannot load shell32.dll"); + ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA"); + if (!ShellExecute) + die("cannot run browser"); + printf("Launching default browser to display HTML ...\n"); ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); + + FreeLibrary(shell32); } int link(const char *oldpath, const char *newpath) diff --git a/compat/mingw.h b/compat/mingw.h index b3d299f5bc..e254fb4e06 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -209,18 +209,21 @@ int mingw_getpagesize(void); * mingw_fstat() instead of fstat() on Windows. */ #define off_t off64_t -#define stat _stati64 #define lseek _lseeki64 +#ifndef ALREADY_DECLARED_STAT_FUNCS +#define stat _stati64 int mingw_lstat(const char *file_name, struct stat *buf); int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat #define _stati64(x,y) mingw_lstat(x,y) +#endif int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env); +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + int fhin, int fhout, int fherr); void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp @@ -307,3 +310,8 @@ struct mingw_dirent #define readdir(x) mingw_readdir(x) struct dirent *mingw_readdir(DIR *dir); #endif // !NO_MINGW_REPLACE_READDIR + +/* + * Used by Pthread API implementation for Windows + */ +extern int err_win_to_posix(DWORD winerr); diff --git a/compat/msvc.h b/compat/msvc.h index 9c753a560f..023aba0238 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -21,30 +21,22 @@ static __inline int strcasecmp (const char *s1, const char *s2) } #undef ERROR -#undef stat -#undef _stati64 -#include "compat/mingw.h" -#undef stat -#define stat _stati64 -#define _stat64(x,y) mingw_lstat(x,y) -/* - Even though _stati64 is normally just defined at _stat64 - on Windows, we specify it here as a proper struct to avoid - compiler warnings about macro redefinition due to magic in - mingw.h. Struct taken from ReactOS (GNU GPL license). -*/ -struct _stati64 { - _dev_t st_dev; - _ino_t st_ino; - unsigned short st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - __int64 st_size; - time_t st_atime; - time_t st_mtime; - time_t st_ctime; -}; +/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead + * of fstat(). We add the declaration of these functions here, suppressing + * the corresponding declarations in mingw.h, so that we can use the + * appropriate structure type (and function) names from the msvc headers. + */ +#define stat _stat64 +int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_fstat(int fd, struct stat *buf); +#define fstat mingw_fstat +#define lstat mingw_lstat +#define _stat64(x,y) mingw_lstat(x,y) +#define ALREADY_DECLARED_STAT_FUNCS + +#include "compat/mingw.h" + +#undef ALREADY_DECLARED_STAT_FUNCS + #endif diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c new file mode 100644 index 0000000000..631c0a46ea --- /dev/null +++ b/compat/win32/pthread.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2009 Andrzej K. Haczewski + * + * DISCLAMER: The implementation is Git-specific, it is subset of original + * Pthreads API, without lots of other features that Git doesn't use. + * Git also makes sure that the passed arguments are valid, so there's + * no need for double-checking. + */ + +#include "../../git-compat-util.h" +#include "pthread.h" + +#include +#include + +static unsigned __stdcall win32_start_routine(void *arg) +{ + pthread_t *thread = arg; + thread->arg = thread->start_routine(thread->arg); + return 0; +} + +int pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg) +{ + thread->arg = arg; + thread->start_routine = start_routine; + thread->handle = (HANDLE) + _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL); + + if (!thread->handle) + return errno; + else + return 0; +} + +int win32_pthread_join(pthread_t *thread, void **value_ptr) +{ + DWORD result = WaitForSingleObject(thread->handle, INFINITE); + switch (result) { + case WAIT_OBJECT_0: + if (value_ptr) + *value_ptr = thread->arg; + return 0; + case WAIT_ABANDONED: + return EINVAL; + default: + return err_win_to_posix(GetLastError()); + } +} + +int pthread_cond_init(pthread_cond_t *cond, const void *unused) +{ + cond->waiters = 0; + + cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL); + if (!cond->sema) + die("CreateSemaphore() failed"); + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cond) +{ + CloseHandle(cond->sema); + cond->sema = NULL; + + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex) +{ + InterlockedIncrement(&cond->waiters); + + /* + * Unlock external mutex and wait for signal. + * NOTE: we've held mutex locked long enough to increment + * waiters count above, so there's no problem with + * leaving mutex unlocked before we wait on semaphore. + */ + LeaveCriticalSection(mutex); + + /* let's wait - ignore return value */ + WaitForSingleObject(cond->sema, INFINITE); + + /* we're done waiting, so make sure we decrease waiters count */ + InterlockedDecrement(&cond->waiters); + + /* lock external mutex again */ + EnterCriticalSection(mutex); + + return 0; +} + +int pthread_cond_signal(pthread_cond_t *cond) +{ + /* + * Access to waiters count is atomic; see "Interlocked Variable Access" + * http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx + */ + int have_waiters = cond->waiters > 0; + + /* + * Signal only when there are waiters + */ + if (have_waiters) + return ReleaseSemaphore(cond->sema, 1, NULL) ? + 0 : err_win_to_posix(GetLastError()); + else + return 0; +} diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h new file mode 100644 index 0000000000..b8e1bcb046 --- /dev/null +++ b/compat/win32/pthread.h @@ -0,0 +1,67 @@ +/* + * Header used to adapt pthread-based POSIX code to Windows API threads. + * + * Copyright (C) 2009 Andrzej K. Haczewski + */ + +#ifndef PTHREAD_H +#define PTHREAD_H + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +/* + * Defines that adapt Windows API threads to pthreads API + */ +#define pthread_mutex_t CRITICAL_SECTION + +#define pthread_mutex_init(a,b) InitializeCriticalSection((a)) +#define pthread_mutex_destroy(a) DeleteCriticalSection((a)) +#define pthread_mutex_lock EnterCriticalSection +#define pthread_mutex_unlock LeaveCriticalSection + +/* + * Implement simple condition variable for Windows threads, based on ACE + * implementation. + * + * See original implementation: http://bit.ly/1vkDjo + * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html + * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + */ +typedef struct { + volatile LONG waiters; + HANDLE sema; +} pthread_cond_t; + +extern int pthread_cond_init(pthread_cond_t *cond, const void *unused); + +extern int pthread_cond_destroy(pthread_cond_t *cond); + +extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex); + +extern int pthread_cond_signal(pthread_cond_t *cond); + +/* + * Simple thread creation implementation using pthread API + */ +typedef struct { + HANDLE handle; + void *(*start_routine)(void*); + void *arg; +} pthread_t; + +extern int pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg); + +/* + * To avoid the need of copying a struct, we use small macro wrapper to pass + * pointer to win32_pthread_join instead. + */ +#define pthread_join(a, b) win32_pthread_join(&(a), (b)) + +extern int win32_pthread_join(pthread_t *thread, void **value_ptr); + +#endif /* PTHREAD_H */ diff --git a/config.c b/config.c index f4f2c59a34..6963fbea43 100644 --- a/config.c +++ b/config.c @@ -533,8 +533,7 @@ static int git_default_user_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); strlcpy(git_default_name, value, sizeof(git_default_name)); - if (git_default_email[0]) - user_ident_explicitly_given = 1; + user_ident_explicitly_given |= IDENT_NAME_GIVEN; return 0; } @@ -542,8 +541,7 @@ static int git_default_user_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); strlcpy(git_default_email, value, sizeof(git_default_email)); - if (git_default_name[0]) - user_ident_explicitly_given = 1; + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; return 0; } diff --git a/daemon.c b/daemon.c index 918e560b4e..360635eb1c 100644 --- a/daemon.c +++ b/daemon.c @@ -147,7 +147,6 @@ static char *path_ok(char *directory) { "IP", ip_address }, { "P", tcp_port }, { "D", directory }, - { "%", "%" }, { NULL } }; diff --git a/date.c b/date.c index 5d05ef61cf..45f3684cee 100644 --- a/date.c +++ b/date.c @@ -9,7 +9,7 @@ /* * This is like mktime, but without normalization of tm_wday and tm_yday. */ -time_t tm_to_time_t(const struct tm *tm) +static time_t tm_to_time_t(const struct tm *tm) { static const int mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 diff --git a/dir.c b/dir.c index 3a8d3e67a5..1538ad5da3 100644 --- a/dir.c +++ b/dir.c @@ -655,6 +655,92 @@ static int get_dtype(struct dirent *de, const char *path, int len) return dtype; } +enum path_treatment { + path_ignored, + path_handled, + path_recurse, +}; + +static enum path_treatment treat_one_path(struct dir_struct *dir, + char *path, int *len, + const struct path_simplify *simplify, + int dtype, struct dirent *de) +{ + int exclude = excluded(dir, path, &dtype); + if (exclude && (dir->flags & DIR_COLLECT_IGNORED) + && in_pathspec(path, *len, simplify)) + dir_add_ignored(dir, path, *len); + + /* + * Excluded? If we don't explicitly want to show + * ignored files, ignore it + */ + if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) + return path_ignored; + + if (dtype == DT_UNKNOWN) + dtype = get_dtype(de, path, *len); + + /* + * Do we want to see just the ignored files? + * We still need to recurse into directories, + * even if we don't ignore them, since the + * directory may contain files that we do.. + */ + if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { + if (dtype != DT_DIR) + return path_ignored; + } + + switch (dtype) { + default: + return path_ignored; + case DT_DIR: + memcpy(path + *len, "/", 2); + (*len)++; + switch (treat_directory(dir, path, *len, simplify)) { + case show_directory: + if (exclude != !!(dir->flags + & DIR_SHOW_IGNORED)) + return path_ignored; + break; + case recurse_into_directory: + return path_recurse; + case ignore_directory: + return path_ignored; + } + break; + case DT_REG: + case DT_LNK: + break; + } + return path_handled; +} + +static enum path_treatment treat_path(struct dir_struct *dir, + struct dirent *de, + char *path, int path_max, + int baselen, + const struct path_simplify *simplify, + int *len) +{ + int dtype; + + if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) + return path_ignored; + *len = strlen(de->d_name); + /* Ignore overly long pathnames! */ + if (*len + baselen + 8 > path_max) + return path_ignored; + memcpy(path + baselen, de->d_name, *len + 1); + *len += baselen; + if (simplify_away(path, *len, simplify)) + return path_ignored; + + dtype = DTYPE(de); + return treat_one_path(dir, path, len, simplify, dtype, de); +} + /* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git @@ -664,7 +750,10 @@ static int get_dtype(struct dirent *de, const char *path, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify) +static int read_directory_recursive(struct dir_struct *dir, + const char *base, int baselen, + int check_only, + const struct path_simplify *simplify) { DIR *fdir = opendir(*base ? base : "."); int contents = 0; @@ -675,70 +764,16 @@ static int read_directory_recursive(struct dir_struct *dir, const char *base, in memcpy(path, base, baselen); while ((de = readdir(fdir)) != NULL) { - int len, dtype; - int exclude; - - if (is_dot_or_dotdot(de->d_name) || - !strcmp(de->d_name, ".git")) + int len; + switch (treat_path(dir, de, path, sizeof(path), + baselen, simplify, &len)) { + case path_recurse: + contents += read_directory_recursive + (dir, path, len, 0, simplify); continue; - len = strlen(de->d_name); - /* Ignore overly long pathnames! */ - if (len + baselen + 8 > sizeof(path)) + case path_ignored: continue; - memcpy(path + baselen, de->d_name, len+1); - len = baselen + len; - if (simplify_away(path, len, simplify)) - continue; - - dtype = DTYPE(de); - exclude = excluded(dir, path, &dtype); - if (exclude && (dir->flags & DIR_COLLECT_IGNORED) - && in_pathspec(path, len, simplify)) - dir_add_ignored(dir, path,len); - - /* - * Excluded? If we don't explicitly want to show - * ignored files, ignore it - */ - if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) - continue; - - if (dtype == DT_UNKNOWN) - dtype = get_dtype(de, path, len); - - /* - * Do we want to see just the ignored files? - * We still need to recurse into directories, - * even if we don't ignore them, since the - * directory may contain files that we do.. - */ - if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { - if (dtype != DT_DIR) - continue; - } - - switch (dtype) { - default: - continue; - case DT_DIR: - memcpy(path + len, "/", 2); - len++; - switch (treat_directory(dir, path, len, simplify)) { - case show_directory: - if (exclude != !!(dir->flags - & DIR_SHOW_IGNORED)) - continue; - break; - case recurse_into_directory: - contents += read_directory_recursive(dir, - path, len, 0, simplify); - continue; - case ignore_directory: - continue; - } - break; - case DT_REG: - case DT_LNK: + case path_handled: break; } contents++; @@ -808,6 +843,41 @@ static void free_simplify(struct path_simplify *simplify) free(simplify); } +static int treat_leading_path(struct dir_struct *dir, + const char *path, int len, + const struct path_simplify *simplify) +{ + char pathbuf[PATH_MAX]; + int baselen, blen; + const char *cp; + + while (len && path[len - 1] == '/') + len--; + if (!len) + return 1; + baselen = 0; + while (1) { + cp = path + baselen + !!baselen; + cp = memchr(cp, '/', path + len - cp); + if (!cp) + baselen = len; + else + baselen = cp - path; + memcpy(pathbuf, path, baselen); + pathbuf[baselen] = '\0'; + if (!is_directory(pathbuf)) + return 0; + if (simplify_away(pathbuf, baselen, simplify)) + return 0; + blen = baselen; + if (treat_one_path(dir, pathbuf, &blen, simplify, + DT_DIR, NULL) == path_ignored) + return 0; /* do not recurse into it */ + if (len <= baselen) + return 1; /* finished checking */ + } +} + int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec) { struct path_simplify *simplify; @@ -816,7 +886,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char return dir->nr; simplify = create_simplify(pathspec); - read_directory_recursive(dir, path, len, 0, simplify); + if (!len || treat_leading_path(dir, path, len, simplify)) + read_directory_recursive(dir, path, len, 0, simplify); free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name); diff --git a/entry.c b/entry.c index 9d5b232781..004182c99d 100644 --- a/entry.c +++ b/entry.c @@ -179,7 +179,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout * This is like 'lstat()', except it refuses to follow symlinks * in the path, after skipping "skiplen". */ -int check_path(const char *path, int len, struct stat *st, int skiplen) +static int check_path(const char *path, int len, struct stat *st, int skiplen) { const char *slash = path + len; diff --git a/fast-import.c b/fast-import.c index 52d33bcba8..25c3588385 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2356,6 +2356,7 @@ static void parse_new_tag(void) struct tag *t; uintmax_t from_mark = 0; unsigned char sha1[20]; + enum object_type type; /* Obtain the new tag name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; @@ -2376,19 +2377,18 @@ static void parse_new_tag(void) s = lookup_branch(from); if (s) { hashcpy(sha1, s->sha1); + type = OBJ_COMMIT; } else if (*from == ':') { struct object_entry *oe; from_mark = strtoumax(from + 1, NULL, 10); oe = find_mark(from_mark); - if (oe->type != OBJ_COMMIT) - die("Mark :%" PRIuMAX " not a commit", from_mark); + type = oe->type; hashcpy(sha1, oe->sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; char *buf; - buf = read_object_with_reference(sha1, - commit_type, &size, sha1); + buf = read_sha1_file(sha1, &type, &size); if (!buf || size < 46) die("Not a valid commit: %s", from); free(buf); @@ -2413,7 +2413,7 @@ static void parse_new_tag(void) "object %s\n" "type %s\n" "tag %s\n", - sha1_to_hex(sha1), commit_type, t->name); + sha1_to_hex(sha1), typename(type), t->name); if (tagger) strbuf_addf(&new_data, "tagger %s\n", tagger); diff --git a/git-am.sh b/git-am.sh index 4838cdb9ed..2f46fda47b 100755 --- a/git-am.sh +++ b/git-am.sh @@ -30,6 +30,7 @@ skip skip the current patch abort restore the original branch and abort the patching operation. committer-date-is-author-date lie about committer date ignore-date use current timestamp for author date +rerere-autoupdate update the index with reused conflict resolution if possible rebasing* (internal use for git-rebase)" . git-sh-setup @@ -135,7 +136,7 @@ It does not apply to blobs recorded in its index." export GIT_MERGE_VERBOSITY=0 fi git-merge-recursive $orig_tree -- HEAD $his_tree || { - git rerere + git rerere $allow_rerere_autoupdate echo Failed to merge in the changes. exit 1 } @@ -293,6 +294,7 @@ resolvemsg= resume= scissors= no_inbody_headers= git_apply_opt= committer_date_is_author_date= ignore_date= +allow_rerere_autoupdate= while test $# != 0 do @@ -340,6 +342,8 @@ do committer_date_is_author_date=t ;; --ignore-date) ignore_date=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" ;; -q|--quiet) GIT_QUIET=t ;; --) diff --git a/git-compat-util.h b/git-compat-util.h index 5c596875c2..60c8432f85 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -96,6 +96,7 @@ #include #include #include +#include #ifndef NO_SYS_SELECT_H #include #endif @@ -198,7 +199,6 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern int prefixcmp(const char *str, const char *prefix); -extern time_t tm_to_time_t(const struct tm *tm); static inline const char *skip_prefix(const char *str, const char *prefix) { diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 57e8e3256d..e43b5d64de 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -3,9 +3,8 @@ # This script is typically launched by using the 'git difftool' # convenience command. # -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar -# Load common functions from git-mergetool--lib TOOL_MODE=diff . git-mergetool--lib @@ -20,7 +19,11 @@ should_prompt () { fi } -# Sets up shell variables and runs a merge tool +# Indicates that --extcmd=... was specified +use_ext_cmd () { + test -n "$GIT_DIFFTOOL_EXTCMD" +} + launch_merge_tool () { # Merged is the filename as it appears in the work tree # Local is the contents of a/filename @@ -35,20 +38,28 @@ launch_merge_tool () { # the user with the real $MERGED name before launching $merge_tool. if should_prompt; then printf "\nViewing: '$MERGED'\n" - printf "Hit return to launch '%s': " "$merge_tool" + if use_ext_cmd; then + printf "Hit return to launch '%s': " \ + "$GIT_DIFFTOOL_EXTCMD" + else + printf "Hit return to launch '%s': " "$merge_tool" + fi read ans fi - # Run the appropriate merge tool command - run_merge_tool "$merge_tool" + if use_ext_cmd; then + eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' + else + run_merge_tool "$merge_tool" + fi } -# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values -test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" -test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL" - -if test -z "$merge_tool"; then - merge_tool="$(get_merge_tool)" || exit +if ! use_ext_cmd; then + if test -n "$GIT_DIFF_TOOL"; then + merge_tool="$GIT_DIFF_TOOL" + else + merge_tool="$(get_merge_tool)" || exit + fi fi # Launch the merge tool on each path provided by 'git diff' diff --git a/git-difftool.perl b/git-difftool.perl index ba5e60a45e..d975d072db 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar # # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible # git-difftool--helper script. @@ -15,13 +15,17 @@ use warnings; use Cwd qw(abs_path); use File::Basename qw(dirname); +require Git; + my $DIR = abs_path(dirname($0)); sub usage { print << 'USAGE'; -usage: git difftool [--tool=] [-y|--no-prompt] ["git diff" options] +usage: git difftool [-t|--tool=] [-x|--extcmd=] + [-y|--no-prompt] [-g|--gui] + ['git diff' options] USAGE exit 1; } @@ -63,6 +67,24 @@ sub generate_command $ENV{GIT_DIFF_TOOL} = substr($arg, 7); next; } + if ($arg eq '-x' || $arg eq '--extcmd') { + usage() if $#ARGV <= $idx; + $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1]; + $skip_next = 1; + next; + } + if ($arg =~ /^--extcmd=/) { + $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9); + next; + } + if ($arg eq '-g' || $arg eq '--gui') { + my $tool = Git::command_oneline('config', + 'diff.guitool'); + if (length($tool)) { + $ENV{GIT_DIFF_TOOL} = $tool; + } + next; + } if ($arg eq '-y' || $arg eq '--no-prompt') { $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; delete $ENV{GIT_DIFFTOOL_PROMPT}; diff --git a/git-pull.sh b/git-pull.sh index 9e69ada413..54ce0af2d4 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -13,8 +13,29 @@ set_reflog_action "pull $*" require_work_tree cd_to_toplevel -test -z "$(git ls-files -u)" || - die "You are in the middle of a conflicted merge." + +die_conflict () { + git diff-index --cached --name-status -r --ignore-submodules HEAD -- + if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then + die "Pull is not possible because you have unmerged files. +Please, fix them up in the work tree, and then use 'git add/rm ' +as appropriate to mark resolution, or use 'git commit -a'." + else + die "Pull is not possible because you have unmerged files." + fi +} + +die_merge () { + if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then + die "You have not concluded your merge (MERGE_HEAD exists). +Please, commit your changes before you can merge." + else + die "You have not concluded your merge (MERGE_HEAD exists)." + fi +} + +test -z "$(git ls-files -u)" || die_conflict +test -f "$GIT_DIR/MERGE_HEAD" && die_merge strategy_args= diffstat= no_commit= squash= no_ff= ff_only= log_arg= verbosity= diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 1560e84bd5..e551906ecd 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -28,24 +28,81 @@ abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation root rebase all reachable commmits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i " . git-sh-setup require_work_tree DOTEST="$GIT_DIR/rebase-merge" + +# The file containing rebase commands, comments, and empty lines. +# This file is created by "git rebase -i" then edited by the user. As +# the lines are processed, they are removed from the front of this +# file and written to the tail of $DONE. TODO="$DOTEST"/git-rebase-todo + +# The rebase command lines that have already been processed. A line +# is moved here when it is first handled, before any associated user +# actions. DONE="$DOTEST"/done + +# The commit message that is planned to be used for any changes that +# need to be committed following a user interaction. MSG="$DOTEST"/message + +# The file into which is accumulated the suggested commit message for +# squash/fixup commands. When the first of a series of squash/fixups +# is seen, the file is created and the commit message from the +# previous commit and from the first squash/fixup commit are written +# to it. The commit message for each subsequent squash/fixup commit +# is appended to the file as it is processed. +# +# The first line of the file is of the form +# # This is a combination of $COUNT commits. +# where $COUNT is the number of commits whose messages have been +# written to the file so far (including the initial "pick" commit). +# Each time that a commit message is processed, this line is read and +# updated. It is deleted just before the combined commit is made. SQUASH_MSG="$DOTEST"/message-squash + +# If the current series of squash/fixups has not yet included a squash +# command, then this file exists and holds the commit message of the +# original "pick" commit. (If the series ends without a "squash" +# command, then this can be used as the commit message of the combined +# commit without opening the editor.) +FIXUP_MSG="$DOTEST"/message-fixup + +# $REWRITTEN is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $HEAD and +# $UPSTREAM. They are not necessarily rewritten, but their children +# might be. This ensures that commits on merged, but otherwise +# unrelated side branches are left alone. (Think "X" in the man page's +# example.) REWRITTEN="$DOTEST"/rewritten + DROPPED="$DOTEST"/dropped + +# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE that will be used for the commit that is currently +# being rebased. +AUTHOR_SCRIPT="$DOTEST"/author-script + +# When an "edit" rebase command is being processed, the SHA1 of the +# commit to be edited is recorded in this file. When "git rebase +# --continue" is executed, if there are any staged changes then they +# will be amended to the HEAD commit, but only provided the HEAD +# commit is still the commit to be edited. When any other rebase +# command is processed, this file is deleted. +AMEND="$DOTEST"/amend + PRESERVE_MERGES= STRATEGY= ONTO= VERBOSE= OK_TO_SKIP_PRE_REBASE= REBASE_ROOT= +AUTOSQUASH= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add ', and @@ -70,6 +127,11 @@ output () { esac } +# Output the commit message for the specified commit. +commit_message () { + git cat-file commit "$1" | sed "1,/^$/d" +} + run_pre_rebase_hook () { if test -z "$OK_TO_SKIP_PRE_REBASE" && test -x "$GIT_DIR/hooks/pre-rebase" @@ -129,10 +191,10 @@ make_patch () { echo "Root commit" ;; esac > "$DOTEST"/patch - test -f "$DOTEST"/message || - git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message - test -f "$DOTEST"/author-script || - get_author_ident_from_commit "$1" > "$DOTEST"/author-script + test -f "$MSG" || + commit_message "$1" > "$MSG" + test -f "$AUTHOR_SCRIPT" || + get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" } die_with_patch () { @@ -150,13 +212,22 @@ has_action () { sane_grep '^[^#]' "$1" >/dev/null } +# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE exported from the current environment. +do_with_author () { + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + "$@" +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - if test ! -z "$REBASE_ROOT" + if test -n "$REBASE_ROOT" then output git cherry-pick "$@" return @@ -164,11 +235,10 @@ pick_one () { parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) - if test "$no_ff$current_sha1" = "$parent_sha1"; then + if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" + then output git reset --hard $sha1 - test "a$1" = a-n && output git reset --soft $current_sha1 - sha1=$(git rev-parse --short $sha1) - output warn Fast-forward to $sha1 + output warn Fast-forward to $(git rev-parse --short $sha1) else output git cherry-pick "$@" fi @@ -269,14 +339,11 @@ pick_one_preserving_merges () { # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" + msg="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} - if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - output git merge $STRATEGY -m "$msg" \ - $new_parents + if ! do_with_author output \ + git merge $STRATEGY -m "$msg" $new_parents then printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" @@ -300,34 +367,66 @@ nth_string () { esac } -make_squash_message () { +update_squash_messages () { if test -f "$SQUASH_MSG"; then - COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \ - < "$SQUASH_MSG" | sed -ne '$p')+1)) - echo "# This is a combination of $COUNT commits." - sed -e 1d -e '2,/^./{ - /^$/d - }' <"$SQUASH_MSG" + mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit + COUNT=$(($(sed -n \ + -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ + -e "q" < "$SQUASH_MSG".bak)+1)) + { + echo "# This is a combination of $COUNT commits." + sed -e 1d -e '2,/^./{ + /^$/d + }' <"$SQUASH_MSG".bak + } >$SQUASH_MSG else + commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG" COUNT=2 - echo "# This is a combination of two commits." - echo "# The first commit's message is:" - echo - git cat-file commit HEAD | sed -e '1,/^$/d' + { + echo "# This is a combination of 2 commits." + echo "# The first commit's message is:" + echo + cat "$FIXUP_MSG" + } >$SQUASH_MSG fi - echo - echo "# This is the $(nth_string $COUNT) commit message:" - echo - git cat-file commit $1 | sed -e '1,/^$/d' + case $1 in + squash) + rm -f "$FIXUP_MSG" + echo + echo "# This is the $(nth_string $COUNT) commit message:" + echo + commit_message $2 + ;; + fixup) + echo + echo "# The $(nth_string $COUNT) commit message will be skipped:" + echo + commit_message $2 | sed -e 's/^/# /' + ;; + esac >>$SQUASH_MSG } peek_next_command () { sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO" } +# A squash/fixup has failed. Prepare the long version of the squash +# commit message, then die_with_patch. This code path requires the +# user to edit the combined commit message for all commits that have +# been squashed/fixedup so far. So also erase the old squash +# messages, effectively causing the combined commit to be used as the +# new basis for any further squash/fixups. Args: sha1 rest +die_failed_squash() { + mv "$SQUASH_MSG" "$MSG" || exit + rm -f "$FIXUP_MSG" + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $1... $2" + die_with_patch $1 "" +} + do_next () { - rm -f "$DOTEST"/message "$DOTEST"/author-script \ - "$DOTEST"/amend || exit + rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit read command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) @@ -355,7 +454,7 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 - git rev-parse --verify HEAD > "$DOTEST"/amend + git rev-parse --verify HEAD > "$AMEND" warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn @@ -367,52 +466,49 @@ do_next () { warn exit 0 ;; - squash|s) - comment_for_reflog squash - - test -f "$DONE" && has_action "$DONE" || - die "Cannot 'squash' without a previous commit" - - mark_action_done - make_squash_message $sha1 > "$MSG" - failed=f - author_script=$(get_author_ident_from_commit HEAD) - output git reset --soft HEAD^ - pick_one -n $sha1 || failed=t - case "$(peek_next_command)" in + squash|s|fixup|f) + case "$command" in squash|s) - USE_OUTPUT=output - MSG_OPT=-F - EDIT_OR_FILE="$MSG" - cp "$MSG" "$SQUASH_MSG" + squash_style=squash ;; - *) - USE_OUTPUT= - MSG_OPT= - EDIT_OR_FILE=-e - rm -f "$SQUASH_MSG" || exit - cp "$MSG" "$GIT_DIR"/SQUASH_MSG - rm -f "$GIT_DIR"/MERGE_MSG || exit + fixup|f) + squash_style=fixup + ;; + esac + comment_for_reflog $squash_style + + test -f "$DONE" && has_action "$DONE" || + die "Cannot '$squash_style' without a previous commit" + + mark_action_done + update_squash_messages $squash_style $sha1 + author_script=$(get_author_ident_from_commit HEAD) + echo "$author_script" > "$AUTHOR_SCRIPT" + eval "$author_script" + output git reset --soft HEAD^ + pick_one -n $sha1 || die_failed_squash $sha1 "$rest" + case "$(peek_next_command)" in + squash|s|fixup|f) + # This is an intermediate commit; its message will only be + # used in case of trouble. So use the long version: + do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + die_failed_squash $sha1 "$rest" + ;; + *) + # This is the final command of this squash/fixup group + if test -f "$FIXUP_MSG" + then + do_with_author git commit --no-verify -F "$FIXUP_MSG" || + die_failed_squash $sha1 "$rest" + else + cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --no-verify -e || + die_failed_squash $sha1 "$rest" + fi + rm -f "$SQUASH_MSG" "$FIXUP_MSG" ;; esac - echo "$author_script" > "$DOTEST"/author-script - if test $failed = f - then - # This is like --amend, but with a different message - eval "$author_script" - GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - $USE_OUTPUT git commit --no-verify \ - $MSG_OPT "$EDIT_OR_FILE" || failed=t - fi - if test $failed = t - then - cp "$MSG" "$GIT_DIR"/MERGE_MSG - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi ;; *) warn "Unknown command: $command $sha1 $rest" @@ -495,6 +591,37 @@ get_saved_options () { test -f "$DOTEST"/rebase-root && REBASE_ROOT=t } +# Rearrange the todo list that has both "pick sha1 msg" and +# "pick sha1 fixup!/squash! msg" appears in it so that the latter +# comes immediately after the former, and change "pick" to +# "fixup"/"squash". +rearrange_squash () { + sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \ + -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \ + "$1" >"$1.sq" + test -s "$1.sq" || return + + used= + while read pick sha1 message + do + case " $used" in + *" $sha1 "*) continue ;; + esac + echo "$pick $sha1 $message" + while read squash action msg + do + case "$message" in + "$msg"*) + echo "$action $squash $action! $msg" + used="$used$squash " + ;; + esac + done <"$1.sq" + done >"$1.rearranged" <"$1" + cat "$1.rearranged" >"$1" + rm -f "$1.sq" "$1.rearranged" +} + LF=' ' parse_onto () { @@ -541,21 +668,20 @@ do then : Nothing to commit -- skip this else - . "$DOTEST"/author-script || + . "$AUTHOR_SCRIPT" || die "Cannot find the author identity" amend= - if test -f "$DOTEST"/amend + if test -f "$AMEND" then amend=$(git rev-parse --verify HEAD) - test "$amend" = $(cat "$DOTEST"/amend) || + test "$amend" = $(cat "$AMEND") || die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." git reset --soft HEAD^ || die "Cannot rewind the HEAD" fi - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$DOTEST"/message -e || { + do_with_author git commit --no-verify -F "$MSG" -e || { test -n "$amend" && git reset --soft $amend die "Could not commit staged changes." } @@ -619,6 +745,9 @@ first and then run 'git rebase --continue' again." --root) REBASE_ROOT=t ;; + --autosquash) + AUTOSQUASH=t + ;; --onto) shift ONTO=$(parse_onto "$1") || @@ -679,13 +808,6 @@ first and then run 'git rebase --continue' again." test t = "$VERBOSE" && : > "$DOTEST"/verbose if test t = "$PRESERVE_MERGES" then - # $REWRITTEN contains files for each commit that is - # reachable by at least one merge base of $HEAD and - # $UPSTREAM. They are not necessarily rewritten, but - # their children might be. - # This ensures that commits on merged, but otherwise - # unrelated side branches are left alone. (Think "X" - # in the man page's example.) if test -z "$REBASE_ROOT" then mkdir "$REWRITTEN" && @@ -778,6 +900,7 @@ first and then run 'git rebase --continue' again." fi test -s "$TODO" || echo noop >> "$TODO" + test -n "$AUTOSQUASH" && rearrange_squash "$TODO" cat >> "$TODO" << EOF # Rebase $SHORTREVISIONS onto $SHORTONTO @@ -787,6 +910,7 @@ first and then run 'git rebase --continue' again." # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit +# f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. diff --git a/git-rebase.sh b/git-rebase.sh index 3de0942c0f..eddc02875f 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -52,6 +52,7 @@ diffstat=$(git config --bool rebase.stat) git_am_opt= rebase_root= force_rebase= +allow_rerere_autoupdate= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -120,7 +121,7 @@ call_merge () { return ;; 1) - git rerere + git rerere $allow_rerere_autoupdate die "$RESOLVEMSG" ;; 2) @@ -351,6 +352,9 @@ do -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase) force_rebase=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" + ;; -*) usage ;; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index dfcb8078f5..d56426dd39 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -120,20 +120,11 @@ is_bare_repository () { } cd_to_toplevel () { - cdup=$(git rev-parse --show-cdup) - if test ! -z "$cdup" - then - # The "-P" option says to follow "physical" directory - # structure instead of following symbolic links. When cdup is - # "../", this means following the ".." entry in the current - # directory instead textually removing a symlink path element - # from the PWD shell variable. The "-P" behavior is more - # consistent with the C-style chdir used by most of Git. - cd -P "$cdup" || { - echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" - exit 1 - } - fi + cdup=$(git rev-parse --show-toplevel) && + cd "$cdup" || { + echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" + exit 1 + } } require_work_tree () { diff --git a/git.c b/git.c index 11544cdb40..ad074735fc 100644 --- a/git.c +++ b/git.c @@ -317,7 +317,7 @@ static void handle_internal_command(int argc, const char **argv) { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, + { "grep", cmd_grep, USE_PAGER }, { "help", cmd_help }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, diff --git a/grep.c b/grep.c index bdadf2c0cc..8e1f7de771 100644 --- a/grep.c +++ b/grep.c @@ -29,13 +29,6 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, p->next = NULL; } -static int is_fixed(const char *s) -{ - while (*s && !is_regex_special(*s)) - s++; - return !*s; -} - static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { int err; @@ -43,7 +36,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) p->word_regexp = opt->word_regexp; p->ignore_case = opt->ignore_case; - if (opt->fixed || is_fixed(p->pattern)) + if (opt->fixed) p->fixed = 1; if (opt->regflags & REG_ICASE) p->fixed = 0; @@ -615,6 +608,65 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf, } } +static int should_lookahead(struct grep_opt *opt) +{ + struct grep_pat *p; + + if (opt->extended) + return 0; /* punt for too complex stuff */ + if (opt->invert) + return 0; + for (p = opt->pattern_list; p; p = p->next) { + if (p->token != GREP_PATTERN) + return 0; /* punt for "header only" and stuff */ + } + return 1; +} + +static int look_ahead(struct grep_opt *opt, + unsigned long *left_p, + unsigned *lno_p, + char **bol_p) +{ + unsigned lno = *lno_p; + char *bol = *bol_p; + struct grep_pat *p; + char *sp, *last_bol; + regoff_t earliest = -1; + + for (p = opt->pattern_list; p; p = p->next) { + int hit; + regmatch_t m; + + if (p->fixed) + hit = !fixmatch(p->pattern, bol, p->ignore_case, &m); + else + hit = !regexec(&p->regexp, bol, 1, &m, 0); + if (!hit || m.rm_so < 0 || m.rm_eo < 0) + continue; + if (earliest < 0 || m.rm_so < earliest) + earliest = m.rm_so; + } + + if (earliest < 0) { + *bol_p = bol + *left_p; + *left_p = 0; + return 1; + } + for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--) + ; /* find the beginning of the line */ + last_bol = sp; + + for (sp = bol; sp < last_bol; sp++) { + if (*sp == '\n') + lno++; + } + *left_p -= last_bol - bol; + *bol_p = last_bol; + *lno_p = lno; + return 0; +} + static int grep_buffer_1(struct grep_opt *opt, const char *name, char *buf, unsigned long size, int collect_hits) { @@ -624,6 +676,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, unsigned last_hit = 0; int binary_match_only = 0; unsigned count = 0; + int try_lookahead = 0; enum grep_context ctx = GREP_CONTEXT_HEAD; xdemitconf_t xecfg; @@ -652,11 +705,26 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, opt->priv = &xecfg; } } + try_lookahead = should_lookahead(opt); while (left) { char *eol, ch; int hit; + /* + * look_ahead() skips quicly to the line that possibly + * has the next hit; don't call it if we need to do + * something more than just skipping the current line + * in response to an unmatch for the current line. E.g. + * inside a post-context window, we will show the current + * line as a context around the previous hit when it + * doesn't hit. + */ + if (try_lookahead + && !(last_hit + && lno <= last_hit + opt->post_context) + && look_ahead(opt, &left, &lno, &bol)) + break; eol = end_of_line(bol, &left); ch = *eol; *eol = 0; diff --git a/grep.h b/grep.h index 75370f60d7..0c61b00cbf 100644 --- a/grep.h +++ b/grep.h @@ -85,7 +85,6 @@ struct grep_opt { int max_depth; int funcname; char color_match[COLOR_MAXLEN]; - const char *color_external; int regflags; unsigned pre_context; unsigned post_context; diff --git a/http.c b/http.c index 5c3efb96d9..deab59551d 100644 --- a/http.c +++ b/http.c @@ -651,7 +651,7 @@ static void closedown_active_slot(struct active_request_slot *slot) slot->in_use = 0; } -void release_active_slot(struct active_request_slot *slot) +static void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); if (slot->curl && curl_session_count > min_curl_sessions) { @@ -834,7 +834,13 @@ int http_get_strbuf(const char *url, struct strbuf *result, int options) return http_request(url, result, HTTP_REQUEST_STRBUF, options); } -int http_get_file(const char *url, const char *filename, int options) +/* + * Downloads an url and stores the result in the given file. + * + * If a previous interrupted download is detected (i.e. a previous temporary + * file is still around) the download is resumed. + */ +static int http_get_file(const char *url, const char *filename, int options) { int ret; struct strbuf tmpfile = STRBUF_INIT; diff --git a/http.h b/http.h index f828e1d806..5c9441c10c 100644 --- a/http.h +++ b/http.h @@ -81,7 +81,6 @@ extern int start_active_slot(struct active_request_slot *slot); extern void run_active_slot(struct active_request_slot *slot); extern void finish_active_slot(struct active_request_slot *slot); extern void finish_all_active_slots(void); -extern void release_active_slot(struct active_request_slot *slot); #ifdef USE_CURL_MULTI extern void fill_active_slots(void); @@ -135,14 +134,6 @@ extern char *get_remote_object_url(const char *url, const char *hex, */ int http_get_strbuf(const char *url, struct strbuf *result, int options); -/* - * Downloads an url and stores the result in the given file. - * - * If a previous interrupted download is detected (i.e. a previous temporary - * file is still around) the download is resumed. - */ -int http_get_file(const char *url, const char *filename, int options); - /* * Prints an error message using error() containing url and curl_errorstr, * and returns ret. diff --git a/ident.c b/ident.c index 26409b2a1b..9e2438826d 100644 --- a/ident.c +++ b/ident.c @@ -85,10 +85,11 @@ static void setup_ident(void) if (!git_default_email[0]) { const char *email = getenv("EMAIL"); - if (email && email[0]) + if (email && email[0]) { strlcpy(git_default_email, email, sizeof(git_default_email)); - else { + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + } else { if (!pw) pw = getpwuid(getuid()); if (!pw) @@ -168,8 +169,6 @@ static int copy(char *buf, size_t size, int offset, const char *src) return offset; } -static const char au_env[] = "GIT_AUTHOR_NAME"; -static const char co_env[] = "GIT_COMMITTER_NAME"; static const char *env_hint = "\n" "*** Please tell me who you are.\n" @@ -204,7 +203,7 @@ const char *fmt_ident(const char *name, const char *email, if ((warn_on_no_name || error_on_no_name) && name == git_default_name && env_hint) { - fprintf(stderr, env_hint, au_env, co_env); + fputs(env_hint, stderr); env_hint = NULL; /* warn only once */ } if (error_on_no_name) @@ -251,11 +250,21 @@ const char *git_author_info(int flag) const char *git_committer_info(int flag) { - if (getenv("GIT_COMMITTER_NAME") && - getenv("GIT_COMMITTER_EMAIL")) - user_ident_explicitly_given = 1; + if (getenv("GIT_COMMITTER_NAME")) + user_ident_explicitly_given |= IDENT_NAME_GIVEN; + if (getenv("GIT_COMMITTER_EMAIL")) + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"), flag); } + +int user_ident_sufficiently_given(void) +{ +#ifndef WINDOWS + return (user_ident_explicitly_given & IDENT_MAIL_GIVEN); +#else + return (user_ident_explicitly_given == IDENT_ALL_GIVEN); +#endif +} diff --git a/mailmap.c b/mailmap.c index f167c005bf..b68c1fec9c 100644 --- a/mailmap.c +++ b/mailmap.c @@ -243,8 +243,3 @@ int map_user(struct string_list *map, debug_mm("map_user: --\n"); return 0; } - -int map_email(struct string_list *map, const char *email, char *name, int maxlen) -{ - return map_user(map, (char *)email, 0, name, maxlen); -} diff --git a/mailmap.h b/mailmap.h index 4b2ca3a7de..d5c3664322 100644 --- a/mailmap.h +++ b/mailmap.h @@ -4,7 +4,6 @@ int read_mailmap(struct string_list *map, char **repo_abbrev); void clear_mailmap(struct string_list *map); -int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen); int map_user(struct string_list *mailmap, char *email, int maxlen_email, char *name, int maxlen_name); diff --git a/object.c b/object.c index fe8eaaf19f..3ca92c4c4d 100644 --- a/object.c +++ b/object.c @@ -217,27 +217,6 @@ struct object_list *object_list_insert(struct object *item, return new_list; } -void object_list_append(struct object *item, - struct object_list **list_p) -{ - while (*list_p) { - list_p = &((*list_p)->next); - } - *list_p = xmalloc(sizeof(struct object_list)); - (*list_p)->next = NULL; - (*list_p)->item = item; -} - -unsigned object_list_length(struct object_list *list) -{ - unsigned ret = 0; - while (list) { - list = list->next; - ret++; - } - return ret; -} - int object_list_contains(struct object_list *list, struct object *obj) { while (list) { diff --git a/object.h b/object.h index 89dd0c47a6..82877c831c 100644 --- a/object.h +++ b/object.h @@ -72,11 +72,6 @@ struct object *lookup_unknown_object(const unsigned char *sha1); struct object_list *object_list_insert(struct object *item, struct object_list **list_p); -void object_list_append(struct object *item, - struct object_list **list_p); - -unsigned object_list_length(struct object_list *list); - int object_list_contains(struct object_list *list, struct object *obj); /* Object array handling .. */ diff --git a/parse-options.c b/parse-options.c index f5594114ed..d218122af5 100644 --- a/parse-options.c +++ b/parse-options.c @@ -3,6 +3,9 @@ #include "cache.h" #include "commit.h" +static int parse_options_usage(const char * const *usagestr, + const struct option *opts); + #define OPT_SHORT 1 #define OPT_UNSET 2 @@ -560,8 +563,8 @@ void usage_msg_opt(const char *msg, usage_with_options(usagestr, options); } -int parse_options_usage(const char * const *usagestr, - const struct option *opts) +static int parse_options_usage(const char * const *usagestr, + const struct option *opts) { return usage_with_options_internal(usagestr, opts, 0); } @@ -633,3 +636,10 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) commit_list_insert(commit, opt->value); return 0; } + +int parse_opt_tertiary(const struct option *opt, const char *arg, int unset) +{ + int *target = opt->value; + *target = unset ? 2 : 1; + return 0; +} diff --git a/parse-options.h b/parse-options.h index f295a2cf85..0c996916b6 100644 --- a/parse-options.h +++ b/parse-options.h @@ -123,6 +123,8 @@ struct option { (h), PARSE_OPT_NOARG, NULL, (p) } #define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } +#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, &parse_opt_tertiary } #define OPT_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \ parse_opt_approxidate_cb } @@ -171,9 +173,6 @@ struct parse_opt_ctx_t { const char *prefix; }; -extern int parse_options_usage(const char * const *usagestr, - const struct option *opts); - extern void parse_options_start(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, int flags); @@ -190,6 +189,7 @@ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); +extern int parse_opt_tertiary(const struct option *, const char *, int); #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") diff --git a/pretty.c b/pretty.c index 8f5bd1ab7f..9001379a9d 100644 --- a/pretty.c +++ b/pretty.c @@ -83,7 +83,7 @@ static int get_one_line(const char *msg) } /* High bit set, or ISO-2022-INT */ -int non_ascii(int ch) +static int non_ascii(int ch) { return !isascii(ch) || ch == '\033'; } diff --git a/quote.c b/quote.c index 848d174cc5..acb6bf929f 100644 --- a/quote.c +++ b/quote.c @@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) } } -char *sq_dequote_step(char *arg, char **next) +static char *sq_dequote_step(char *arg, char **next) { char *dst = arg; char *src = arg; diff --git a/quote.h b/quote.h index 66730f2bff..f83eb233c4 100644 --- a/quote.h +++ b/quote.h @@ -45,7 +45,6 @@ extern char *sq_dequote(char *); * next argument that should be passed as first parameter. When there * is no more argument to be dequoted, "next" is updated to point to NULL. */ -extern char *sq_dequote_step(char *arg, char **next); extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); diff --git a/read-cache.c b/read-cache.c index d214abab16..edd995943d 100644 --- a/read-cache.c +++ b/read-cache.c @@ -14,6 +14,9 @@ #include "diffcore.h" #include "revision.h" #include "blob.h" +#include "resolve-undo.h" + +static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); /* Index extensions. * @@ -26,6 +29,7 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ +#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUN" */ struct index_state the_index; @@ -156,7 +160,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st) return 0; } -int is_empty_blob_sha1(const unsigned char *sha1) +static int is_empty_blob_sha1(const unsigned char *sha1) { static const unsigned char empty_blob_sha1[20] = { 0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b, @@ -454,6 +458,7 @@ int remove_index_entry_at(struct index_state *istate, int pos) { struct cache_entry *ce = istate->cache[pos]; + record_resolve_undo(istate, ce); remove_name_hash(ce); istate->cache_changed = 1; istate->cache_nr--; @@ -1152,7 +1157,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p return has_errors; } -struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) +static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) { return refresh_cache_ent(&the_index, ce, really, NULL); } @@ -1181,6 +1186,9 @@ static int read_index_extension(struct index_state *istate, case CACHE_EXT_TREE: istate->cache_tree = cache_tree_read(data, sz); break; + case CACHE_EXT_RESOLVE_UNDO: + istate->resolve_undo = resolve_undo_read(data, sz); + break; default: if (*ext < 'A' || 'Z' < *ext) return error("index uses %.4s extension, which we do not understand", @@ -1360,6 +1368,7 @@ int is_index_unborn(struct index_state *istate) int discard_index(struct index_state *istate) { + resolve_undo_clear_index(istate); istate->cache_nr = 0; istate->cache_changed = 0; istate->timestamp.sec = 0; @@ -1585,6 +1594,17 @@ int write_index(struct index_state *istate, int newfd) if (err) return -1; } + if (istate->resolve_undo) { + struct strbuf sb = STRBUF_INIT; + + resolve_undo_write(&sb, istate->resolve_undo); + err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO, + sb.len) < 0 + || ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + if (err) + return -1; + } if (ce_flush(&c, newfd) || fstat(newfd, &st)) return -1; diff --git a/remote-curl.c b/remote-curl.c index 70fdceade5..1361006959 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -317,7 +317,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, } #ifndef NO_CURL_IOCTL -curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) +static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) { struct rpc_state *rpc = clientp; diff --git a/remote.c b/remote.c index e3afecdb10..c70181cdc6 100644 --- a/remote.c +++ b/remote.c @@ -1247,6 +1247,56 @@ int match_refs(struct ref *src, struct ref **dst, return 0; } +void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, + int force_update) +{ + struct ref *ref; + + for (ref = remote_refs; ref; ref = ref->next) { + if (ref->peer_ref) + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + else if (!send_mirror) + continue; + + ref->deletion = is_null_sha1(ref->new_sha1); + if (!ref->deletion && + !hashcmp(ref->old_sha1, ref->new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + /* This part determines what can overwrite what. + * The rules are: + * + * (0) you can always use --force or +A:B notation to + * selectively force individual ref pairs. + * + * (1) if the old thing does not exist, it is OK. + * + * (2) if you do not have the old thing, you are not allowed + * to overwrite it; you would not know what you are losing + * otherwise. + * + * (3) if both new and old are commit-ish, and new is a + * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. + */ + + ref->nonfastforward = + !ref->deletion && + !is_null_sha1(ref->old_sha1) && + (!has_sha1_file(ref->old_sha1) + || !ref_newer(ref->new_sha1, ref->old_sha1)); + + if (ref->nonfastforward && !ref->force && !force_update) { + ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + continue; + } + } +} + struct branch *branch_get(const char *name) { struct branch *ret; diff --git a/remote.h b/remote.h index 8b7ecf9197..6e13643cab 100644 --- a/remote.h +++ b/remote.h @@ -98,6 +98,8 @@ char *apply_refspecs(struct refspec *refspecs, int nr_refspec, int match_refs(struct ref *src, struct ref **dst, int nr_refspec, const char **refspec, int all); +void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, + int force_update); /* * Given a list of the remote refs and the specification of things to diff --git a/rerere.c b/rerere.c index 29f95f657d..70d0f7afff 100644 --- a/rerere.c +++ b/rerere.c @@ -3,6 +3,9 @@ #include "rerere.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" +#include "dir.h" +#include "resolve-undo.h" +#include "ll-merge.h" /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ static int rerere_enabled = -1; @@ -83,61 +86,74 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err) ferr_write(s, strlen(s), fp, err); } -static int handle_file(const char *path, - unsigned char *sha1, const char *output) +struct rerere_io { + int (*getline)(struct strbuf *, struct rerere_io *); + FILE *output; + int wrerror; + /* some more stuff */ +}; + +static void rerere_io_putstr(const char *str, struct rerere_io *io) +{ + if (io->output) + ferr_puts(str, io->output, &io->wrerror); +} + +static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io) +{ + if (io->output) + ferr_write(mem, sz, io->output, &io->wrerror); +} + +struct rerere_io_file { + struct rerere_io io; + FILE *input; +}; + +static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_file *io = (struct rerere_io_file *)io_; + return strbuf_getwholeline(sb, io->input, '\n'); +} + +static int handle_path(unsigned char *sha1, struct rerere_io *io) { git_SHA_CTX ctx; - char buf[1024]; int hunk_no = 0; enum { RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL, } hunk = RR_CONTEXT; struct strbuf one = STRBUF_INIT, two = STRBUF_INIT; - FILE *f = fopen(path, "r"); - FILE *out = NULL; - int wrerror = 0; - - if (!f) - return error("Could not open %s", path); - - if (output) { - out = fopen(output, "w"); - if (!out) { - fclose(f); - return error("Could not write %s", output); - } - } + struct strbuf buf = STRBUF_INIT; if (sha1) git_SHA1_Init(&ctx); - while (fgets(buf, sizeof(buf), f)) { - if (!prefixcmp(buf, "<<<<<<< ")) { + while (!io->getline(&buf, io)) { + if (!prefixcmp(buf.buf, "<<<<<<< ")) { if (hunk != RR_CONTEXT) goto bad; hunk = RR_SIDE_1; - } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) { + } else if (!prefixcmp(buf.buf, "|||||||") && isspace(buf.buf[7])) { if (hunk != RR_SIDE_1) goto bad; hunk = RR_ORIGINAL; - } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + } else if (!prefixcmp(buf.buf, "=======") && isspace(buf.buf[7])) { if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL) goto bad; hunk = RR_SIDE_2; - } else if (!prefixcmp(buf, ">>>>>>> ")) { + } else if (!prefixcmp(buf.buf, ">>>>>>> ")) { if (hunk != RR_SIDE_2) goto bad; if (strbuf_cmp(&one, &two) > 0) strbuf_swap(&one, &two); hunk_no++; hunk = RR_CONTEXT; - if (out) { - ferr_puts("<<<<<<<\n", out, &wrerror); - ferr_write(one.buf, one.len, out, &wrerror); - ferr_puts("=======\n", out, &wrerror); - ferr_write(two.buf, two.len, out, &wrerror); - ferr_puts(">>>>>>>\n", out, &wrerror); - } + rerere_io_putstr("<<<<<<<\n", io); + rerere_io_putmem(one.buf, one.len, io); + rerere_io_putstr("=======\n", io); + rerere_io_putmem(two.buf, two.len, io); + rerere_io_putstr(">>>>>>>\n", io); if (sha1) { git_SHA1_Update(&ctx, one.buf ? one.buf : "", one.len + 1); @@ -147,13 +163,13 @@ static int handle_file(const char *path, strbuf_reset(&one); strbuf_reset(&two); } else if (hunk == RR_SIDE_1) - strbuf_addstr(&one, buf); + strbuf_addstr(&one, buf.buf); else if (hunk == RR_ORIGINAL) ; /* discard */ else if (hunk == RR_SIDE_2) - strbuf_addstr(&two, buf); - else if (out) - ferr_puts(buf, out, &wrerror); + strbuf_addstr(&two, buf.buf); + else + rerere_io_putstr(buf.buf, io); continue; bad: hunk = 99; /* force error exit */ @@ -161,26 +177,136 @@ static int handle_file(const char *path, } strbuf_release(&one); strbuf_release(&two); + strbuf_release(&buf); - fclose(f); - if (wrerror) - error("There were errors while writing %s (%s)", - path, strerror(wrerror)); - if (out && fclose(out)) - wrerror = error("Failed to flush %s: %s", - path, strerror(errno)); if (sha1) git_SHA1_Final(sha1, &ctx); - if (hunk != RR_CONTEXT) { + if (hunk != RR_CONTEXT) + return -1; + return hunk_no; +} + +static int handle_file(const char *path, unsigned char *sha1, const char *output) +{ + int hunk_no = 0; + struct rerere_io_file io; + + memset(&io, 0, sizeof(io)); + io.io.getline = rerere_file_getline; + io.input = fopen(path, "r"); + io.io.wrerror = 0; + if (!io.input) + return error("Could not open %s", path); + + if (output) { + io.io.output = fopen(output, "w"); + if (!io.io.output) { + fclose(io.input); + return error("Could not write %s", output); + } + } + + hunk_no = handle_path(sha1, (struct rerere_io *)&io); + + fclose(io.input); + if (io.io.wrerror) + error("There were errors while writing %s (%s)", + path, strerror(io.io.wrerror)); + if (io.io.output && fclose(io.io.output)) + io.io.wrerror = error("Failed to flush %s: %s", + path, strerror(errno)); + + if (hunk_no < 0) { if (output) unlink_or_warn(output); return error("Could not parse conflict hunks in %s", path); } - if (wrerror) + if (io.io.wrerror) return -1; return hunk_no; } +struct rerere_io_mem { + struct rerere_io io; + struct strbuf input; +}; + +static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_mem *io = (struct rerere_io_mem *)io_; + char *ep; + size_t len; + + strbuf_release(sb); + if (!io->input.len) + return -1; + ep = strchrnul(io->input.buf, '\n'); + if (*ep == '\n') + ep++; + len = ep - io->input.buf; + strbuf_add(sb, io->input.buf, len); + strbuf_remove(&io->input, 0, len); + return 0; +} + +static int handle_cache(const char *path, unsigned char *sha1, const char *output) +{ + mmfile_t mmfile[3]; + mmbuffer_t result = {NULL, 0}; + struct cache_entry *ce; + int pos, len, i, hunk_no; + struct rerere_io_mem io; + + /* + * Reproduce the conflicted merge in-core + */ + len = strlen(path); + pos = cache_name_pos(path, len); + if (0 <= pos) + return -1; + pos = -pos - 1; + + for (i = 0; i < 3; i++) { + enum object_type type; + unsigned long size; + + mmfile[i].size = 0; + mmfile[i].ptr = NULL; + if (active_nr <= pos) + break; + ce = active_cache[pos++]; + if (ce_namelen(ce) != len || memcmp(ce->name, path, len) + || ce_stage(ce) != i + 1) + break; + mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].size = size; + } + for (i = 0; i < 3; i++) { + if (!mmfile[i].ptr && !mmfile[i].size) + mmfile[i].ptr = xstrdup(""); + } + ll_merge(&result, path, &mmfile[0], + &mmfile[1], "ours", + &mmfile[2], "theirs", 0); + for (i = 0; i < 3; i++) + free(mmfile[i].ptr); + + memset(&io, 0, sizeof(&io)); + io.io.getline = rerere_mem_getline; + if (output) + io.io.output = fopen(output, "w"); + else + io.io.output = NULL; + strbuf_init(&io.input, 0); + strbuf_attach(&io.input, result.ptr, result.size, result.size); + + hunk_no = handle_path(sha1, (struct rerere_io *)&io); + strbuf_release(&io.input); + if (io.io.output) + fclose(io.io.output); + return hunk_no; +} + static int find_conflict(struct string_list *conflict) { int i; @@ -367,7 +493,7 @@ static int is_rerere_enabled(void) return 1; } -int setup_rerere(struct string_list *merge_rr) +int setup_rerere(struct string_list *merge_rr, int flags) { int fd; @@ -375,6 +501,8 @@ int setup_rerere(struct string_list *merge_rr) if (!is_rerere_enabled()) return -1; + if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE)) + rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE); merge_rr_path = git_pathdup("MERGE_RR"); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, LOCK_DIE_ON_ERROR); @@ -382,13 +510,62 @@ int setup_rerere(struct string_list *merge_rr) return fd; } -int rerere(void) +int rerere(int flags) { struct string_list merge_rr = { NULL, 0, 0, 1 }; int fd; - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; return do_plain_rerere(&merge_rr, fd); } + +static int rerere_forget_one_path(const char *path, struct string_list *rr) +{ + const char *filename; + char *hex; + unsigned char sha1[20]; + int ret; + + ret = handle_cache(path, sha1, NULL); + if (ret < 1) + return error("Could not parse conflict hunks in '%s'", path); + hex = xstrdup(sha1_to_hex(sha1)); + filename = rerere_path(hex, "postimage"); + if (unlink(filename)) + return (errno == ENOENT + ? error("no remembered resolution for %s", path) + : error("cannot unlink %s: %s", filename, strerror(errno))); + + handle_cache(path, sha1, rerere_path(hex, "preimage")); + fprintf(stderr, "Updated preimage for '%s'\n", path); + + + string_list_insert(path, rr)->util = hex; + fprintf(stderr, "Forgot resolution for %s\n", path); + return 0; +} + +int rerere_forget(const char **pathspec) +{ + int i, fd; + struct string_list conflict = { NULL, 0, 0, 1 }; + struct string_list merge_rr = { NULL, 0, 0, 1 }; + + if (read_cache() < 0) + return error("Could not read index"); + + fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE); + + unmerge_cache(pathspec); + find_conflict(&conflict); + for (i = 0; i < conflict.nr; i++) { + struct string_list_item *it = &conflict.items[i]; + if (!match_pathspec(pathspec, it->string, strlen(it->string), + 0, NULL)) + continue; + rerere_forget_one_path(it->string, &merge_rr); + } + return write_rr(&merge_rr, fd); +} diff --git a/rerere.h b/rerere.h index 13313f3f2b..eaa9004dcd 100644 --- a/rerere.h +++ b/rerere.h @@ -3,9 +3,16 @@ #include "string-list.h" -extern int setup_rerere(struct string_list *); -extern int rerere(void); +#define RERERE_AUTOUPDATE 01 +#define RERERE_NOAUTOUPDATE 02 + +extern int setup_rerere(struct string_list *, int); +extern int rerere(int); extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); +extern int rerere_forget(const char **); + +#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \ + "update the index with reused conflict resolution if possible") #endif diff --git a/resolve-undo.c b/resolve-undo.c new file mode 100644 index 0000000000..37d73cd949 --- /dev/null +++ b/resolve-undo.c @@ -0,0 +1,176 @@ +#include "cache.h" +#include "dir.h" +#include "resolve-undo.h" +#include "string-list.h" + +/* The only error case is to run out of memory in string-list */ +void record_resolve_undo(struct index_state *istate, struct cache_entry *ce) +{ + struct string_list_item *lost; + struct resolve_undo_info *ui; + struct string_list *resolve_undo; + int stage = ce_stage(ce); + + if (!stage) + return; + + if (!istate->resolve_undo) { + resolve_undo = xcalloc(1, sizeof(*resolve_undo)); + resolve_undo->strdup_strings = 1; + istate->resolve_undo = resolve_undo; + } + resolve_undo = istate->resolve_undo; + lost = string_list_insert(ce->name, resolve_undo); + if (!lost->util) + lost->util = xcalloc(1, sizeof(*ui)); + ui = lost->util; + hashcpy(ui->sha1[stage - 1], ce->sha1); + ui->mode[stage - 1] = ce->ce_mode; +} + +static int write_one(struct string_list_item *item, void *cbdata) +{ + struct strbuf *sb = cbdata; + struct resolve_undo_info *ui = item->util; + int i; + + if (!ui) + return 0; + strbuf_addstr(sb, item->string); + strbuf_addch(sb, 0); + for (i = 0; i < 3; i++) + strbuf_addf(sb, "%o%c", ui->mode[i], 0); + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + strbuf_add(sb, ui->sha1[i], 20); + } + return 0; +} + +void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) +{ + for_each_string_list(write_one, resolve_undo, sb); +} + +struct string_list *resolve_undo_read(void *data, unsigned long size) +{ + struct string_list *resolve_undo; + size_t len; + char *endptr; + int i; + + resolve_undo = xcalloc(1, sizeof(*resolve_undo)); + resolve_undo->strdup_strings = 1; + + while (size) { + struct string_list_item *lost; + struct resolve_undo_info *ui; + + len = strlen(data) + 1; + if (size <= len) + goto error; + lost = string_list_insert(data, resolve_undo); + if (!lost->util) + lost->util = xcalloc(1, sizeof(*ui)); + ui = lost->util; + size -= len; + data += len; + + for (i = 0; i < 3; i++) { + ui->mode[i] = strtoul(data, &endptr, 8); + if (!endptr || endptr == data || *endptr) + goto error; + len = (endptr + 1) - (char*)data; + if (size <= len) + goto error; + size -= len; + data += len; + } + + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + if (size < 20) + goto error; + hashcpy(ui->sha1[i], data); + size -= 20; + data += 20; + } + } + return resolve_undo; + +error: + string_list_clear(resolve_undo, 1); + error("Index records invalid resolve-undo information"); + return NULL; +} + +void resolve_undo_clear_index(struct index_state *istate) +{ + struct string_list *resolve_undo = istate->resolve_undo; + if (!resolve_undo) + return; + string_list_clear(resolve_undo, 1); + free(resolve_undo); + istate->resolve_undo = NULL; + istate->cache_changed = 1; +} + +int unmerge_index_entry_at(struct index_state *istate, int pos) +{ + struct cache_entry *ce; + struct string_list_item *item; + struct resolve_undo_info *ru; + int i, err = 0; + + if (!istate->resolve_undo) + return pos; + + ce = istate->cache[pos]; + if (ce_stage(ce)) { + /* already unmerged */ + while ((pos < istate->cache_nr) && + ! strcmp(istate->cache[pos]->name, ce->name)) + pos++; + return pos - 1; /* return the last entry processed */ + } + item = string_list_lookup(ce->name, istate->resolve_undo); + if (!item) + return pos; + ru = item->util; + if (!ru) + return pos; + remove_index_entry_at(istate, pos); + for (i = 0; i < 3; i++) { + struct cache_entry *nce; + if (!ru->mode[i]) + continue; + nce = make_cache_entry(ru->mode[i], ru->sha1[i], + ce->name, i + 1, 0); + if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) { + err = 1; + error("cannot unmerge '%s'", ce->name); + } + } + if (err) + return pos; + free(ru); + item->util = NULL; + return unmerge_index_entry_at(istate, pos); +} + +void unmerge_index(struct index_state *istate, const char **pathspec) +{ + int i; + + if (!istate->resolve_undo) + return; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) + continue; + i = unmerge_index_entry_at(istate, i); + } +} diff --git a/resolve-undo.h b/resolve-undo.h new file mode 100644 index 0000000000..e4e5c1b1ad --- /dev/null +++ b/resolve-undo.h @@ -0,0 +1,16 @@ +#ifndef RESOLVE_UNDO_H +#define RESOLVE_UNDO_H + +struct resolve_undo_info { + unsigned int mode[3]; + unsigned char sha1[3][20]; +}; + +extern void record_resolve_undo(struct index_state *, struct cache_entry *); +extern void resolve_undo_write(struct strbuf *, struct string_list *); +extern struct string_list *resolve_undo_read(void *, unsigned long); +extern void resolve_undo_clear_index(struct index_state *); +extern int unmerge_index_entry_at(struct index_state *, int); +extern void unmerge_index(struct index_state *, const char **); + +#endif diff --git a/run-command.c b/run-command.c index 47ced570bd..2feb493951 100644 --- a/run-command.c +++ b/run-command.c @@ -8,12 +8,14 @@ static inline void close_pair(int fd[2]) close(fd[1]); } +#ifndef WIN32 static inline void dup_devnull(int to) { int fd = open("/dev/null", O_RDWR); dup2(fd, to); close(fd); } +#endif static const char **prepare_shell_cmd(const char **argv) { @@ -59,6 +61,78 @@ static int execv_shell_cmd(const char **argv) } #endif +#ifndef WIN32 +static int child_err = 2; +static int child_notifier = -1; + +static void notify_parent(void) +{ + write(child_notifier, "", 1); +} + +static NORETURN void die_child(const char *err, va_list params) +{ + char msg[4096]; + int len = vsnprintf(msg, sizeof(msg), err, params); + if (len > sizeof(msg)) + len = sizeof(msg); + + write(child_err, "fatal: ", 7); + write(child_err, msg, len); + write(child_err, "\n", 1); + exit(128); +} + +static inline void set_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags >= 0) + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} +#endif + +static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) +{ + int status, code = -1; + pid_t waiting; + int failed_errno = 0; + + while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) + ; /* nothing */ + + if (waiting < 0) { + failed_errno = errno; + error("waitpid for %s failed: %s", argv0, strerror(errno)); + } else if (waiting != pid) { + error("waitpid is confused (%s)", argv0); + } else if (WIFSIGNALED(status)) { + code = WTERMSIG(status); + error("%s died of signal %d", argv0, code); + /* + * This return value is chosen so that code & 0xff + * mimics the exit code that a POSIX shell would report for + * a program that died from this signal. + */ + code -= 128; + } else if (WIFEXITED(status)) { + code = WEXITSTATUS(status); + /* + * Convert special exit code when execvp failed. + */ + if (code == 127) { + code = -1; + failed_errno = ENOENT; + if (!silent_exec_failure) + error("cannot run %s: %s", argv0, + strerror(ENOENT)); + } + } else { + error("waitpid is confused (%s)", argv0); + } + errno = failed_errno; + return code; +} + int start_command(struct child_process *cmd) { int need_in, need_out, need_err; @@ -120,9 +194,30 @@ fail_pipe: trace_argv_printf(cmd->argv, "trace: run_command:"); #ifndef WIN32 +{ + int notify_pipe[2]; + if (pipe(notify_pipe)) + notify_pipe[0] = notify_pipe[1] = -1; + fflush(NULL); cmd->pid = fork(); if (!cmd->pid) { + /* + * Redirect the channel to write syscall error messages to + * before redirecting the process's stderr so that all die() + * in subsequent call paths use the parent's stderr. + */ + if (cmd->no_stderr || need_err) { + child_err = dup(2); + set_cloexec(child_err); + } + set_die_routine(die_child); + + close(notify_pipe[0]); + set_cloexec(notify_pipe[1]); + child_notifier = notify_pipe[1]; + atexit(notify_parent); + if (cmd->no_stdin) dup_devnull(0); else if (need_in) { @@ -163,8 +258,16 @@ fail_pipe: unsetenv(*cmd->env); } } - if (cmd->preexec_cb) + if (cmd->preexec_cb) { + /* + * We cannot predict what the pre-exec callback does. + * Forgo parent notification. + */ + close(child_notifier); + child_notifier = -1; + cmd->preexec_cb(); + } if (cmd->git_cmd) { execv_git_cmd(cmd->argv); } else if (cmd->use_shell) { @@ -172,51 +275,65 @@ fail_pipe: } else { execvp(cmd->argv[0], (char *const*) cmd->argv); } - trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0], - strerror(errno)); - exit(127); + /* + * Do not check for cmd->silent_exec_failure; the parent + * process will check it when it sees this exit code. + */ + if (errno == ENOENT) + exit(127); + else + die_errno("cannot exec '%s'", cmd->argv[0]); } if (cmd->pid < 0) error("cannot fork() for %s: %s", cmd->argv[0], strerror(failed_errno = errno)); + + /* + * Wait for child's execvp. If the execvp succeeds (or if fork() + * failed), EOF is seen immediately by the parent. Otherwise, the + * child process sends a single byte. + * Note that use of this infrastructure is completely advisory, + * therefore, we keep error checks minimal. + */ + close(notify_pipe[1]); + if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) { + /* + * At this point we know that fork() succeeded, but execvp() + * failed. Errors have been reported to our stderr. + */ + wait_or_whine(cmd->pid, cmd->argv[0], + cmd->silent_exec_failure); + failed_errno = errno; + cmd->pid = -1; + } + close(notify_pipe[0]); +} #else { - int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ + int fhin = 0, fhout = 1, fherr = 2; const char **sargv = cmd->argv; char **env = environ; - if (cmd->no_stdin) { - s0 = dup(0); - dup_devnull(0); - } else if (need_in) { - s0 = dup(0); - dup2(fdin[0], 0); - } else if (cmd->in) { - s0 = dup(0); - dup2(cmd->in, 0); - } + if (cmd->no_stdin) + fhin = open("/dev/null", O_RDWR); + else if (need_in) + fhin = dup(fdin[0]); + else if (cmd->in) + fhin = dup(cmd->in); - if (cmd->no_stderr) { - s2 = dup(2); - dup_devnull(2); - } else if (need_err) { - s2 = dup(2); - dup2(fderr[1], 2); - } + if (cmd->no_stderr) + fherr = open("/dev/null", O_RDWR); + else if (need_err) + fherr = dup(fderr[1]); - if (cmd->no_stdout) { - s1 = dup(1); - dup_devnull(1); - } else if (cmd->stdout_to_stderr) { - s1 = dup(1); - dup2(2, 1); - } else if (need_out) { - s1 = dup(1); - dup2(fdout[1], 1); - } else if (cmd->out > 1) { - s1 = dup(1); - dup2(cmd->out, 1); - } + if (cmd->no_stdout) + fhout = open("/dev/null", O_RDWR); + else if (cmd->stdout_to_stderr) + fhout = dup(fherr); + else if (need_out) + fhout = dup(fdout[1]); + else if (cmd->out > 1) + fhout = dup(cmd->out); if (cmd->dir) die("chdir in start_command() not implemented"); @@ -229,7 +346,8 @@ fail_pipe: cmd->argv = prepare_shell_cmd(cmd->argv); } - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, + fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); @@ -240,12 +358,12 @@ fail_pipe: free(cmd->argv); cmd->argv = sargv; - if (s0 >= 0) - dup2(s0, 0), close(s0); - if (s1 >= 0) - dup2(s1, 1), close(s1); - if (s2 >= 0) - dup2(s2, 2), close(s2); + if (fhin != 0) + close(fhin); + if (fhout != 1) + close(fhout); + if (fherr != 2) + close(fherr); } #endif @@ -280,48 +398,6 @@ fail_pipe: return 0; } -static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) -{ - int status, code = -1; - pid_t waiting; - int failed_errno = 0; - - while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) - ; /* nothing */ - - if (waiting < 0) { - failed_errno = errno; - error("waitpid for %s failed: %s", argv0, strerror(errno)); - } else if (waiting != pid) { - error("waitpid is confused (%s)", argv0); - } else if (WIFSIGNALED(status)) { - code = WTERMSIG(status); - error("%s died of signal %d", argv0, code); - /* - * This return value is chosen so that code & 0xff - * mimics the exit code that a POSIX shell would report for - * a program that died from this signal. - */ - code -= 128; - } else if (WIFEXITED(status)) { - code = WEXITSTATUS(status); - /* - * Convert special exit code when execvp failed. - */ - if (code == 127) { - code = -1; - failed_errno = ENOENT; - if (!silent_exec_failure) - error("cannot run %s: %s", argv0, - strerror(ENOENT)); - } - } else { - error("waitpid is confused (%s)", argv0); - } - errno = failed_errno; - return code; -} - int finish_command(struct child_process *cmd) { return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure); diff --git a/setup.c b/setup.c index 3a07aa4df7..710e2f3008 100644 --- a/setup.c +++ b/setup.c @@ -263,6 +263,8 @@ static int check_repository_format_gently(int *nongit_ok) const char *read_gitfile_gently(const char *path) { char *buf; + char *dir; + const char *slash; struct stat st; int fd; size_t len; @@ -287,9 +289,23 @@ const char *read_gitfile_gently(const char *path) if (len < 9) die("No path in gitfile: %s", path); buf[len] = '\0'; - if (!is_git_directory(buf + 8)) - die("Not a git repository: %s", buf + 8); - path = make_absolute_path(buf + 8); + dir = buf + 8; + + if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) { + size_t pathlen = slash+1 - path; + size_t dirlen = pathlen + len - 8; + dir = xmalloc(dirlen + 1); + strncpy(dir, path, pathlen); + strncpy(dir + pathlen, buf + 8, len - 8); + dir[dirlen] = '\0'; + free(buf); + buf = dir; + } + + if (!is_git_directory(dir)) + die("Not a git repository: %s", dir); + path = make_absolute_path(dir); + free(buf); return path; } diff --git a/sha1_file.c b/sha1_file.c index 63981fb3fd..7086760dbe 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2458,14 +2458,6 @@ int has_pack_index(const unsigned char *sha1) return 1; } -int has_pack_file(const unsigned char *sha1) -{ - struct stat st; - if (stat(sha1_pack_name(sha1), &st)) - return 0; - return 1; -} - int has_sha1_pack(const unsigned char *sha1) { struct pack_entry e; diff --git a/strbuf.c b/strbuf.c index a6153dca27..67448b7b07 100644 --- a/strbuf.c +++ b/strbuf.c @@ -91,13 +91,6 @@ void strbuf_ltrim(struct strbuf *sb) sb->buf[sb->len] = '\0'; } -void strbuf_tolower(struct strbuf *sb) -{ - int i; - for (i = 0; i < sb->len; i++) - sb->buf[i] = tolower(sb->buf[i]); -} - struct strbuf **strbuf_split(const struct strbuf *sb, int delim) { int alloc = 2, pos = 0; @@ -227,6 +220,12 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, break; format = percent + 1; + if (*format == '%') { + strbuf_addch(sb, '%'); + format++; + continue; + } + consumed = fn(sb, format, context); if (consumed) format += consumed; @@ -251,6 +250,17 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, return 0; } +void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src) +{ + int i, len = src->len; + + for (i = 0; i < len; i++) { + if (src->buf[i] == '%') + strbuf_addch(dst, '%'); + strbuf_addch(dst, src->buf[i]); + } +} + size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f) { size_t res; diff --git a/strbuf.h b/strbuf.h index fa07ecf094..fac2dbc24f 100644 --- a/strbuf.h +++ b/strbuf.h @@ -81,7 +81,6 @@ extern void strbuf_trim(struct strbuf *); extern void strbuf_rtrim(struct strbuf *); extern void strbuf_ltrim(struct strbuf *); extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); -extern void strbuf_tolower(struct strbuf *); extern struct strbuf **strbuf_split(const struct strbuf *, int delim); extern void strbuf_list_free(struct strbuf **); @@ -105,6 +104,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) { strbuf_add(sb, s, strlen(s)); } static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) { + strbuf_grow(sb, sb2->len); strbuf_add(sb, sb2->buf, sb2->len); } extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); @@ -116,6 +116,7 @@ struct strbuf_expand_dict_entry { const char *value; }; extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); +extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); __attribute__((format (printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); diff --git a/submodule.c b/submodule.c index 86aad653b7..3007f7d5a6 100644 --- a/submodule.c +++ b/submodule.c @@ -5,7 +5,7 @@ #include "commit.h" #include "revision.h" -int add_submodule_odb(const char *path) +static int add_submodule_odb(const char *path) { struct strbuf objects_directory = STRBUF_INIT; struct alternate_object_database *alt_odb; diff --git a/symlinks.c b/symlinks.c index 7b0a86d357..8860120011 100644 --- a/symlinks.c +++ b/symlinks.c @@ -179,37 +179,6 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, return ret_flags; } -/* - * Invalidate the given 'name' from the cache, if 'name' matches - * completely with the cache. - */ -void invalidate_lstat_cache(const char *name, int len) -{ - int match_len, previous_slash; - struct cache_def *cache = &default_cache; /* FIXME */ - - match_len = longest_path_match(name, len, cache->path, cache->len, - &previous_slash); - if (len == match_len) { - if ((cache->track_flags & FL_DIR) && previous_slash > 0) { - cache->path[previous_slash] = '\0'; - cache->len = previous_slash; - cache->flags = FL_DIR; - } else { - reset_lstat_cache(cache); - } - } -} - -/* - * Completely clear the contents of the cache - */ -void clear_lstat_cache(void) -{ - struct cache_def *cache = &default_cache; /* FIXME */ - reset_lstat_cache(cache); -} - #define USE_ONLY_LSTAT 0 /* diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 62f452c8ea..2d922ae43c 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -2,21 +2,33 @@ # After setting the fake editor with this function, you can # -# - override the commit message with $FAKE_COMMIT_MESSAGE, +# - override the commit message with $FAKE_COMMIT_MESSAGE # - amend the commit message with $FAKE_COMMIT_AMEND # - check that non-commit messages have a certain line count with $EXPECT_COUNT -# - rewrite a rebase -i script with $FAKE_LINES in the form +# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT +# - rewrite a rebase -i script as directed by $FAKE_LINES. +# $FAKE_LINES consists of a sequence of words separated by spaces. +# The following word combinations are possible: # -# "[] []..." +# "" -- add a "pick" line with the SHA1 taken from the +# specified line. # -# If a line number is prefixed with "squash", "edit", or "reword", the -# respective line's command will be replaced with the specified one. +# " " -- add a line with the specified command +# ("squash", "fixup", "edit", or "reword") and the SHA1 taken +# from the specified line. +# +# "#" -- Add a comment line. +# +# ">" -- Add a blank line. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh cat >> fake-editor.sh <<\EOF case "$1" in */COMMIT_EDITMSG) + test -z "$EXPECT_HEADER_COUNT" || + test "$EXPECT_HEADER_COUNT" = $(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1") || + exit test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" exit @@ -28,19 +40,24 @@ test -z "$EXPECT_COUNT" || test -z "$FAKE_LINES" && exit grep -v '^#' < "$1" > "$1".tmp rm -f "$1" +echo 'rebase -i script before editing:' cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash|edit|reword) + squash|fixup|edit|reword) action="$line";; + "#") + echo '# comment' >> "$1";; + ">") + echo >> "$1";; *) - echo sed -n "${line}s/^pick/$action/p" - sed -n "${line}p" < "$1".tmp sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" action=pick;; esac done +echo 'rebase -i script after editing:' +cat "$1" EOF test_set_editor "$(pwd)/fake-editor.sh" diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh new file mode 100755 index 0000000000..10b26e4d8e --- /dev/null +++ b/t/t0061-run-command.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Copyright (c) 2009 Ilari Liusvaara +# + +test_description='Test run command' + +. ./test-lib.sh + +test_expect_success 'start_command reports ENOENT' ' + test-run-command start-command-ENOENT ./does-not-exist +' + +test_done diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh new file mode 100755 index 0000000000..a38bd6df84 --- /dev/null +++ b/t/t2030-unresolve-info.sh @@ -0,0 +1,143 @@ +#!/bin/sh + +test_description='undoing resolution' + +. ./test-lib.sh + +check_resolve_undo () { + msg=$1 + shift + while case $# in + 0) break ;; + 1|2|3) die "Bug in check-resolve-undo test" ;; + esac + do + path=$1 + shift + for stage in 1 2 3 + do + sha1=$1 + shift + case "$sha1" in + '') continue ;; + esac + sha1=$(git rev-parse --verify "$sha1") + printf "100644 %s %s\t%s\n" $sha1 $stage $path + done + done >"$msg.expect" && + git ls-files --resolve-undo >"$msg.actual" && + test_cmp "$msg.expect" "$msg.actual" +} + +prime_resolve_undo () { + git reset --hard && + git checkout second^0 && + test_tick && + test_must_fail git merge third^0 && + echo merge does not leave anything && + check_resolve_undo empty && + echo different >file && + git add file && + echo resolving records && + check_resolve_undo recorded file initial:file second:file third:file +} + +test_expect_success setup ' + test_commit initial file first && + git branch side && + git branch another && + test_commit second file second && + git checkout side && + test_commit third file third && + git checkout another && + test_commit fourth file fourth && + git checkout master +' + +test_expect_success 'add records switch clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept file initial:file second:file third:file && + git checkout second^0 && + echo switching clears && + check_resolve_undo cleared +' + +test_expect_success 'rm records reset clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept file initial:file second:file third:file && + + echo merge clears upfront && + test_must_fail git merge fourth^0 && + check_resolve_undo nuked && + + git rm -f file && + echo resolving records && + check_resolve_undo recorded file initial:file HEAD:file fourth:file && + + git reset --hard && + echo resetting discards && + check_resolve_undo discarded +' + +test_expect_success 'plumbing clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept file initial:file second:file third:file && + + echo plumbing clear && + git update-index --clear-resolve-undo && + check_resolve_undo cleared +' + +test_expect_success 'add records checkout -m undoes' ' + prime_resolve_undo && + git diff HEAD && + git checkout --conflict=merge file && + echo checkout used the record and removed it && + check_resolve_undo removed && + echo the index and the work tree is unmerged again && + git diff >actual && + grep "^++<<<<<<<" actual +' + +test_expect_success 'unmerge with plumbing' ' + prime_resolve_undo && + git update-index --unresolve file && + git ls-files -u >actual && + test $(wc -l actual && + echo "$rerere_id file" >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t2104-update-index-gitfile.sh b/t/t2104-update-index-gitfile.sh new file mode 100755 index 0000000000..641607d89a --- /dev/null +++ b/t/t2104-update-index-gitfile.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Brad King +# + +test_description='git update-index for gitlink to .git file. +' + +. ./test-lib.sh + +test_expect_success 'submodule with absolute .git file' ' + mkdir sub1 && + (cd sub1 && + git init && + REAL="$(pwd)/.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git && + test_commit first) +' + +test_expect_success 'add gitlink to absolute .git file' ' + git update-index --add -- sub1 +' + +test_expect_success 'submodule with relative .git file' ' + mkdir sub2 && + (cd sub2 && + git init && + mv .git .real && + echo "gitdir: .real" >.git && + test_commit first) +' + +test_expect_success 'add gitlink to relative .git file' ' + git update-index --add -- sub2 +' + +test_done diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index 132c4765cb..6d2f2b67ee 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -175,4 +175,43 @@ test_expect_success 'negated exclude matches can override previous ones' ' grep "^a.1" output ' +test_expect_success 'subdirectory ignore (setup)' ' + mkdir -p top/l1/l2 && + ( + cd top && + git init && + echo /.gitignore >.gitignore && + echo l1 >>.gitignore && + echo l2 >l1/.gitignore && + >l1/l2/l1 + ) +' + +test_expect_success 'subdirectory ignore (toplevel)' ' + ( + cd top && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1/l2)' ' + ( + cd top/l1/l2 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1)' ' + ( + cd top/l1 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 9b3fa2bdcd..9929f82021 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -276,11 +276,13 @@ test_expect_success 'fail if the index has unresolved entries' ' test_must_fail git merge "$c5" && test_must_fail git merge "$c5" 2> out && + grep "not possible because you have unmerged files" out && + git add -u && + test_must_fail git merge "$c5" 2> out && grep "You have not concluded your merge" out && rm -f .git/MERGE_HEAD && test_must_fail git merge "$c5" 2> out && - grep "You are in the middle of a conflicted merge" out - + grep "Your local changes to .* would be overwritten by merge." out ' test_expect_success 'merge-recursive remove conflict' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3a37793c0d..4e3513709e 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -14,58 +14,47 @@ that the result still makes sense. set_fake_editor -# set up two branches like this: +# Set up the repository like this: # -# A - B - C - D - E +# one - two - three - four (conflict-branch) +# / +# A - B - C - D - E (master) +# | \ +# | F - G - H (branch1) +# | \ +# \ I (branch2) # \ -# F - G - H -# \ -# I +# J - K - L - M (no-conflict-branch) # -# where B, D and G touch the same file. +# where A, B, D and G all touch file1, and one, two, three, four all +# touch file "conflict". test_expect_success 'setup' ' - : > file1 && - git add file1 && - test_tick && - git commit -m A && - git tag A && - echo 1 > file1 && - test_tick && - git commit -m B file1 && - : > file2 && - git add file2 && - test_tick && - git commit -m C && - echo 2 > file1 && - test_tick && - git commit -m D file1 && - : > file3 && - git add file3 && - test_tick && - git commit -m E && + test_commit A file1 && + test_commit B file1 && + test_commit C file2 && + test_commit D file1 && + test_commit E file3 && git checkout -b branch1 A && - : > file4 && - git add file4 && - test_tick && - git commit -m F && - git tag F && - echo 3 > file1 && - test_tick && - git commit -m G file1 && - : > file5 && - git add file5 && - test_tick && - git commit -m H && + test_commit F file4 && + test_commit G file1 && + test_commit H file5 && git checkout -b branch2 F && - : > file6 && - git add file6 && - test_tick && - git commit -m I && - git tag I + test_commit I file6 + git checkout -b conflict-branch A && + for n in one two three four + do + test_commit $n conflict + done && + git checkout -b no-conflict-branch A && + for n in J K L M + do + test_commit $n file$n + done ' test_expect_success 'no changes are a nop' ' + git checkout branch2 && git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && test $(git rev-parse I) = $(git rev-parse HEAD) @@ -111,19 +100,20 @@ test_expect_success 'exchange two commits' ' cat > expect << EOF diff --git a/file1 b/file1 -index e69de29..00750ed 100644 +index f70f10e..fd79235 100644 --- a/file1 +++ b/file1 -@@ -0,0 +1 @@ -+3 +@@ -1 +1 @@ +-A ++G EOF cat > expect2 << EOF <<<<<<< HEAD -2 +D ======= -3 ->>>>>>> b7ca976... G +G +>>>>>>> 51047de... G EOF test_expect_success 'stop on conflicting pick' ' @@ -161,7 +151,8 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 && + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \ + git rebase -i --onto master HEAD~2 && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) ' @@ -256,30 +247,113 @@ test_expect_success 'verbose flag is heeded, even after --continue' ' test_expect_success 'multi-squash only fires up editor once' ' base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) ' +test_expect_success 'multi-fixup does not fire up editor' ' + git checkout -b multi-fixup E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 0 = $(git show | grep NEVER | wc -l) && + git checkout to-be-rebased && + git branch -D multi-fixup +' + +test_expect_success 'commit message used after conflict' ' + git checkout -b conflict-fixup conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 fixup 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-fixup +' + +test_expect_success 'commit message retained after conflict' ' + git checkout -b conflict-squash conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 squash 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 2 = $(git show | grep TWICE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-squash +' + +cat > expect-squash-fixup << EOF +B + +D + +ONCE +EOF + +test_expect_success 'squash and fixup generate correct log messages' ' + git checkout -b squash-fixup E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && + test_cmp expect-squash-fixup actual-squash-fixup && + git checkout to-be-rebased && + git branch -D squash-fixup +' + +test_expect_success 'squash ignores comments' ' + git checkout -b skip-comments E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D skip-comments +' + +test_expect_success 'squash ignores blank lines' ' + git checkout -b skip-blank-lines E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D skip-blank-lines +' + test_expect_success 'squash works as expected' ' - for n in one two three four - do - echo $n >> file$n && - git add file$n && - git commit -m $n - done && + git checkout -b squash-works no-conflict-branch && one=$(git rev-parse HEAD~3) && - FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && + FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \ + git rebase -i HEAD~3 && test $one = $(git rev-parse HEAD~2) ' test_expect_success 'interrupted squash works as expected' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="1 squash 3 2" && @@ -296,12 +370,7 @@ test_expect_success 'interrupted squash works as expected' ' ' test_expect_success 'interrupted squash works as expected (case 2)' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash2 conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="3 squash 1 2" && diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh new file mode 100755 index 0000000000..b63f4e2d67 --- /dev/null +++ b/t/t3415-rebase-autosquash.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +test_description='auto squash' + +. ./test-lib.sh + +test_expect_success setup ' + echo 0 >file0 && + git add . && + test_tick && + git commit -m "initial commit" && + echo 0 >file1 && + echo 2 >file2 && + git add . && + test_tick && + git commit -m "first commit" && + echo 3 >file3 && + git add . && + test_tick && + git commit -m "second commit" && + git tag base +' + +test_expect_success 'auto fixup' ' + git reset --hard base && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "fixup! first" + + git tag final-fixup && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! first" + + git tag final-squash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! forst" + git tag final-missquash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 4 = $(wc -l extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - grep "Dirty index" errors + grep "Your local changes would be overwritten by " errors ' diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index a6bc028a57..bb402c3780 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -217,7 +217,22 @@ test_expect_success 'rerere.autoupdate' ' git checkout version2 && test_must_fail git merge fifth && test 0 = $(git ls-files -u | wc -l) +' +test_expect_success 'merge --rerere-autoupdate' ' + git config --unset rerere.autoupdate + git reset --hard && + git checkout version2 && + test_must_fail git merge --rerere-autoupdate fifth && + test 0 = $(git ls-files -u | wc -l) +' + +test_expect_success 'merge --no-rerere-autoupdate' ' + git config rerere.autoupdate true + git reset --hard && + git checkout version2 && + test_must_fail git merge --no-rerere-autoupdate fifth && + test 2 = $(git ls-files -u | wc -l) ' test_done diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh new file mode 100755 index 0000000000..00da70763b --- /dev/null +++ b/t/t5523-push-upstream.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='push with --set-upstream' +. ./test-lib.sh + +test_expect_success 'setup bare parent' ' + git init --bare parent && + git remote add upstream parent +' + +test_expect_success 'setup local commit' ' + echo content >file && + git add file && + git commit -m one +' + +check_config() { + (echo $2; echo $3) >expect.$1 + (git config branch.$1.remote + git config branch.$1.merge) >actual.$1 + test_cmp expect.$1 actual.$1 +} + +test_expect_success 'push -u master:master' ' + git push -u upstream master:master && + check_config master upstream refs/heads/master +' + +test_expect_success 'push -u master:other' ' + git push -u upstream master:other && + check_config master upstream refs/heads/other +' + +test_expect_success 'push -u --dry-run master:otherX' ' + git push -u --dry-run upstream master:otherX && + check_config master upstream refs/heads/other +' + +test_expect_success 'push -u master2:master2' ' + git branch master2 && + git push -u upstream master2:master2 && + check_config master2 upstream refs/heads/master2 +' + +test_expect_success 'push -u master2:other2' ' + git push -u upstream master2:other2 && + check_config master2 upstream refs/heads/other2 +' + +test_expect_success 'push -u :master2' ' + git push -u upstream :master2 && + check_config master2 upstream refs/heads/other2 +' + +test_expect_success 'push -u --all' ' + git branch all1 && + git branch all2 && + git push -u --all && + check_config all1 upstream refs/heads/all1 && + check_config all2 upstream refs/heads/all2 +' + +test_expect_success 'push -u HEAD' ' + git checkout -b headbranch && + git push -u upstream HEAD && + check_config headbranch upstream refs/heads/headbranch +' + +test_done diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 2a58d0cc9c..83a8e14c6c 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -88,5 +88,49 @@ test_expect_success 'used receive-pack service' ' test_cmp exp act ' +test_expect_success 'non-fast-forward push fails' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout master && + echo "changed" > path2 && + git commit -a -m path2 --amend && + + HEAD=$(git rev-parse --verify HEAD) && + !(git push -v origin >output 2>&1) && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD != $(git rev-parse --verify HEAD)) +' + +test_expect_success 'non-fast-forward push show ref status' ' + grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output +' + +test_expect_success 'non-fast-forward push shows help message' ' + grep \ +"To prevent you from losing history, non-fast-forward updates were rejected +Merge the remote changes before pushing again. See the '"'non-fast-forward'"' +section of '"'git push --help'"' for details." output +' + +test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' ' + # create a dissimilarly-named remote ref so that git is unable to match the + # two refs (viz. local, remote) unless an explicit refspec is provided. + git push origin master:retsam + + echo "change changed" > path2 && + git commit -a -m path2 --amend && + + # push master too; this ensures there is at least one '"'push'"' command to + # the remote helper and triggers interaction with the helper. + !(git push -v origin +master master:retsam >output 2>&1) && + + grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output && + grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output && + + grep \ +"To prevent you from losing history, non-fast-forward updates were rejected +Merge the remote changes before pushing again. See the '"'non-fast-forward'"' +section of '"'git push --help'"' for details." output +' + stop_httpd test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 571931588e..b0047d3c6b 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -19,6 +19,13 @@ test_cmp expect.$1 output.$1 " } +test_format percent %%h <<'EOF' +commit 131a310eb913d107dd3c09a65d1651175898735d +%h +commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +%h +EOF + test_format hash %H%n%h <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d 131a310eb913d107dd3c09a65d1651175898735d diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 76c5e091b7..7eceb086be 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -8,18 +8,6 @@ test_description='git grep various. . ./test-lib.sh -test_expect_success 'Check for external grep support' ' - case "$(git grep -h 2>&1|grep ext-grep)" in - *"(default)"*) - test_set_prereq EXTGREP - true;; - *"(ignored by this build)"*) - true;; - *) - false;; - esac -' - cat >hello.c < int main(int argc, const char **argv) @@ -314,8 +302,8 @@ test_expect_success 'grep -C1, hunk mark between files' ' test_cmp expected actual ' -test_expect_success 'grep -C1 --no-ext-grep, hunk mark between files' ' - git grep -C1 --no-ext-grep "^[yz]" >actual && +test_expect_success 'grep -C1 hunk mark between files' ' + git grep -C1 "^[yz]" >actual && test_cmp expected actual ' @@ -371,7 +359,7 @@ test_expect_success 'log grep (6)' ' test_expect_success 'grep with CE_VALID file' ' git update-index --assume-unchanged t/t && rm t/t && - test "$(git grep --no-ext-grep test)" = "t/t:test" && + test "$(git grep test)" = "t/t:test" && git update-index --no-assume-unchanged t/t && git checkout t/t ' @@ -438,16 +426,56 @@ test_expect_success 'grep -Fi' ' test_cmp expected actual ' -test_expect_success EXTGREP 'external grep is called' ' - GIT_TRACE=2 git grep foo >/dev/null 2>actual && - grep "trace: grep:.*foo" actual >/dev/null +test_expect_success 'outside of git repository' ' + rm -fr non && + mkdir -p non/git/sub && + echo hello >non/git/file1 && + echo world >non/git/sub/file2 && + echo ".*o*" >non/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >non/expect.full && + echo file2:world >non/expect.sub + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git grep o && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full + cd sub && + test_must_fail git grep o && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) ' -test_expect_success EXTGREP 'no external grep when skip-worktree entries exist' ' - git update-index --skip-worktree file && - GIT_TRACE=2 git grep foo >/dev/null 2>actual && - ! grep "trace: grep:" actual >/dev/null && - git update-index --no-skip-worktree file +test_expect_success 'inside git repository but with --no-index' ' + rm -fr is && + mkdir -p is/git/sub && + echo hello >is/git/file1 && + echo world >is/git/sub/file2 && + echo ".*o*" >is/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >is/expect.full && + : >is/expect.empty && + echo file2:world >is/expect.sub + ( + cd is/git && + git init && + test_must_fail git grep o >../actual.full && + test_cmp ../expect.empty ../actual.full && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full && + cd sub && + test_must_fail git grep o >../../actual.sub && + test_cmp ../../expect.empty ../../actual.sub && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) ' test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index a0cc99ab9f..1a4dc5f893 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -299,6 +299,15 @@ test_expect_success 'ls-files gracefully handles trailing slash' ' ' +test_expect_success 'moving to a commit without submodule does not leave empty dir' ' + rm -rf init && + mkdir init && + git reset --hard && + git checkout initial && + test ! -d init && + git checkout second +' + test_expect_success 'submodule warns' ' git submodule no-such-submodule 2> output.err && diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index a529701241..7940901d47 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -117,7 +117,11 @@ test_expect_success \ test_expect_success \ "overriding author from command line" \ "echo 'gak' >file && \ - git commit -m 'author' --author 'Rubber Duck ' -a" + git commit -m 'author' --author 'Rubber Duck ' -a >output 2>&1" + +test_expect_success \ + "commit --author output mentions author" \ + "grep Rubber.Duck output" test_expect_success PERL \ "interactive add" \ diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index fff6a6d0ea..fad5472257 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar # test_description='git-difftool @@ -15,10 +15,14 @@ if ! test_have_prereq PERL; then test_done fi +LF=' +' + remove_config_vars() { # Unset all config variables used by git-difftool git config --unset diff.tool + git config --unset diff.guitool git config --unset difftool.test-tool.cmd git config --unset difftool.prompt git config --unset merge.tool @@ -31,11 +35,11 @@ restore_test_defaults() # Restores the test defaults used by several tests remove_config_vars unset GIT_DIFF_TOOL - unset GIT_MERGE_TOOL unset GIT_DIFFTOOL_PROMPT unset GIT_DIFFTOOL_NO_PROMPT git config diff.tool test-tool && git config difftool.test-tool.cmd 'cat $LOCAL' + git config difftool.bogus-tool.cmd false } prompt_given() @@ -71,11 +75,22 @@ test_expect_success 'custom commands' ' # Ensures that git-difftool ignores bogus --tool values test_expect_success 'difftool ignores bad --tool values' ' - diff=$(git difftool --no-prompt --tool=bogus-tool branch) + diff=$(git difftool --no-prompt --tool=bad-tool branch) test "$?" = 1 && test "$diff" = "" ' +test_expect_success 'difftool honors --gui' ' + git config merge.tool bogus-tool && + git config diff.tool bogus-tool && + git config diff.guitool test-tool && + + diff=$(git difftool --no-prompt --gui branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + # Specify the diff tool using $GIT_DIFF_TOOL test_expect_success 'GIT_DIFF_TOOL variable' ' git config --unset diff.tool @@ -94,15 +109,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' ' git config diff.tool bogus-tool && git config merge.tool bogus-tool && - GIT_MERGE_TOOL=test-tool && - export GIT_MERGE_TOOL && - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && - unset GIT_MERGE_TOOL && - - GIT_MERGE_TOOL=bogus-tool && GIT_DIFF_TOOL=test-tool && - export GIT_MERGE_TOOL && export GIT_DIFF_TOOL && diff=$(git difftool --no-prompt branch) && @@ -210,7 +217,39 @@ test_expect_success 'difftool..path' ' diff=$(git difftool --tool=tkdiff --no-prompt branch) && git config --unset difftool.tkdiff.path && lines=$(echo "$diff" | grep file | wc -l) && - test "$lines" -eq 1 + test "$lines" -eq 1 && + + restore_test_defaults +' + +test_expect_success 'difftool --extcmd=cat' ' + diff=$(git difftool --no-prompt --extcmd=cat branch) && + test "$diff" = branch"$LF"master +' + +test_expect_success 'difftool --extcmd cat' ' + diff=$(git difftool --no-prompt --extcmd cat branch) && + test "$diff" = branch"$LF"master +' + +test_expect_success 'difftool -x cat' ' + diff=$(git difftool --no-prompt -x cat branch) && + test "$diff" = branch"$LF"master +' + +test_expect_success 'difftool --extcmd echo arg1' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) + test "$diff" = file +' + +test_expect_success 'difftool --extcmd cat arg1' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) + test "$diff" = master +' + +test_expect_success 'difftool --extcmd cat arg2' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) + test "$diff" = branch ' test_done diff --git a/test-run-command.c b/test-run-command.c new file mode 100644 index 0000000000..0612bfa7cd --- /dev/null +++ b/test-run-command.c @@ -0,0 +1,35 @@ +/* + * test-run-command.c: test run command API. + * + * (C) 2009 Ilari Liusvaara + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "git-compat-util.h" +#include "run-command.h" +#include +#include + +int main(int argc, char **argv) +{ + struct child_process proc; + + memset(&proc, 0, sizeof(proc)); + + if (argc < 3) + return 1; + proc.argv = (const char **)argv+2; + + if (!strcmp(argv[1], "start-command-ENOENT")) { + if (start_command(&proc) < 0 && errno == ENOENT) + return 0; + fprintf(stderr, "FAIL %s\n", argv[1]); + return 1; + } + + fprintf(stderr, "check usage\n"); + return 1; +} diff --git a/transport-helper.c b/transport-helper.c index ca8fa92e63..107742891f 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -102,6 +102,7 @@ static struct child_process *get_helper(struct transport *transport) int refspec_nr = 0; int refspec_alloc = 0; int duped; + int code; if (data->helper) return data->helper; @@ -111,13 +112,18 @@ static struct child_process *get_helper(struct transport *transport) helper->out = -1; helper->err = 0; helper->argv = xcalloc(4, sizeof(*helper->argv)); - strbuf_addf(&buf, "remote-%s", data->name); + strbuf_addf(&buf, "git-remote-%s", data->name); helper->argv[0] = strbuf_detach(&buf, NULL); helper->argv[1] = transport->remote->name; helper->argv[2] = remove_ext_force(transport->url); - helper->git_cmd = 1; - if (start_command(helper)) - die("Unable to run helper: git %s", helper->argv[0]); + helper->git_cmd = 0; + helper->silent_exec_failure = 1; + code = start_command(helper); + if (code < 0 && errno == ENOENT) + die("Unable to find remote helper for '%s'", data->name); + else if (code != 0) + exit(code); + data->helper = helper; data->no_disconnect_req = 0; @@ -528,24 +534,27 @@ static int push_refs(struct transport *transport, return transport->push_refs(transport, remote_refs, flags); } - if (!remote_refs) + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); return 0; + } helper = get_helper(transport); if (!data->push) return 1; for (ref = remote_refs; ref; ref = ref->next) { - if (ref->peer_ref) - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!mirror) + if (!ref->peer_ref && !mirror) continue; - ref->deletion = is_null_sha1(ref->new_sha1); - if (!ref->deletion && - !hashcmp(ref->old_sha1, ref->new_sha1)) { - ref->status = REF_STATUS_UPTODATE; + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: continue; + default: + ; /* do nothing */ } if (force_all) @@ -634,6 +643,15 @@ static int push_refs(struct transport *transport, continue; } + if (ref->status != REF_STATUS_NONE) { + /* + * Earlier, the ref was marked not to be pushed, so ignore the ref + * status reported by the remote helper if the latter is 'no match'. + */ + if (status == REF_STATUS_NONE) + continue; + } + ref->status = status; ref->remote_status = msg; } diff --git a/transport.c b/transport.c index c3f156ea04..7714fdb6c6 100644 --- a/transport.c +++ b/transport.c @@ -8,6 +8,7 @@ #include "bundle.h" #include "dir.h" #include "refs.h" +#include "branch.h" /* rsync support */ @@ -135,6 +136,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) } } +static void set_upstreams(struct transport *transport, struct ref *refs, + int pretend) +{ + struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + const char *localname; + const char *tmp; + const char *remotename; + unsigned char sha[20]; + int flag = 0; + /* + * Check suitability for tracking. Must be successful / + * already up-to-date ref create/modify (not delete). + */ + if (ref->status != REF_STATUS_OK && + ref->status != REF_STATUS_UPTODATE) + continue; + if (!ref->peer_ref) + continue; + if (!ref->new_sha1 || is_null_sha1(ref->new_sha1)) + continue; + + /* Follow symbolic refs (mainly for HEAD). */ + localname = ref->peer_ref->name; + remotename = ref->name; + tmp = resolve_ref(localname, sha, 1, &flag); + if (tmp && flag & REF_ISSYMREF && + !prefixcmp(tmp, "refs/heads/")) + localname = tmp; + + /* Both source and destination must be local branches. */ + if (!localname || prefixcmp(localname, "refs/heads/")) + continue; + if (!remotename || prefixcmp(remotename, "refs/heads/")) + continue; + + if (!pretend) + install_branch_config(BRANCH_CONFIG_VERBOSE, + localname + 11, transport->remote->name, + remotename); + else + printf("Would set upstream of '%s' to '%s' of '%s'\n", + localname + 11, remotename + 11, + transport->remote->name); + } +} + static const char *rsync_url(const char *url) { return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url; @@ -974,6 +1022,10 @@ int transport_push(struct transport *transport, verify_remote_names(refspec_nr, refspec); if (transport->push) { + /* Maybe FIXME. But no important transport uses this case. */ + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + die("This transport does not support using --set-upstream"); + return transport->push(transport, refspec_nr, refspec, flags); } else if (transport->push_refs) { struct ref *remote_refs = @@ -983,7 +1035,8 @@ int transport_push(struct transport *transport, int verbose = flags & TRANSPORT_PUSH_VERBOSE; int quiet = flags & TRANSPORT_PUSH_QUIET; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; - int ret; + int pretend = flags & TRANSPORT_PUSH_DRY_RUN; + int ret, err; if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -995,13 +1048,23 @@ int transport_push(struct transport *transport, return -1; } - ret = transport->push_refs(transport, remote_refs, flags); + set_ref_status_for_push(remote_refs, + flags & TRANSPORT_PUSH_MIRROR, + flags & TRANSPORT_PUSH_FORCE); - if (!quiet || push_had_errors(remote_refs)) + ret = transport->push_refs(transport, remote_refs, flags); + err = push_had_errors(remote_refs); + + ret |= err; + + if (!quiet || err) print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + set_upstreams(transport, remote_refs, pretend); + if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) diff --git a/transport.h b/transport.h index 7a242fe3bd..7cea5cc723 100644 --- a/transport.h +++ b/transport.h @@ -91,6 +91,7 @@ struct transport { #define TRANSPORT_PUSH_VERBOSE 16 #define TRANSPORT_PUSH_PORCELAIN 32 #define TRANSPORT_PUSH_QUIET 64 +#define TRANSPORT_PUSH_SET_UPSTREAM 128 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); diff --git a/unpack-trees.c b/unpack-trees.c index acdd311737..0ddbef3e63 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -67,8 +67,16 @@ static void unlink_entry(struct cache_entry *ce) { if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (unlink_or_warn(ce->name)) - return; + if (S_ISGITLINK(ce->ce_mode)) { + if (rmdir(ce->name)) { + warning("unable to rmdir %s: %s", + ce->name, strerror(errno)); + return; + } + } + else + if (unlink_or_warn(ce->name)) + return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } diff --git a/utf8.c b/utf8.c index 7ddff23fa7..ab326ac83e 100644 --- a/utf8.c +++ b/utf8.c @@ -163,7 +163,7 @@ static int git_wcwidth(ucs_char_t ch) * If the string was not a valid UTF-8, *start pointer is set to NULL * and the return value is undefined. */ -ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p) +static ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p) { unsigned char *s = (unsigned char *)*start; ucs_char_t ch; diff --git a/utf8.h b/utf8.h index ae30ae4c6e..c9738d83d9 100644 --- a/utf8.h +++ b/utf8.h @@ -3,7 +3,6 @@ typedef unsigned int ucs_char_t; /* assuming 32bit int */ -ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p); int utf8_width(const char **start, size_t *remainder_p); int utf8_strwidth(const char *string); int is_utf8(const char *text);