From: Junio C Hamano Date: Wed, 3 Apr 2013 16:34:28 +0000 (-0700) Subject: Merge branch 'jk/check-corrupt-objects-carefully' X-Git-Tag: v1.8.3-rc0~125 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/b9c78e97237df7df45549d29755e51b4a0fdc5ea?hp=d3b34622f699ff14646de4ec1b1ab9afb0bcb056 Merge branch 'jk/check-corrupt-objects-carefully' Have the streaming interface and other codepaths more carefully examine for corrupt objects. * jk/check-corrupt-objects-carefully: clone: leave repo in place after checkout errors clone: run check_everything_connected clone: die on errors from unpack_trees add tests for cloning corrupted repositories streaming_write_entry: propagate streaming errors add test for streaming corrupt blobs avoid infinite loop in read_istream_loose read_istream_filtered: propagate read error from upstream check_sha1_signature: check return value from read_istream stream_blob_to_fd: detect errors reading from stream --- diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index b1bfff630f..7e4d5716a6 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -237,6 +237,9 @@ For Python scripts: Writing Documentation: + Most (if not all) of the documentation pages are written in AsciiDoc + and processed into HTML output and manpages. + Every user-visible change should be reflected in the documentation. The same general rule as for code applies -- imitate the existing conventions. A few commented examples follow to provide reference diff --git a/Documentation/RelNotes/1.8.2.1.txt b/Documentation/RelNotes/1.8.2.1.txt index e77f8333ea..2cd562233e 100644 --- a/Documentation/RelNotes/1.8.2.1.txt +++ b/Documentation/RelNotes/1.8.2.1.txt @@ -4,6 +4,49 @@ Git v1.8.2.1 Release Notes Fixes since v1.8.2 ------------------ + * The logic used by "git diff -M --stat" to shorten the names of + files before and after a rename did not work correctly when the + common prefix and suffix between the two filenames overlapped. + + * "git submodule update", when recursed into sub-submodules, did not + acccumulate the prefix paths. + + * "git am $maildir/" applied messages in an unexpected order; sort + filenames read from the maildir/ in a way that is more likely to + sort messages in the order the writing MUA meant to, by sorting + numeric segment in numeric order and non-numeric segment in + alphabetical order. + + * When export-subst is used, "zip" output recorded incorrect + size of the file. + + * Some platforms and users spell UTF-8 differently; retry with the + most official "UTF-8" when the system does not understand the + user-supplied encoding name that are the common alternative + spellings of UTF-8. + + * "git branch" did not bother to check nonsense command line + parameters and issue errors in many cases. + + * "git update-index -h" did not do the usual "-h(elp)" thing. + + * perl/Git.pm::cat_blob slurped everything in core only to write it + out to a file descriptor, which was not a very smart thing to do. + + * The SSL peer verification done by "git imap-send" did not ask for + Server Name Indication (RFC 4366), failing to connect SSL/TLS + sites that serve multiple hostnames on a single IP. + + * "git index-pack" had a buffer-overflow while preparing an + informational message when the translated version of it was too + long. + + * Clarify in the documentation "what" gets pushed to "where" when the + command line to "git push" does not say these explicitly. + + * In "git reflog expire", REACHABLE bit was not cleared from the + correct objects. + * The "--color=" argument to the commands in the diff family was described poorly. @@ -27,3 +70,6 @@ Fixes since v1.8.2 to file scope static, but a few functions in graph.c were used by CGit from sideways bypassing the entry points of the API the in-tree users use. + + * "git merge-tree" had a typo in the logic to detect d/f conflicts, + which caused it to segfault in some cases. diff --git a/Documentation/RelNotes/1.8.3.txt b/Documentation/RelNotes/1.8.3.txt index 54b05db9d0..4231b684d6 100644 --- a/Documentation/RelNotes/1.8.3.txt +++ b/Documentation/RelNotes/1.8.3.txt @@ -37,9 +37,17 @@ UI, Workflows & Features An explicit way to help the end users who connect to the service by issuing custom messages to refuse such an access has been added. - * "git status" suggests users to look into using--untracked=no option + * In addition to the case where the user edits the log message with + the "e)dit" option of "am -i", replace the "Applying: this patch" + message with the final log message contents after applymsg hook + munges it. + + * "git status" suggests users to look into using --untracked=no option when it takes too long. + * "git status" shows a bit more information to "git status" during a + rebase/bisect session. + * "git fetch" learned to fetch a commit at the tip of an unadvertised ref by specifying a raw object name from the command line when the server side supports this feature. @@ -62,9 +70,16 @@ UI, Workflows & Features * The new "--follow-tags" option tells "git push" to push relevant annotated tags when pushing branches out. + * "git mergetool" now feeds files to the "p4merge" backend in the + order that matches the p4 convention, where "theirs" is usually + shown on the left side, which is the opposite from other backend + expects. -Foreign Interface + * "show/log" now honors gpg.program configuration just like other + parts of the code that use GnuPG. + * "git log" that shows the difference between the parent and the + child has been optimized somewhat. Performance, Internal Implementation, etc. @@ -75,6 +90,21 @@ Performance, Internal Implementation, etc. hashtables they use upfront, but still started from a small table repeatedly growing and rehashing. + * The API to walk reflog entries from the latest to older, which was + necessary for operations such as "git checkout -", was cumbersome + to use correctly and also inefficient. + + * Codepaths that inspect log-message-to-be and decide when to add a + new Signed-off-by line in various commands have been consolidated. + + * The pkt-line API, implementation and its callers have been cleaned + up to make them more robust. + + * Cygwin port has a faster-but-lying lstat(2) emulation whose + incorrectness does not matter in practice except for a few + codepaths, and setting permission bits to directories is a codepath + that needs to use a more correct one. + Also contains minor documentation updates and code clean-ups. @@ -86,6 +116,19 @@ Unless otherwise noted, all the fixes since v1.8.2 in the maintenance track are contained in this release (see release notes to them for details). + * "index-pack --fix-thin" used uninitialize value to compute delta + depths of objects it appends to the resulting pack. + (merge 57165db jk/index-pack-correct-depth-fix later to maint). + + * "index-pack --verify-stat" used a few counters outside protection + of mutex, possibly showing incorrect numbers. + (merge 8f82aad nd/index-pack-threaded-fixes later to maint). + + * The code to keep track of what directory names are known to Git on + platforms with case insensitive filesystems can get confused upon a + hash collision between these pathnames and looped forever. + (merge 2092678 kb/name-hash later to maint). + * Annotated tags outside refs/tags/ hierarchy were not advertised correctly to the ls-remote and fetch with recent version of Git. (merge c29c46f jk/fully-peeled-packed-ref later to maint). @@ -121,12 +164,10 @@ details). * In "git reflog expire", REACHABLE bit was not cleared from the correct objects. - (merge e8e92e0 jc/maint-reflog-expire-clean-mark-typofix later to maint). * The logic used by "git diff -M --stat" to shorten the names of files before and after a rename did not work correctly when the common prefix and suffix between the two filenames overlapped. - (merge b174eb4 ap/maint-diff-rename-avoid-overlap later to maint). * The "--match=" option of "git describe", when used with "--all" to allow refs that are not annotated tags to be used as a @@ -136,7 +177,6 @@ details). * Clarify in the documentation "what" gets pushed to "where" when the command line to "git push" does not say these explicitly. - (merge cfe1348 jc/maint-push-refspec-default-doc later to maint). * The "--color=" argument to the commands in the diff family was described poorly. @@ -161,14 +201,12 @@ details). to file scope static, but a few functions in graph.c were used by CGit from sideways bypassing the entry points of the API the in-tree users use. - (merge ac751a0 jk/graph-c-expose-symbols-for-cgit later to maint). * "git update-index -h" did not do the usual "-h(elp)" thing. * "git index-pack" had a buffer-overflow while preparing an informational message when the translated version of it was too long. - (merge 5c3459f nd/index-pack-l10n-buf-overflow later to maint). * 'git commit -m "$msg"' used to add an extra newline even when $msg already ended with one. @@ -177,15 +215,12 @@ details). * The SSL peer verification done by "git imap-send" did not ask for Server Name Indication (RFC 4366), failing to connect SSL/TLS sites that serve multiple hostnames on a single IP. - (merge 698a1ec ob/imap-send-ssl-verify later to maint). * perl/Git.pm::cat_blob slurped everything in core only to write it out to a file descriptor, which was not a very smart thing to do. - (merge 712c6ad jc/perl-cat-blob later to maint). * "git branch" did not bother to check nonsense command line parameters and issue errors in many cases. - (merge 8efb889 nd/branch-error-cases later to maint). * Verification of signed tags were not done correctly when not in C or en/US locale. @@ -195,19 +230,15 @@ details). most official "UTF-8" when the system does not understand the user-supplied encoding name that are the common alternative spellings of UTF-8. - (merge 5c680be jk/utf-8-can-be-spelled-differently later to maint). * When export-subst is used, "zip" output recorded incorrect size of the file. - (merge d3c1472 rs/zip-compresssed-size-with-export-subst later to maint). * "git am $maildir/" applied messages in an unexpected order; sort filenames read from the maildir/ in a way that is more likely to sort messages in the order the writing MUA meant to, by sorting numeric segment in numeric order and non-numeric segment in alphabetical order. - (merge 18505c3 jk/mailsplit-maildir-muttsort later to maint). * "git submodule update", when recursed into sub-submodules, did not acccumulate the prefix paths. - (merge 75bf5e6 we/submodule-update-prefix-output later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index c1f435f6df..f79184c0a8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -555,7 +555,7 @@ core.commentchar:: (default '#'). sequence.editor:: - Text editor used by `git rebase -i` for editing the rebase insn file. + Text editor used by `git rebase -i` for editing the rebase instruction file. The value is meant to be interpreted by the shell when it is used. It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable. When not configured the default commit message editor is used instead. diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 86ef56e7c8..cafdc9642d 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -10,7 +10,9 @@ SYNOPSIS -------- [verse] 'git commit-tree' [(-p )...] < changelog -'git commit-tree' [(-p )...] [(-m )...] [(-F )...] +'git commit-tree' [(-p )...] [-S[]] [(-m )...] + [(-F )...] + DESCRIPTION ----------- @@ -52,6 +54,9 @@ OPTIONS Read the commit log message from the given file. Use `-` to read from the standard input. +-S[]:: + GPG-sign commit. + Commit Information ------------------ diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 24a99ccc99..05f8297368 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -137,6 +137,8 @@ OPTIONS -m :: --message=:: Use the given as the commit message. + If multiple `-m` options are given, their values are + concatenated as separate paragraphs. -t :: --template=:: diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index e0e12e9470..8361e6e4e3 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -72,10 +72,12 @@ with custom merge tool commands and has the same value as `$MERGED`. --symlinks:: --no-symlinks:: 'git difftool''s default behavior is create symlinks to the - working tree when run in `--dir-diff` mode. + working tree when run in `--dir-diff` mode and the right-hand + side of the comparison yields the same content as the file in + the working tree. + - Specifying `--no-symlinks` instructs 'git difftool' to create - copies instead. `--no-symlinks` is the default on Windows. +Specifying `--no-symlinks` instructs 'git difftool' to create copies +instead. `--no-symlinks` is the default on Windows. -x :: --extcmd=:: diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index c852a2677a..42391f2ae7 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -170,6 +170,30 @@ happens: If you tried a merge which resulted in complex conflicts and want to start over, you can recover with `git merge --abort`. +MERGING TAG +----------- + +When merging an annotated (and possibly signed) tag, Git always +creates a merge commit even if a fast-forward merge is possible, and +the commit message template is prepared with the tag message. +Additionally, if the tag is signed, the signature check is reported +as a comment in the message template. See also linkgit:git-tag[1]. + +When you want to just integrate with the work leading to the commit +that happens to be tagged, e.g. synchronizing with an upstream +release point, you may not want to make an unnecessary merge commit. + +In such a case, you can "unwrap" the tag yourself before feeding it +to `git merge`, or pass `--ff-only` when you do not have any work on +your own. e.g. + +--- +git fetch origin +git merge v1.2.3^0 +git merge --ff-only v1.2.3 +--- + + HOW CONFLICTS ARE PRESENTED --------------------------- diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index c975743230..24ab07a3f8 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -218,7 +218,7 @@ $ git merge origin/next ------------------------------------------------ -If you tried a pull which resulted in a complex conflicts and +If you tried a pull which resulted in complex conflicts and would want to start over, you can recover with 'git reset'. diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt index 6a9f66d1d9..5d709d02c3 100644 --- a/Documentation/git-sh-setup.txt +++ b/Documentation/git-sh-setup.txt @@ -82,6 +82,12 @@ get_author_ident_from_commit:: outputs code for use with eval to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit. +create_virtual_base:: + modifies the first file so only lines in common with the + second file remain. If there is insufficient common material, + then the first file is left empty. The result is suitable + as a virtual base input for a 3-way merge. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 7efaa591b8..4307d62bd4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -774,9 +774,12 @@ other If this environment variable is set then 'git fetch' and 'git push' will use this command instead of 'ssh' when they need to connect to a remote system. - The '$GIT_SSH' command will be given exactly two arguments: - the 'username@host' (or just 'host') from the URL and the - shell command to execute on that remote system. + The '$GIT_SSH' command will be given exactly two or + four arguments: the 'username@host' (or just 'host') + from the URL and the shell command to execute on that + remote system, optionally preceded by '-p' (literally) and + the 'port' from the URL when it specifies something other + than the default SSH port. + To pass options to the program that you want to list in GIT_SSH you will need to wrap the program and options into a shell script, diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 0bcbe0ac3c..34a8445828 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -30,7 +30,8 @@ set to `no` at the beginning of them. --no-ff:: Create a merge commit even when the merge resolves as a - fast-forward. + fast-forward. This is the default behaviour when merging an + annotated (and possibly signed) tag. --ff-only:: Refuse to merge and exit with a non-zero status unless the diff --git a/Makefile b/Makefile index 598d6313da..0f931a2030 100644 --- a/Makefile +++ b/Makefile @@ -358,33 +358,39 @@ STRIP ?= strip # Among the variables below, these: # gitexecdir # template_dir -# mandir -# infodir -# htmldir # sysconfdir # can be specified as a relative path some/where/else; # this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. +# Additionally, the following will be treated as relative by "git" if they +# begin with "$(prefix)/": +# mandir +# infodir +# htmldir # This can help installing the suite in a relocatable way. prefix = $(HOME) bindir_relative = bin bindir = $(prefix)/$(bindir_relative) -mandir = share/man -infodir = share/info +mandir = $(prefix)/share/man +infodir = $(prefix)/share/info gitexecdir = libexec/git-core mergetoolsdir = $(gitexecdir)/mergetools sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb localedir = $(sharedir)/locale template_dir = share/git-core/templates -htmldir = share/doc/git-doc +htmldir = $(prefix)/share/doc/git-doc ETC_GITCONFIG = $(sysconfdir)/gitconfig ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes lib = lib # DESTDIR = pathsep = : +mandir_relative = $(patsubst $(prefix)/%,%,$(mandir)) +infodir_relative = $(patsubst $(prefix)/%,%,$(infodir)) +htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir)) + export prefix bindir sharedir sysconfdir gitwebdir localedir CC = cc @@ -1539,12 +1545,12 @@ ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) -mandir_SQ = $(subst ','\'',$(mandir)) -infodir_SQ = $(subst ','\'',$(infodir)) +mandir_relative_SQ = $(subst ','\'',$(mandir_relative)) +infodir_relative_SQ = $(subst ','\'',$(infodir_relative)) localedir_SQ = $(subst ','\'',$(localedir)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) template_dir_SQ = $(subst ','\'',$(template_dir)) -htmldir_SQ = $(subst ','\'',$(htmldir)) +htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative)) prefix_SQ = $(subst ','\'',$(prefix)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) @@ -1676,9 +1682,9 @@ strip: $(PROGRAMS) git$X git.sp git.s git.o: GIT-PREFIX git.sp git.s git.o: EXTRA_CPPFLAGS = \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - '-DGIT_MAN_PATH="$(mandir_SQ)"' \ - '-DGIT_INFO_PATH="$(infodir_SQ)"' + '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_relative_SQ)"' git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ @@ -1688,9 +1694,9 @@ help.sp help.s help.o: common-cmds.h builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - '-DGIT_MAN_PATH="$(mandir_SQ)"' \ - '-DGIT_INFO_PATH="$(infodir_SQ)"' + '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_relative_SQ)"' version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT version.sp version.s version.o: EXTRA_CPPFLAGS = \ diff --git a/archive-zip.c b/archive-zip.c index a8d119305f..b2c4fe0e9f 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -111,8 +111,9 @@ static void copy_le32(unsigned char *dest, unsigned int n) dest[3] = 0xff & (n >> 030); } -static void *zlib_deflate(void *data, unsigned long size, - int compression_level, unsigned long *compressed_size) +static void *zlib_deflate_raw(void *data, unsigned long size, + int compression_level, + unsigned long *compressed_size) { git_zstream stream; unsigned long maxsize; @@ -120,7 +121,7 @@ static void *zlib_deflate(void *data, unsigned long size, int result; memset(&stream, 0, sizeof(stream)); - git_deflate_init(&stream, compression_level); + git_deflate_init_raw(&stream, compression_level); maxsize = git_deflate_bound(&stream, size); buffer = xmalloc(maxsize); @@ -265,14 +266,11 @@ static int write_zip_entry(struct archiver_args *args, } if (buffer && method == 8) { - deflated = zlib_deflate(buffer, size, args->compression_level, - &compressed_size); - if (deflated && compressed_size - 6 < size) { - /* ZLIB --> raw compressed data (see RFC 1950) */ - /* CMF and FLG ... */ - out = (unsigned char *)deflated + 2; - compressed_size -= 6; /* ... and ADLER32 */ - } else { + out = deflated = zlib_deflate_raw(buffer, size, + args->compression_level, + &compressed_size); + if (!out || compressed_size >= size) { + out = buffer; method = 0; compressed_size = size; } @@ -353,7 +351,7 @@ static int write_zip_entry(struct archiver_args *args, unsigned char compressed[STREAM_BUFFER_SIZE * 2]; memset(&zstream, 0, sizeof(zstream)); - git_deflate_init(&zstream, args->compression_level); + git_deflate_init_raw(&zstream, args->compression_level); compressed_size = 0; zstream.next_out = compressed; @@ -370,13 +368,10 @@ static int write_zip_entry(struct archiver_args *args, result = git_deflate(&zstream, 0); if (result != Z_OK) die("deflate error (%d)", result); - out = compressed; - if (!compressed_size) - out += 2; - out_len = zstream.next_out - out; + out_len = zstream.next_out - compressed; if (out_len > 0) { - write_or_die(1, out, out_len); + write_or_die(1, compressed, out_len); compressed_size += out_len; zstream.next_out = compressed; zstream.avail_out = sizeof(compressed); @@ -394,11 +389,8 @@ static int write_zip_entry(struct archiver_args *args, die("deflate error (%d)", result); git_deflate_end(&zstream); - out = compressed; - if (!compressed_size) - out += 2; - out_len = zstream.next_out - out - 4; - write_or_die(1, out, out_len); + out_len = zstream.next_out - compressed; + write_or_die(1, compressed, out_len); compressed_size += out_len; zip_offset += compressed_size; diff --git a/attr.c b/attr.c index e2f9377891..689bc2a896 100644 --- a/attr.c +++ b/attr.c @@ -657,24 +657,24 @@ static void prepare_attr_stack(const char *path, int dirlen) } static int path_matches(const char *pathname, int pathlen, - const char *basename, + int basename_offset, const struct pattern *pat, const char *base, int baselen) { const char *pattern = pat->pattern; int prefix = pat->nowildcardlen; + int isdir = (pathlen && pathname[pathlen - 1] == '/'); - if ((pat->flags & EXC_FLAG_MUSTBEDIR) && - ((!pathlen) || (pathname[pathlen-1] != '/'))) + if ((pat->flags & EXC_FLAG_MUSTBEDIR) && !isdir) return 0; if (pat->flags & EXC_FLAG_NODIR) { - return match_basename(basename, - pathlen - (basename - pathname), + return match_basename(pathname + basename_offset, + pathlen - basename_offset - isdir, pattern, prefix, pat->patternlen, pat->flags); } - return match_pathname(pathname, pathlen, + return match_pathname(pathname, pathlen - isdir, base, baselen, pattern, prefix, pat->patternlen, pat->flags); } @@ -703,7 +703,7 @@ static int fill_one(const char *what, struct match_attr *a, int rem) return rem; } -static int fill(const char *path, int pathlen, const char *basename, +static int fill(const char *path, int pathlen, int basename_offset, struct attr_stack *stk, int rem) { int i; @@ -713,7 +713,7 @@ static int fill(const char *path, int pathlen, const char *basename, struct match_attr *a = stk->attrs[i]; if (a->is_macro) continue; - if (path_matches(path, pathlen, basename, + if (path_matches(path, pathlen, basename_offset, &a->u.pat, base, stk->originlen)) rem = fill_one("fill", a, rem); } @@ -752,7 +752,8 @@ static void collect_all_attrs(const char *path) { struct attr_stack *stk; int i, pathlen, rem, dirlen; - const char *basename, *cp, *last_slash = NULL; + const char *cp, *last_slash = NULL; + int basename_offset; for (cp = path; *cp; cp++) { if (*cp == '/' && cp[1]) @@ -760,10 +761,10 @@ static void collect_all_attrs(const char *path) } pathlen = cp - path; if (last_slash) { - basename = last_slash + 1; + basename_offset = last_slash + 1 - path; dirlen = last_slash - path; } else { - basename = path; + basename_offset = 0; dirlen = 0; } @@ -773,7 +774,7 @@ static void collect_all_attrs(const char *path) rem = attr_nr; for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = fill(path, pathlen, basename, stk, rem); + rem = fill(path, pathlen, basename_offset, stk, rem); } int git_check_attr(const char *path, int num, struct git_attr_check *check) diff --git a/builtin/apply.c b/builtin/apply.c index 06f5320b18..5b882d01f6 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -2117,10 +2117,10 @@ static void update_pre_post_images(struct image *preimage, /* * Adjust the common context lines in postimage. This can be - * done in-place when we are just doing whitespace fixing, - * which does not make the string grow, but needs a new buffer - * when ignoring whitespace causes the update, since in this case - * we could have e.g. tabs converted to multiple spaces. + * done in-place when we are shrinking it with whitespace + * fixing, but needs a new buffer when ignoring whitespace or + * expanding leading tabs to spaces. + * * We trust the caller to tell us if the update can be done * in place (postlen==0) or not. */ @@ -2185,7 +2185,7 @@ static int match_fragment(struct image *img, int i; char *fixed_buf, *buf, *orig, *target; struct strbuf fixed; - size_t fixed_len; + size_t fixed_len, postlen; int preimage_limit; if (preimage->nr + try_lno <= img->nr) { @@ -2335,6 +2335,7 @@ static int match_fragment(struct image *img, strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; + postlen = 0; for (i = 0; i < preimage_limit; i++) { size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; @@ -2362,6 +2363,7 @@ static int match_fragment(struct image *img, match = (tgtfix.len == fixed.len - fixstart && !memcmp(tgtfix.buf, fixed.buf + fixstart, fixed.len - fixstart)); + postlen += tgtfix.len; strbuf_release(&tgtfix); if (!match) @@ -2399,8 +2401,10 @@ static int match_fragment(struct image *img, * hunk match. Update the context lines in the postimage. */ fixed_buf = strbuf_detach(&fixed, &fixed_len); + if (postlen < postimage->len) + postlen = 0; update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, 0); + fixed_buf, fixed_len, postlen); return 1; unmatch_exit: diff --git a/builtin/archive.c b/builtin/archive.c index 9a1cfd3dac..49178f159e 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -27,8 +27,8 @@ static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec, const char *name_hint) { - char buf[LARGE_PACKET_MAX]; - int fd[2], i, len, rv; + char *buf; + int fd[2], i, rv; struct transport *transport; struct remote *_remote; @@ -53,21 +53,18 @@ static int run_remote_archiver(int argc, const char **argv, packet_write(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) + buf = packet_read_line(fd[0], NULL); + if (!buf) die(_("git archive: expected ACK/NAK, got EOF")); - if (buf[len-1] == '\n') - buf[--len] = 0; if (strcmp(buf, "ACK")) { - if (len > 5 && !prefixcmp(buf, "NACK ")) + if (!prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); - if (len > 4 && !prefixcmp(buf, "ERR ")) + if (!prefixcmp(buf, "ERR ")) die(_("remote error: %s"), buf + 4); die(_("git archive: protocol error")); } - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) + if (packet_read_line(fd[0], NULL)) die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ diff --git a/builtin/branch.c b/builtin/branch.c index 00d17d25d1..e09ce51c2e 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -18,6 +18,7 @@ #include "string-list.h" #include "column.h" #include "utf8.h" +#include "wt-status.h" static const char * const builtin_branch_usage[] = { N_("git branch [options] [-r | -a] [--merged | --no-merged]"), @@ -550,6 +551,29 @@ static int calc_maxwidth(struct ref_list *refs) return w; } +static char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) + strbuf_addf(&desc, _("(detached from %s)"), + state.detached_from); + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} static void show_detached(struct ref_list *ref_list) { @@ -557,7 +581,7 @@ static void show_detached(struct ref_list *ref_list) if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { struct ref_item item; - item.name = xstrdup(_("(no branch)")); + item.name = get_head_description(); item.width = utf8_strwidth(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; diff --git a/builtin/cat-file.c b/builtin/cat-file.c index ad29000736..40f87b4649 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -193,7 +193,7 @@ static int batch_one_object(const char *obj_name, int print_contents) unsigned char sha1[20]; enum object_type type = 0; unsigned long size; - void *contents; + void *contents = NULL; if (!obj_name) return 1; diff --git a/builtin/checkout.c b/builtin/checkout.c index a9c1b5a95f..f8033f446e 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -271,24 +271,55 @@ static int checkout_paths(const struct checkout_opts *opts, ; ps_matched = xcalloc(1, pos); + /* + * Make sure all pathspecs participated in locating the paths + * to be checked out. + */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; + ce->ce_flags &= ~CE_MATCHED; if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index; it will not be checked + * out to the working tree and it does not matter + * if pathspec matched this entry. We will not do + * anything to this entry at all. + */ continue; - match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, ps_matched); + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree_recursive (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), + 0, ps_matched)) + ce->ce_flags |= CE_MATCHED; } - if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) + if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) { + free(ps_matched); return 1; + } + free(ps_matched); /* "checkout -m path" to recreate conflicted state */ if (opts->merge) - unmerge_cache(opts->pathspec); + unmerge_marked_index(&the_index); /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; if (opts->force) { @@ -313,9 +344,7 @@ static int checkout_paths(const struct checkout_opts *opts, state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) - continue; - if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); continue; diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index eac901a0ee..f641ff2a89 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -10,7 +10,7 @@ #include "utf8.h" #include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p )...] [-S] [-m ] [-F ] fp) < sb.len) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 670e81fd99..aba4465552 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -119,14 +119,11 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) /* in stateless RPC mode we use pkt-line to read * from stdin, until we get a flush packet */ - static char line[1000]; for (;;) { - int n = packet_read_line(0, line, sizeof(line)); - if (!n) + char *line = packet_read_line(0, NULL); + if (!line) break; - if (line[n-1] == '\n') - n--; - add_sought_entry_mem(&sought, &nr_sought, &alloc_sought, line, n); + add_sought_entry(&sought, &nr_sought, &alloc_sought, line); } } else { @@ -147,7 +144,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], &ref, 0, NULL); + get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL); ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought, pack_lockfile_ptr); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index ef62124aa4..79dfe47320 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -78,6 +78,7 @@ static int nr_threads; static int from_stdin; static int strict; static int verbose; +static int show_stat; static struct progress *progress; @@ -108,6 +109,10 @@ static pthread_mutex_t work_mutex; #define work_lock() lock_mutex(&work_mutex) #define work_unlock() unlock_mutex(&work_mutex) +static pthread_mutex_t deepest_delta_mutex; +#define deepest_delta_lock() lock_mutex(&deepest_delta_mutex) +#define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex) + static pthread_key_t key; static inline void lock_mutex(pthread_mutex_t *mutex) @@ -130,6 +135,8 @@ static void init_thread(void) init_recursive_mutex(&read_mutex); pthread_mutex_init(&counter_mutex, NULL); pthread_mutex_init(&work_mutex, NULL); + if (show_stat) + pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); thread_data = xcalloc(nr_threads, sizeof(*thread_data)); threads_active = 1; @@ -143,6 +150,8 @@ static void cleanup_thread(void) pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&counter_mutex); pthread_mutex_destroy(&work_mutex); + if (show_stat) + pthread_mutex_destroy(&deepest_delta_mutex); pthread_key_delete(key); free(thread_data); } @@ -158,6 +167,9 @@ static void cleanup_thread(void) #define work_lock() #define work_unlock() +#define deepest_delta_lock() +#define deepest_delta_unlock() + #endif @@ -833,9 +845,13 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, *delta_data; delta_obj->real_type = base->obj->real_type; - delta_obj->delta_depth = base->obj->delta_depth + 1; - if (deepest_delta < delta_obj->delta_depth) - deepest_delta = delta_obj->delta_depth; + if (show_stat) { + delta_obj->delta_depth = base->obj->delta_depth + 1; + deepest_delta_lock(); + if (deepest_delta < delta_obj->delta_depth) + deepest_delta = delta_obj->delta_depth; + deepest_delta_unlock(); + } delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); @@ -951,8 +967,10 @@ static void *threaded_second_pass(void *data) set_thread_data(data); for (;;) { int i; - work_lock(); + counter_lock(); display_progress(progress, nr_resolved_deltas); + counter_unlock(); + work_lock(); while (nr_dispatched < nr_objects && is_delta_type(objects[nr_dispatched].type)) nr_dispatched++; @@ -1107,6 +1125,8 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha objects = xrealloc(objects, (nr_objects + nr_unresolved + 1) * sizeof(*objects)); + memset(objects + nr_objects + 1, 0, + nr_unresolved * sizeof(*objects)); f = sha1fd(output_fd, curr_pack); fix_unresolved_deltas(f, nr_unresolved); strbuf_addf(&msg, _("completed with %d local objects"), @@ -1463,7 +1483,7 @@ static void show_pack_info(int stat_only) int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0; const char *curr_pack, *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; @@ -1496,10 +1516,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) verify = 1; } else if (!strcmp(arg, "--verify-stat")) { verify = 1; - stat = 1; + show_stat = 1; } else if (!strcmp(arg, "--verify-stat-only")) { verify = 1; - stat = 1; + show_stat = 1; stat_only = 1; } else if (!strcmp(arg, "--keep")) { keep_msg = ""; @@ -1607,7 +1627,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (strict) check_objects(); - if (stat) + if (show_stat) show_pack_info(stat_only); idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); diff --git a/builtin/log.c b/builtin/log.c index 8f0b2e84fe..0f318107e5 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -23,6 +23,7 @@ #include "streaming.h" #include "version.h" #include "mailmap.h" +#include "gpg-interface.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -367,6 +368,8 @@ static int git_log_config(const char *var, const char *value, void *cb) if (grep_config(var, value, cb) < 0) return -1; + if (git_gpg_config(var, value, cb) < 0) + return -1; return git_diff_ui_config(var, value, cb); } @@ -1086,7 +1089,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct commit *origin = NULL, *head = NULL; const char *in_reply_to = NULL; struct patch_ids ids; - char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; @@ -1193,16 +1195,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.subject_prefix = strbuf_detach(&sprefix, NULL); } - if (do_signoff) { - const char *committer; - const char *endpos; - committer = git_committer_info(IDENT_STRICT); - endpos = strchr(committer, '>'); - if (!endpos) - die(_("bogus committer info %s"), committer); - add_signoff = xmemdupz(committer, endpos - committer + 1); - } - for (i = 0; i < extra_hdr.nr; i++) { strbuf_addstr(&buf, extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); @@ -1393,7 +1385,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) total++; start_number--; } - rev.add_signoff = add_signoff; + rev.add_signoff = do_signoff; while (0 <= --nr) { int shown; commit = list[nr]; diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index e0d0b7d28b..bc912e399e 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -245,7 +245,7 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) unsigned dirmask = 0, mask = 0; for (i = 0; i < 3; i++) { - mask |= (1 << 1); + mask |= (1 << i); if (n[i].mode && S_ISDIR(n[i].mode)) dirmask |= (1 << i); } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 62ba6e7a3d..ccebd74f16 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -754,17 +754,15 @@ static struct command *read_head_info(void) struct command *commands = NULL; struct command **p = &commands; for (;;) { - static char line[1000]; + char *line; unsigned char old_sha1[20], new_sha1[20]; struct command *cmd; char *refname; int len, reflen; - len = packet_read_line(0, line, sizeof(line)); - if (!len) + line = packet_read_line(0, &len); + if (!line) break; - if (line[len-1] == '\n') - line[--len] = 0; if (len < 83 || line[40] != ' ' || line[81] != ' ' || @@ -932,7 +930,7 @@ static void report(struct command *commands, const char *unpack_status) if (use_sideband) send_sideband(1, 1, buf.buf, buf.len, use_sideband); else - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 57a46b2654..152c4ea092 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -79,7 +79,7 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); } @@ -207,7 +207,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) memset(&extra_have, 0, sizeof(extra_have)); - get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have); + get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have); transport_verify_remote_names(nr_refspecs, refspecs); diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index b928beb8ed..af2da35e7d 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -7,6 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" +#include "argv-array.h" static const char upload_archive_usage[] = "git upload-archive "; @@ -18,51 +19,31 @@ static const char deadchild[] = int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { - const char *sent_argv[MAX_ARGS]; + struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - char *p, buf[4096]; - int sent_argc; - int len; if (argc != 2) usage(upload_archive_usage); - if (strlen(argv[1]) + 1 > sizeof(buf)) - die("insanely long repository name"); - - strcpy(buf, argv[1]); /* enter-repo smudges its argument */ - - if (!enter_repo(buf, 0)) - die("'%s' does not appear to be a git repository", buf); + if (!enter_repo(argv[1], 0)) + die("'%s' does not appear to be a git repository", argv[1]); /* put received options in sent_argv[] */ - sent_argc = 1; - sent_argv[0] = "git-upload-archive"; - for (p = buf;;) { - /* This will die if not enough free space in buf */ - len = packet_read_line(0, p, (buf + sizeof buf) - p); - if (len == 0) + argv_array_push(&sent_argv, "git-upload-archive"); + for (;;) { + char *buf = packet_read_line(0, NULL); + if (!buf) break; /* got a flush */ - if (sent_argc > MAX_ARGS - 2) - die("Too many options (>%d)", MAX_ARGS - 2); + if (sent_argv.argc > MAX_ARGS) + die("Too many options (>%d)", MAX_ARGS - 1); - if (p[len-1] == '\n') { - p[--len] = 0; - } - if (len < strlen(arg_cmd) || - strncmp(arg_cmd, p, strlen(arg_cmd))) + if (prefixcmp(buf, arg_cmd)) die("'argument' token or flush expected"); - - len -= strlen(arg_cmd); - memmove(p, p + strlen(arg_cmd), len); - sent_argv[sent_argc++] = p; - p += len; - *p++ = 0; + argv_array_push(&sent_argv, buf + strlen(arg_cmd)); } - sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); + return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) diff --git a/cache.h b/cache.h index fc5c5b5733..c12957bb03 100644 --- a/cache.h +++ b/cache.h @@ -34,6 +34,7 @@ int git_inflate(git_zstream *, int flush); void git_deflate_init(git_zstream *, int level); void git_deflate_init_gzip(git_zstream *, int level); +void git_deflate_init_raw(git_zstream *, int level); void git_deflate_end(git_zstream *); int git_deflate_abort(git_zstream *); int git_deflate_end_gently(git_zstream *); @@ -131,7 +132,6 @@ struct cache_entry { unsigned int ce_namelen; unsigned char sha1[20]; struct cache_entry *next; - struct cache_entry *dir_next; char name[FLEX_ARRAY]; /* more */ }; @@ -162,6 +162,9 @@ struct cache_entry { #define CE_UNPACKED (1 << 24) #define CE_NEW_SKIP_WORKTREE (1 << 25) +/* used to temporarily mark paths matched by pathspecs */ +#define CE_MATCHED (1 << 26) + /* * Extended on-disk flags */ @@ -267,25 +270,15 @@ struct index_state { unsigned name_hash_initialized : 1, initialized : 1; struct hash_table name_hash; + struct hash_table dir_hash; }; extern struct index_state the_index; /* Name hashing */ extern void add_name_hash(struct index_state *istate, struct cache_entry *ce); -/* - * We don't actually *remove* it, we can just mark it invalid so that - * we won't find it in lookups. - * - * Not only would we have to search the lists (simple enough), but - * we'd also have to rehash other hash buckets in case this makes the - * hash bucket empty (common). So it's much better to just mark - * it. - */ -static inline void remove_name_hash(struct cache_entry *ce) -{ - ce->ce_flags |= CE_UNHASHED; -} +extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce); +extern void free_name_hash(struct index_state *istate); #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS @@ -1063,7 +1056,9 @@ struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; }; -extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *); +extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, + struct extra_have_objects *); extern int server_supports(const char *feature); extern int parse_feature_request(const char *features, const char *feature); extern const char *server_feature_value(const char *feature, int *len_ret); diff --git a/combine-diff.c b/combine-diff.c index 35d41cd56d..6288485965 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -5,6 +5,7 @@ #include "diffcore.h" #include "quote.h" #include "xdiff-interface.h" +#include "xdiff/xmacros.h" #include "log-tree.h" #include "refs.h" #include "userdiff.h" @@ -122,7 +123,47 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode, return blob; } -static void append_lost(struct sline *sline, int n, const char *line, int len) +static int match_string_spaces(const char *line1, int len1, + const char *line2, int len2, + long flags) +{ + if (flags & XDF_WHITESPACE_FLAGS) { + for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--); + for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--); + } + + if (!(flags & (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE))) + return (len1 == len2 && !memcmp(line1, line2, len1)); + + while (len1 > 0 && len2 > 0) { + len1--; + len2--; + if (XDL_ISSPACE(line1[len1]) || XDL_ISSPACE(line2[len2])) { + if ((flags & XDF_IGNORE_WHITESPACE_CHANGE) && + (!XDL_ISSPACE(line1[len1]) || !XDL_ISSPACE(line2[len2]))) + return 0; + + for (; len1 > 0 && XDL_ISSPACE(line1[len1]); len1--); + for (; len2 > 0 && XDL_ISSPACE(line2[len2]); len2--); + } + if (line1[len1] != line2[len2]) + return 0; + } + + if (flags & XDF_IGNORE_WHITESPACE) { + /* Consume remaining spaces */ + for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--); + for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--); + } + + /* We matched full line1 and line2 */ + if (!len1 && !len2) + return 1; + + return 0; +} + +static void append_lost(struct sline *sline, int n, const char *line, int len, long flags) { struct lline *lline; unsigned long this_mask = (1UL<lost_head) { lline = sline->next_lost; while (lline) { - if (lline->len == len && - !memcmp(lline->line, line, len)) { + if (match_string_spaces(lline->line, lline->len, + line, len, flags)) { lline->parent_map |= this_mask; sline->next_lost = lline->next; return; @@ -162,6 +203,7 @@ struct combine_diff_state { int n; struct sline *sline; struct sline *lost_bucket; + long flags; }; static void consume_line(void *state_, char *line, unsigned long len) @@ -201,7 +243,7 @@ static void consume_line(void *state_, char *line, unsigned long len) return; /* not in any hunk yet */ switch (line[0]) { case '-': - append_lost(state->lost_bucket, state->n, line+1, len-1); + append_lost(state->lost_bucket, state->n, line+1, len-1, state->flags); break; case '+': state->sline[state->lno-1].flag |= state->nmask; @@ -215,7 +257,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, struct sline *sline, unsigned int cnt, int n, int num_parent, int result_deleted, struct userdiff_driver *textconv, - const char *path) + const char *path, long flags) { unsigned int p_lno, lno; unsigned long nmask = (1UL << n); @@ -231,9 +273,10 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path); parent_file.size = sz; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = 0; + xpp.flags = flags; memset(&xecfg, 0, sizeof(xecfg)); memset(&state, 0, sizeof(state)); + state.flags = flags; state.nmask = nmask; state.sline = sline; state.lno = 1; @@ -962,7 +1005,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, elem->parent[i].mode, &result_file, sline, cnt, i, num_parent, result_deleted, - textconv, elem->path); + textconv, elem->path, opt->xdl_opts); } show_hunks = make_hunks(sline, cnt, num_parent, dense); diff --git a/compat/cygwin.c b/compat/cygwin.c index 5428858875..871b41d23a 100644 --- a/compat/cygwin.c +++ b/compat/cygwin.c @@ -1,3 +1,4 @@ +#define CYGWIN_C #define WIN32_LEAN_AND_MEAN #ifdef CYGWIN_V15_WIN32API #include "../git-compat-util.h" @@ -10,6 +11,18 @@ #endif #include "../cache.h" /* to read configuration */ +/* + * Return POSIX permission bits, regardless of core.ignorecygwinfstricks + */ +int cygwin_get_st_mode_bits(const char *path, int *mode) +{ + struct stat st; + if (lstat(path, &st) < 0) + return -1; + *mode = st.st_mode; + return 0; +} + static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts) { long long winTime = ((long long)ft->dwHighDateTime << 32) + diff --git a/compat/cygwin.h b/compat/cygwin.h index a3229f5b4f..c04965a2e0 100644 --- a/compat/cygwin.h +++ b/compat/cygwin.h @@ -4,6 +4,11 @@ typedef int (*stat_fn_t)(const char*, struct stat*); extern stat_fn_t cygwin_stat_fn; extern stat_fn_t cygwin_lstat_fn; +int cygwin_get_st_mode_bits(const char *path, int *mode); +#define get_st_mode_bits(p,m) cygwin_get_st_mode_bits((p),(m)) +#ifndef CYGWIN_C +/* cygwin.c needs the original lstat() */ #define stat(path, buf) (*cygwin_stat_fn)(path, buf) #define lstat(path, buf) (*cygwin_lstat_fn)(path, buf) +#endif diff --git a/config.mak.in b/config.mak.in index fa02bdd82a..e6a6d0f941 100644 --- a/config.mak.in +++ b/config.mak.in @@ -22,9 +22,3 @@ docdir = @docdir@ mandir = @mandir@ htmldir = @htmldir@ - -srcdir = @srcdir@ -VPATH = @srcdir@ - -export exec_prefix mandir -export srcdir VPATH diff --git a/connect.c b/connect.c index 49e56ba35a..f57efd06c1 100644 --- a/connect.c +++ b/connect.c @@ -62,8 +62,8 @@ static void die_initial_contact(int got_at_least_one_head) /* * Read all the refs from the other end */ -struct ref **get_remote_heads(int in, struct ref **list, - unsigned int flags, +struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, struct extra_have_objects *extra_have) { int got_at_least_one_head = 0; @@ -72,18 +72,19 @@ struct ref **get_remote_heads(int in, struct ref **list, for (;;) { struct ref *ref; unsigned char old_sha1[20]; - static char buffer[1000]; char *name; int len, name_len; + char *buffer = packet_buffer; - len = packet_read(in, buffer, sizeof(buffer)); + len = packet_read(in, &src_buf, &src_len, + packet_buffer, sizeof(packet_buffer), + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); if (len < 0) die_initial_contact(got_at_least_one_head); if (!len) break; - if (buffer[len-1] == '\n') - buffer[--len] = 0; if (len > 4 && !prefixcmp(buffer, "ERR ")) die("remote error: %s", buffer + 4); diff --git a/daemon.c b/daemon.c index df8c0ab058..6aeddcb98d 100644 --- a/daemon.c +++ b/daemon.c @@ -600,7 +600,7 @@ static void parse_host_arg(char *extra_args, int buflen) static int execute(void) { - static char line[1000]; + char *line = packet_buffer; int pktlen, len, i; char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); @@ -608,7 +608,7 @@ static int execute(void) loginfo("Connection from %s:%s", addr, port); alarm(init_timeout ? init_timeout : timeout); - pktlen = packet_read_line(0, line, sizeof(line)); + pktlen = packet_read(0, NULL, NULL, packet_buffer, sizeof(packet_buffer), 0); alarm(0); len = strlen(line); diff --git a/dir.c b/dir.c index 57394e452e..1e42b2b150 100644 --- a/dir.c +++ b/dir.c @@ -59,6 +59,35 @@ inline int git_fnmatch(const char *pattern, const char *string, return fnmatch(pattern, string, fnm_flags); } +static int fnmatch_icase_mem(const char *pattern, int patternlen, + const char *string, int stringlen, + int flags) +{ + int match_status; + struct strbuf pat_buf = STRBUF_INIT; + struct strbuf str_buf = STRBUF_INIT; + const char *use_pat = pattern; + const char *use_str = string; + + if (pattern[patternlen]) { + strbuf_add(&pat_buf, pattern, patternlen); + use_pat = pat_buf.buf; + } + if (string[stringlen]) { + strbuf_add(&str_buf, string, stringlen); + use_str = str_buf.buf; + } + + if (ignore_case) + flags |= WM_CASEFOLD; + match_status = wildmatch(use_pat, use_str, flags, NULL); + + strbuf_release(&pat_buf); + strbuf_release(&str_buf); + + return match_status; +} + static size_t common_prefix_len(const char **pathspec) { const char *n, *first; @@ -626,15 +655,20 @@ int match_basename(const char *basename, int basenamelen, int flags) { if (prefix == patternlen) { - if (!strcmp_icase(pattern, basename)) + if (patternlen == basenamelen && + !strncmp_icase(pattern, basename, basenamelen)) return 1; } else if (flags & EXC_FLAG_ENDSWITH) { + /* "*literal" matching against "fooliteral" */ if (patternlen - 1 <= basenamelen && - !strcmp_icase(pattern + 1, - basename + basenamelen - patternlen + 1)) + !strncmp_icase(pattern + 1, + basename + basenamelen - (patternlen - 1), + patternlen - 1)) return 1; } else { - if (fnmatch_icase(pattern, basename, 0) == 0) + if (fnmatch_icase_mem(pattern, patternlen, + basename, basenamelen, + 0) == 0) return 1; } return 0; @@ -654,6 +688,7 @@ int match_pathname(const char *pathname, int pathlen, */ if (*pattern == '/') { pattern++; + patternlen--; prefix--; } @@ -680,13 +715,22 @@ int match_pathname(const char *pathname, int pathlen, if (strncmp_icase(pattern, name, prefix)) return 0; pattern += prefix; + patternlen -= prefix; name += prefix; namelen -= prefix; + + /* + * If the whole pattern did not have a wildcard, + * then our prefix match is all we need; we + * do not need to call fnmatch at all. + */ + if (!patternlen && !namelen) + return 1; } - return wildmatch(pattern, name, - WM_PATHNAME | (ignore_case ? WM_CASEFOLD : 0), - NULL) == 0; + return fnmatch_icase_mem(pattern, patternlen, + name, namelen, + WM_PATHNAME) == 0; } /* diff --git a/entry.c b/entry.c index a20bcbc47a..d7c131d453 100644 --- a/entry.c +++ b/entry.c @@ -147,7 +147,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout struct stat st; if (ce_mode_s_ifmt == S_IFREG) { - struct stream_filter *filter = get_stream_filter(path, ce->sha1); + struct stream_filter *filter = get_stream_filter(ce->name, ce->sha1); if (filter && !streaming_write_entry(ce, path, filter, state, to_tempfile, diff --git a/fast-import.c b/fast-import.c index a0c2c2ff14..5f539d7d8f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2465,6 +2465,7 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout) hashcpy(sha1, oe->idx.sha1); } else if (!prefixcmp(p, "inline ")) { inline_data = 1; + oe = NULL; /* not used with inline_data, but makes gcc happy */ p += strlen("inline"); /* advance to space */ } else { if (get_sha1_hex(p, sha1)) diff --git a/fetch-pack.c b/fetch-pack.c index cef8fde61b..f156dd4fac 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -172,8 +172,8 @@ static void consume_shallow_list(struct fetch_pack_args *args, int fd) * shallow and unshallow commands every time there * is a block of have lines exchanged. */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { + char *line; + while ((line = packet_read_line(fd, NULL))) { if (!prefixcmp(line, "shallow ")) continue; if (!prefixcmp(line, "unshallow ")) @@ -215,17 +215,17 @@ static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) static enum ack_type get_ack(int fd, unsigned char *result_sha1) { - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); + int len; + char *line = packet_read_line(fd, &len); if (!len) die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; if (!strcmp(line, "NAK")) return NAK; if (!prefixcmp(line, "ACK ")) { if (!get_sha1_hex(line+4, result_sha1)) { + if (len < 45) + return ACK; if (strstr(line+45, "continue")) return ACK_continue; if (strstr(line+45, "common")) @@ -245,7 +245,7 @@ static void send_request(struct fetch_pack_args *args, send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); packet_flush(fd); } else - safe_write(fd, buf->buf, buf->len); + write_or_die(fd, buf->buf, buf->len); } static void insert_one_alternate_ref(const struct ref *ref, void *unused) @@ -346,11 +346,11 @@ static int find_common(struct fetch_pack_args *args, state_len = req_buf.len; if (args->depth > 0) { - char line[1024]; + char *line; unsigned char sha1[20]; send_request(args, fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { + while ((line = packet_read_line(fd[0], NULL))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); diff --git a/git-am.sh b/git-am.sh index 202130f888..c092855dd7 100755 --- a/git-am.sh +++ b/git-am.sh @@ -778,13 +778,6 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"." action=yes fi - if test -f "$dotest/final-commit" - then - FIRSTLINE=$(sed 1q "$dotest/final-commit") - else - FIRSTLINE="" - fi - if test $action = skip then go_next @@ -797,6 +790,13 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"." stop_here $this fi + if test -f "$dotest/final-commit" + then + FIRSTLINE=$(sed 1q "$dotest/final-commit") + else + FIRSTLINE="" + fi + say "$(eval_gettext "Applying: \$FIRSTLINE")" case "$resolved" in diff --git a/git-compat-util.h b/git-compat-util.h index 90e0372038..cde442fb5f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -163,6 +163,7 @@ typedef long intptr_t; typedef unsigned long uintptr_t; #endif +int get_st_mode_bits(const char *path, int *mode); #if defined(__CYGWIN__) #undef _XOPEN_SOURCE #include diff --git a/git-difftool.perl b/git-difftool.perl index 12231fbc67..67802922cc 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -13,9 +13,9 @@ use 5.008; use strict; use warnings; +use Error qw(:try); use File::Basename qw(dirname); use File::Copy; -use File::Compare; use File::Find; use File::stat; use File::Path qw(mkpath rmtree); @@ -83,6 +83,52 @@ sub exit_cleanup exit($status | ($status >> 8)); } +sub use_wt_file +{ + my ($repo, $workdir, $file, $sha1, $symlinks) = @_; + my $null_sha1 = '0' x 40; + + if ($sha1 ne $null_sha1 and not $symlinks) { + return 0; + } + + my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file"); + my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1); + return ($use, $wt_sha1); +} + +sub changed_files +{ + my ($repo_path, $index, $worktree) = @_; + $ENV{GIT_INDEX_FILE} = $index; + $ENV{GIT_WORK_TREE} = $worktree; + my $must_unset_git_dir = 0; + if (not defined($ENV{GIT_DIR})) { + $must_unset_git_dir = 1; + $ENV{GIT_DIR} = $repo_path; + } + + my @refreshargs = qw/update-index --really-refresh -q --unmerged/; + my @gitargs = qw/diff-files --name-only -z/; + try { + Git::command_oneline(@refreshargs); + } catch Git::Error::Command with {}; + + my $line = Git::command_oneline(@gitargs); + my @files; + if (defined $line) { + @files = split('\0', $line); + } else { + @files = (); + } + + delete($ENV{GIT_INDEX_FILE}); + delete($ENV{GIT_WORK_TREE}); + delete($ENV{GIT_DIR}) if ($must_unset_git_dir); + + return map { $_ => 1 } @files; +} + sub setup_dir_diff { my ($repo, $workdir, $symlinks) = @_; @@ -106,6 +152,7 @@ sub setup_dir_diff my $null_sha1 = '0' x 40; my $lindex = ''; my $rindex = ''; + my $wtindex = ''; my %submodule; my %symlink; my @working_tree = (); @@ -159,10 +206,14 @@ sub setup_dir_diff } if ($rmode ne $null_mode) { - if ($rsha1 ne $null_sha1) { - $rindex .= "$rmode $rsha1\t$dst_path\0"; + my ($use, $wt_sha1) = use_wt_file($repo, $workdir, + $dst_path, $rsha1, + $symlinks); + if ($use) { + push @working_tree, $dst_path; + $wtindex .= "$rmode $wt_sha1\t$dst_path\0"; } else { - push(@working_tree, $dst_path); + $rindex .= "$rmode $rsha1\t$dst_path\0"; } } } @@ -203,13 +254,21 @@ sub setup_dir_diff $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/"); exit_cleanup($tmpdir, $rc) if $rc != 0; + $ENV{GIT_INDEX_FILE} = "$tmpdir/wtindex"; + ($inpipe, $ctx) = + $repo->command_input_pipe(qw(update-index --info-only -z --index-info)); + print($inpipe $wtindex); + $repo->command_close_pipe($inpipe, $ctx); + # If $GIT_DIR was explicitly set just for the update/checkout # commands, then it should be unset before continuing. delete($ENV{GIT_DIR}) if ($must_unset_git_dir); delete($ENV{GIT_INDEX_FILE}); # Changes in the working tree need special treatment since they are - # not part of the index + # not part of the index. Remove any trailing slash from $workdir + # before starting to avoid double slashes in symlink targets. + $workdir =~ s|/$||; for my $file (@working_tree) { my $dir = dirname($file); unless (-d "$rdir/$dir") { @@ -373,19 +432,34 @@ sub dir_diff # should be copied back to the working tree. # Do not copy back files when symlinks are used and the # external tool did not replace the original link with a file. + # + # These hashes are loaded lazily since they aren't needed + # in the common case of --symlinks and the difftool updating + # files through the symlink. + my %wt_modified; + my %tmp_modified; + my $indices_loaded = 0; + for my $file (@worktree) { next if $symlinks && -l "$b/$file"; next if ! -f "$b/$file"; - my $diff = compare("$b/$file", "$workdir/$file"); - if ($diff == 0) { - next; - } elsif ($diff == -1) { - my $errmsg = "warning: Could not compare "; - $errmsg += "'$b/$file' with '$workdir/$file'\n"; + if (!$indices_loaded) { + %wt_modified = changed_files($repo->repo_path(), + "$tmpdir/wtindex", "$workdir"); + %tmp_modified = changed_files($repo->repo_path(), + "$tmpdir/wtindex", "$b"); + $indices_loaded = 1; + } + + if (exists $wt_modified{$file} and exists $tmp_modified{$file}) { + my $errmsg = "warning: Both files modified: "; + $errmsg .= "'$workdir/$file' and '$b/$file'.\n"; + $errmsg .= "warning: Working tree file has been left.\n"; + $errmsg .= "warning:\n"; warn $errmsg; $error = 1; - } elsif ($diff == 1) { + } elsif (exists $tmp_modified{$file}) { my $mode = stat("$b/$file")->mode; copy("$b/$file", "$workdir/$file") or exit_cleanup($tmpdir, 1); diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 3373c040d4..07dfeb8df4 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -27,7 +27,7 @@ SUBDIRECTORY_OK=Yes cd_to_toplevel require_work_tree -if ! test "$#" -eq 7 +if test $# != 7 then echo "$LONG_USAGE" exit 1 @@ -38,7 +38,8 @@ case "${1:-.}${2:-.}${3:-.}" in # Deleted in both or deleted in one and unchanged in the other # "$1.." | "$1.$1" | "$1$1.") - if [ "$2" ]; then + if test -n "$2" + then echo "Removing $4" else # read-tree checked that index matches HEAD already, @@ -48,7 +49,8 @@ case "${1:-.}${2:-.}${3:-.}" in # we do not have it in the index, though. exec git update-index --remove -- "$4" fi - if test -f "$4"; then + if test -f "$4" + then rm -f -- "$4" && rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || : fi && @@ -67,7 +69,7 @@ case "${1:-.}${2:-.}${3:-.}" in echo "Adding $4" if test -f "$4" then - echo "ERROR: untracked $4 is overwritten by the merge." + echo "ERROR: untracked $4 is overwritten by the merge." >&2 exit 1 fi git update-index --add --cacheinfo "$7" "$3" "$4" && @@ -78,9 +80,10 @@ case "${1:-.}${2:-.}${3:-.}" in # Added in both, identically (check for same permissions). # ".$3$2") - if [ "$6" != "$7" ]; then - echo "ERROR: File $4 added identically in both branches," - echo "ERROR: but permissions conflict $6->$7." + if test "$6" != "$7" + then + echo "ERROR: File $4 added identically in both branches," >&2 + echo "ERROR: but permissions conflict $6->$7." >&2 exit 1 fi echo "Adding $4" @@ -95,44 +98,36 @@ case "${1:-.}${2:-.}${3:-.}" in case ",$6,$7," in *,120000,*) - echo "ERROR: $4: Not merging symbolic link changes." + echo "ERROR: $4: Not merging symbolic link changes." >&2 exit 1 ;; *,160000,*) - echo "ERROR: $4: Not merging conflicting submodule changes." + echo "ERROR: $4: Not merging conflicting submodule changes." >&2 exit 1 ;; esac - src2=`git-unpack-file $3` + src1=$(git-unpack-file $2) + src2=$(git-unpack-file $3) case "$1" in '') echo "Added $4 in both, but differently." - # This extracts OUR file in $orig, and uses git apply to - # remove lines that are unique to ours. - orig=`git-unpack-file $2` - sz0=`wc -c <"$orig"` - @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add - sz1=`wc -c <"$orig"` - - # If we do not have enough common material, it is not - # worth trying two-file merge using common subsections. - expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig + orig=$(git-unpack-file $2) + create_virtual_base "$orig" "$src2" ;; *) echo "Auto-merging $4" - orig=`git-unpack-file $1` + orig=$(git-unpack-file $1) ;; esac - # Be careful for funny filename such as "-L" in "$4", which - # would confuse "merge" greatly. - src1=`git-unpack-file $2` git merge-file "$src1" "$orig" "$src2" ret=$? msg= - if [ $ret -ne 0 ]; then + if test $ret != 0 || test -z "$1" + then msg='content conflict' + ret=1 fi # Create the working tree file, using "our tree" version from the @@ -140,26 +135,26 @@ case "${1:-.}${2:-.}${3:-.}" in git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1 rm -f -- "$orig" "$src1" "$src2" - if [ "$6" != "$7" ]; then - if [ -n "$msg" ]; then + if test "$6" != "$7" + then + if test -n "$msg" + then msg="$msg, " fi msg="${msg}permissions conflict: $5->$6,$7" ret=1 fi - if [ "$1" = '' ]; then - ret=1 - fi - if [ $ret -ne 0 ]; then - echo "ERROR: $msg in $4" + if test $ret != 0 + then + echo "ERROR: $msg in $4" >&2 exit 1 fi exec git update-index -- "$4" ;; *) - echo "ERROR: $4: Not handling case $1 -> $2 -> $3" + echo "ERROR: $4: Not handling case $1 -> $2 -> $3" >&2 ;; esac exit 1 diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 9cfbe7f143..2f7835941e 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -249,6 +249,18 @@ clear_local_git_env() { unset $(git rev-parse --local-env-vars) } +# Generate a virtual base file for a two-file merge. Uses git apply to +# remove lines from $1 that are not in $2, leaving only common lines. +create_virtual_base() { + sz0=$(wc -c <"$1") + @@DIFF@@ -u -La/"$1" -Lb/"$1" "$1" "$2" | git apply --no-add + sz1=$(wc -c <"$1") + + # If we do not have enough common material, it is not + # worth trying two-file merge using common subsections. + expr $sz0 \< $sz1 \* 2 >/dev/null || : >"$1" +} + # Platform specific tweaks to work around some commands case $(uname -s) in diff --git a/http-backend.c b/http-backend.c index f50e77fb28..8144f3ad5e 100644 --- a/http-backend.c +++ b/http-backend.c @@ -70,7 +70,7 @@ static void format_write(int fd, const char *fmt, ...) if (n >= sizeof(buffer)) die("protocol error: impossibly long line"); - safe_write(fd, buffer, n); + write_or_die(fd, buffer, n); } static void http_status(unsigned code, const char *msg) @@ -111,7 +111,7 @@ static void hdr_cache_forever(void) static void end_headers(void) { - safe_write(1, "\r\n", 2); + write_or_die(1, "\r\n", 2); } __attribute__((format (printf, 1, 2))) @@ -157,7 +157,7 @@ static void send_strbuf(const char *type, struct strbuf *buf) hdr_int(content_length, buf->len); hdr_str(content_type, type); end_headers(); - safe_write(1, buf->buf, buf->len); + write_or_die(1, buf->buf, buf->len); } static void send_local_file(const char *the_type, const char *name) @@ -185,7 +185,7 @@ static void send_local_file(const char *the_type, const char *name) die_errno("Cannot read '%s'", p); if (!n) break; - safe_write(1, buf, n); + write_or_die(1, buf, n); } close(fd); free(buf); diff --git a/http.c b/http.c index d9d1aad3be..8803c70825 100644 --- a/http.c +++ b/http.c @@ -5,6 +5,7 @@ #include "url.h" #include "credential.h" #include "version.h" +#include "pkt-line.h" int active_requests; int http_is_verbose; diff --git a/log-tree.c b/log-tree.c index 3d88823871..7cc7d598e7 100644 --- a/log-tree.c +++ b/log-tree.c @@ -9,6 +9,7 @@ #include "string-list.h" #include "color.h" #include "gpg-interface.h" +#include "sequencer.h" struct decoration name_decoration = { "object names" }; @@ -206,89 +207,6 @@ void show_decorations(struct rev_info *opt, struct commit *commit) putchar(')'); } -/* - * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches - * Signed-off-by: and Acked-by: lines. - */ -static int detect_any_signoff(char *letter, int size) -{ - char *cp; - int seen_colon = 0; - int seen_at = 0; - int seen_name = 0; - int seen_head = 0; - - cp = letter + size; - while (letter <= --cp && *cp == '\n') - continue; - - while (letter <= cp) { - char ch = *cp--; - if (ch == '\n') - break; - - if (!seen_at) { - if (ch == '@') - seen_at = 1; - continue; - } - if (!seen_colon) { - if (ch == '@') - return 0; - else if (ch == ':') - seen_colon = 1; - else - seen_name = 1; - continue; - } - if (('A' <= ch && ch <= 'Z') || - ('a' <= ch && ch <= 'z') || - ch == '-') { - seen_head = 1; - continue; - } - /* no empty last line doesn't match */ - return 0; - } - return seen_head && seen_name; -} - -static void append_signoff(struct strbuf *sb, const char *signoff) -{ - static const char signed_off_by[] = "Signed-off-by: "; - size_t signoff_len = strlen(signoff); - int has_signoff = 0; - char *cp; - - cp = sb->buf; - - /* First see if we already have the sign-off by the signer */ - while ((cp = strstr(cp, signed_off_by))) { - - has_signoff = 1; - - cp += strlen(signed_off_by); - if (cp + signoff_len >= sb->buf + sb->len) - break; - if (strncmp(cp, signoff, signoff_len)) - continue; - if (!isspace(cp[signoff_len])) - continue; - /* we already have him */ - return; - } - - if (!has_signoff) - has_signoff = detect_any_signoff(sb->buf, sb->len); - - if (!has_signoff) - strbuf_addch(sb, '\n'); - - strbuf_addstr(sb, signed_off_by); - strbuf_add(sb, signoff, signoff_len); - strbuf_addch(sb, '\n'); -} - static unsigned int digits_in_number(unsigned int number) { unsigned int i = 10, result = 1; @@ -669,8 +587,10 @@ void show_log(struct rev_info *opt) /* * And then the pretty-printed message itself */ - if (ctx.need_8bit_cte >= 0) - ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); + if (ctx.need_8bit_cte >= 0 && opt->add_signoff) + ctx.need_8bit_cte = + has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); ctx.date_mode = opt->date_mode; ctx.date_mode_explicit = opt->date_mode_explicit; ctx.abbrev = opt->diffopt.abbrev; @@ -683,7 +603,7 @@ void show_log(struct rev_info *opt) pretty_print_commit(&ctx, commit, &msgbuf); if (opt->add_signoff) - append_signoff(&msgbuf, opt->add_signoff); + append_signoff(&msgbuf, 0, APPEND_SIGNOFF_DEDUP); if ((ctx.fmt != CMIT_FMT_USERFORMAT) && ctx.notes_message && *ctx.notes_message) { @@ -789,11 +709,14 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log { int showed_log; struct commit_list *parents; - unsigned const char *sha1 = commit->object.sha1; + unsigned const char *sha1; if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS)) return 0; + parse_commit(commit); + sha1 = commit->tree->object.sha1; + /* Root commit? */ parents = commit->parents; if (!parents) { @@ -816,7 +739,9 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log * parent, showing summary diff of the others * we merged _in_. */ - diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt); + parse_commit(parents->item); + diff_tree_sha1(parents->item->tree->object.sha1, + sha1, "", &opt->diffopt); log_tree_diff_flush(opt); return !opt->loginfo; } @@ -829,7 +754,9 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log for (;;) { struct commit *parent = parents->item; - diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt); + parse_commit(parent); + diff_tree_sha1(parent->tree->object.sha1, + sha1, "", &opt->diffopt); log_tree_diff_flush(opt); showed_log |= !opt->loginfo; diff --git a/match-trees.c b/match-trees.c index 26f7ed143e..2bb734d51c 100644 --- a/match-trees.c +++ b/match-trees.c @@ -47,6 +47,13 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path) return score; } +static int base_name_entries_compare(const struct name_entry *a, + const struct name_entry *b) +{ + return base_name_compare(a->path, tree_entry_len(a), a->mode, + b->path, tree_entry_len(b), b->mode); +} + /* * Inspect two trees, and give a score that tells how similar they are. */ @@ -71,54 +78,35 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2) if (type != OBJ_TREE) die("%s is not a tree", sha1_to_hex(hash2)); init_tree_desc(&two, two_buf, size); - while (one.size | two.size) { - const unsigned char *elem1 = elem1; - const unsigned char *elem2 = elem2; - const char *path1 = path1; - const char *path2 = path2; - unsigned mode1 = mode1; - unsigned mode2 = mode2; + for (;;) { + struct name_entry e1, e2; + int got_entry_from_one = tree_entry(&one, &e1); + int got_entry_from_two = tree_entry(&two, &e2); int cmp; - if (one.size) - elem1 = tree_entry_extract(&one, &path1, &mode1); - if (two.size) - elem2 = tree_entry_extract(&two, &path2, &mode2); - - if (!one.size) { - /* two has more entries */ - score += score_missing(mode2, path2); - update_tree_entry(&two); - continue; - } - if (!two.size) { + if (got_entry_from_one && got_entry_from_two) + cmp = base_name_entries_compare(&e1, &e2); + else if (got_entry_from_one) /* two lacks this entry */ - score += score_missing(mode1, path1); - update_tree_entry(&one); - continue; - } - cmp = base_name_compare(path1, strlen(path1), mode1, - path2, strlen(path2), mode2); - if (cmp < 0) { + cmp = -1; + else if (got_entry_from_two) + /* two has more entries */ + cmp = 1; + else + break; + + if (cmp < 0) /* path1 does not appear in two */ - score += score_missing(mode1, path1); - update_tree_entry(&one); - continue; - } - else if (cmp > 0) { + score += score_missing(e1.mode, e1.path); + else if (cmp > 0) /* path2 does not appear in one */ - score += score_missing(mode2, path2); - update_tree_entry(&two); - continue; - } - else if (hashcmp(elem1, elem2)) + score += score_missing(e2.mode, e2.path); + else if (hashcmp(e1.sha1, e2.sha1)) /* they are different */ - score += score_differs(mode1, mode2, path1); + score += score_differs(e1.mode, e2.mode, e1.path); else /* same subtree or blob */ - score += score_matches(mode1, mode2, path1); - update_tree_entry(&one); - update_tree_entry(&two); + score += score_matches(e1.mode, e2.mode, e1.path); } free(one_buf); free(two_buf); diff --git a/mergetools/p4merge b/mergetools/p4merge index 8a36916567..5a608abf9c 100644 --- a/mergetools/p4merge +++ b/mergetools/p4merge @@ -21,8 +21,12 @@ diff_cmd () { merge_cmd () { touch "$BACKUP" - $base_present || >"$BASE" - "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" + if ! $base_present + then + cp -- "$LOCAL" "$BASE" + create_virtual_base "$BASE" "$REMOTE" + fi + "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED" check_unchanged } diff --git a/name-hash.c b/name-hash.c index 9bac31a6ab..617c86c537 100644 --- a/name-hash.c +++ b/name-hash.c @@ -32,38 +32,96 @@ static unsigned int hash_name(const char *name, int namelen) return hash; } -static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce) +struct dir_entry { + struct dir_entry *next; + struct dir_entry *parent; + struct cache_entry *ce; + int nr; + unsigned int namelen; +}; + +static struct dir_entry *find_dir_entry(struct index_state *istate, + const char *name, unsigned int namelen) +{ + unsigned int hash = hash_name(name, namelen); + struct dir_entry *dir; + + for (dir = lookup_hash(hash, &istate->dir_hash); dir; dir = dir->next) + if (dir->namelen == namelen && + !strncasecmp(dir->ce->name, name, namelen)) + return dir; + return NULL; +} + +static struct dir_entry *hash_dir_entry(struct index_state *istate, + struct cache_entry *ce, int namelen) { /* * Throw each directory component in the hash for quick lookup * during a git status. Directory components are stored with their * closing slash. Despite submodules being a directory, they never * reach this point, because they are stored without a closing slash - * in the cache. + * in index_state.name_hash (as ordinary cache_entries). * - * Note that the cache_entry stored with the directory does not - * represent the directory itself. It is a pointer to an existing - * filename, and its only purpose is to represent existence of the - * directory in the cache. It is very possible multiple directory - * hash entries may point to the same cache_entry. + * Note that the cache_entry stored with the dir_entry merely + * supplies the name of the directory (up to dir_entry.namelen). We + * track the number of 'active' files in a directory in dir_entry.nr, + * so we can tell if the directory is still relevant, e.g. for git + * status. However, if cache_entries are removed, we cannot pinpoint + * an exact cache_entry that's still active. It is very possible that + * multiple dir_entries point to the same cache_entry. */ - unsigned int hash; - void **pos; + struct dir_entry *dir; + + /* get length of parent directory */ + while (namelen > 0 && !is_dir_sep(ce->name[namelen - 1])) + namelen--; + if (namelen <= 0) + return NULL; + + /* lookup existing entry for that directory */ + dir = find_dir_entry(istate, ce->name, namelen); + if (!dir) { + /* not found, create it and add to hash table */ + void **pdir; + unsigned int hash = hash_name(ce->name, namelen); - const char *ptr = ce->name; - while (*ptr) { - while (*ptr && *ptr != '/') - ++ptr; - if (*ptr == '/') { - ++ptr; - hash = hash_name(ce->name, ptr - ce->name); - pos = insert_hash(hash, ce, &istate->name_hash); - if (pos) { - ce->dir_next = *pos; - *pos = ce; - } + dir = xcalloc(1, sizeof(struct dir_entry)); + dir->namelen = namelen; + dir->ce = ce; + + pdir = insert_hash(hash, dir, &istate->dir_hash); + if (pdir) { + dir->next = *pdir; + *pdir = dir; } + + /* recursively add missing parent directories */ + dir->parent = hash_dir_entry(istate, ce, namelen - 1); } + return dir; +} + +static void add_dir_entry(struct index_state *istate, struct cache_entry *ce) +{ + /* Add reference to the directory entry (and parents if 0). */ + struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce)); + while (dir && !(dir->nr++)) + dir = dir->parent; +} + +static void remove_dir_entry(struct index_state *istate, struct cache_entry *ce) +{ + /* + * Release reference to the directory entry (and parents if 0). + * + * Note: we do not remove / free the entry because there's no + * hash.[ch]::remove_hash and dir->next may point to other entries + * that are still valid, so we must not free the memory. + */ + struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce)); + while (dir && dir->nr && !(--dir->nr)) + dir = dir->parent; } static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) @@ -74,7 +132,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) if (ce->ce_flags & CE_HASHED) return; ce->ce_flags |= CE_HASHED; - ce->next = ce->dir_next = NULL; + ce->next = NULL; hash = hash_name(ce->name, ce_namelen(ce)); pos = insert_hash(hash, ce, &istate->name_hash); if (pos) { @@ -82,8 +140,8 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) *pos = ce; } - if (ignore_case) - hash_index_entry_directories(istate, ce); + if (ignore_case && !(ce->ce_flags & CE_UNHASHED)) + add_dir_entry(istate, ce); } static void lazy_init_name_hash(struct index_state *istate) @@ -101,11 +159,33 @@ static void lazy_init_name_hash(struct index_state *istate) void add_name_hash(struct index_state *istate, struct cache_entry *ce) { + /* if already hashed, add reference to directory entries */ + if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_STATE_MASK) + add_dir_entry(istate, ce); + ce->ce_flags &= ~CE_UNHASHED; if (istate->name_hash_initialized) hash_index_entry(istate, ce); } +/* + * We don't actually *remove* it, we can just mark it invalid so that + * we won't find it in lookups. + * + * Not only would we have to search the lists (simple enough), but + * we'd also have to rehash other hash buckets in case this makes the + * hash bucket empty (common). So it's much better to just mark + * it. + */ +void remove_name_hash(struct index_state *istate, struct cache_entry *ce) +{ + /* if already hashed, release reference to directory entries */ + if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_HASHED) + remove_dir_entry(istate, ce); + + ce->ce_flags |= CE_UNHASHED; +} + static int slow_same_name(const char *name1, int len1, const char *name2, int len2) { if (len1 != len2) @@ -139,18 +219,7 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen if (!icase) return 0; - /* - * If the entry we're comparing is a filename (no trailing slash), then compare - * the lengths exactly. - */ - if (name[namelen - 1] != '/') - return slow_same_name(name, namelen, ce->name, len); - - /* - * For a directory, we point to an arbitrary cache_entry filename. Just - * make sure the directory portion matches. - */ - return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len); + return slow_same_name(name, namelen, ce->name, len); } struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase) @@ -166,27 +235,54 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na if (same_name(ce, name, namelen, icase)) return ce; } - if (icase && name[namelen - 1] == '/') - ce = ce->dir_next; - else - ce = ce->next; + ce = ce->next; } /* - * Might be a submodule. Despite submodules being directories, + * When looking for a directory (trailing '/'), it might be a + * submodule or a directory. Despite submodules being directories, * they are stored in the name hash without a closing slash. - * When ignore_case is 1, directories are stored in the name hash - * with their closing slash. + * When ignore_case is 1, directories are stored in a separate hash + * table *with* their closing slash. * * The side effect of this storage technique is we have need to + * lookup the directory in a separate hash table, and if not found * remove the slash from name and perform the lookup again without * the slash. If a match is made, S_ISGITLINK(ce->mode) will be * true. */ if (icase && name[namelen - 1] == '/') { + struct dir_entry *dir = find_dir_entry(istate, name, namelen); + if (dir && dir->nr) + return dir->ce; + ce = index_name_exists(istate, name, namelen - 1, icase); if (ce && S_ISGITLINK(ce->ce_mode)) return ce; } return NULL; } + +static int free_dir_entry(void *entry, void *unused) +{ + struct dir_entry *dir = entry; + while (dir) { + struct dir_entry *next = dir->next; + free(dir); + dir = next; + } + return 0; +} + +void free_name_hash(struct index_state *istate) +{ + if (!istate->name_hash_initialized) + return; + istate->name_hash_initialized = 0; + if (ignore_case) + /* free directory entries */ + for_each_hash(&istate->dir_hash, free_dir_entry, NULL); + + free_hash(&istate->name_hash); + free_hash(&istate->dir_hash); +} diff --git a/path.c b/path.c index d3d3f8b8ad..2fdccc2f18 100644 --- a/path.c +++ b/path.c @@ -14,6 +14,22 @@ #include "strbuf.h" #include "string-list.h" +#ifndef get_st_mode_bits +/* + * The replacement lstat(2) we use on Cygwin is incomplete and + * may return wrong permission bits. Most of the time we do not care, + * but the callsites of this wrapper do care. + */ +int get_st_mode_bits(const char *path, int *mode) +{ + struct stat st; + if (lstat(path, &st) < 0) + return -1; + *mode = st.st_mode; + return 0; +} +#endif + static char bad_path[] = "/bad-path/"; static char *get_pathname(void) @@ -391,7 +407,6 @@ const char *enter_repo(const char *path, int strict) int set_shared_perm(const char *path, int mode) { - struct stat st; int tweak, shared, orig_mode; if (!shared_repository) { @@ -400,9 +415,8 @@ int set_shared_perm(const char *path, int mode) return 0; } if (!mode) { - if (lstat(path, &st) < 0) + if (get_st_mode_bits(path, &mode) < 0) return -1; - mode = st.st_mode; orig_mode = mode; } else orig_mode = 0; diff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm index 049c97bfaf..6a212eb7a8 100644 --- a/perl/Git/SVN/Ra.pm +++ b/perl/Git/SVN/Ra.pm @@ -295,7 +295,7 @@ sub gs_do_switch { my $full_url = add_path_to_url( $self->url, $path ); my ($ra, $reparented); - if ($old_url =~ m#^svn(\+ssh)?://# || + if ($old_url =~ m#^svn(\+\w+)?://# || ($full_url =~ m#^https?://# && canonicalize_url($full_url) ne $full_url)) { $_[0] = undef; diff --git a/pkt-line.c b/pkt-line.c index eaba15f124..70f19501d0 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -1,6 +1,7 @@ #include "cache.h" #include "pkt-line.h" +char packet_buffer[LARGE_PACKET_MAX]; static const char *packet_trace_prefix = "git"; static const char trace_key[] = "GIT_TRACE_PACKET"; @@ -46,38 +47,6 @@ static void packet_trace(const char *buf, unsigned int len, int write) strbuf_release(&out); } -/* - * Write a packetized stream, where each line is preceded by - * its length (including the header) as a 4-byte hex number. - * A length of 'zero' means end of stream (and a length of 1-3 - * would be an error). - * - * This is all pretty stupid, but we use this packetized line - * format to make a streaming format possible without ever - * over-running the read buffers. That way we'll never read - * into what might be the pack data (which should go to another - * process entirely). - * - * The writing side could use stdio, but since the reading - * side can't, we stay with pure read/write interfaces. - */ -ssize_t safe_write(int fd, const void *buf, ssize_t n) -{ - ssize_t nn = n; - while (n) { - int ret = xwrite(fd, buf, n); - if (ret > 0) { - buf = (char *) buf + ret; - n -= ret; - continue; - } - if (!ret) - die("write error (disk full?)"); - die_errno("write error"); - } - return nn; -} - /* * If we buffered things up above (we don't, but we should), * we'd flush it here @@ -85,7 +54,7 @@ ssize_t safe_write(int fd, const void *buf, ssize_t n) void packet_flush(int fd) { packet_trace("0000", 4, 1); - safe_write(fd, "0000", 4); + write_or_die(fd, "0000", 4); } void packet_buf_flush(struct strbuf *buf) @@ -121,7 +90,7 @@ void packet_write(int fd, const char *fmt, ...) va_start(args, fmt); n = format_packet(fmt, args); va_end(args); - safe_write(fd, buffer, n); + write_or_die(fd, buffer, n); } void packet_buf_write(struct strbuf *buf, const char *fmt, ...) @@ -135,13 +104,29 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) strbuf_add(buf, buffer, n); } -static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail) +static int get_packet_data(int fd, char **src_buf, size_t *src_size, + void *dst, unsigned size, int options) { - ssize_t ret = read_in_full(fd, buffer, size); - if (ret < 0) - die_errno("read error"); - else if (ret < size) { - if (return_line_fail) + ssize_t ret; + + if (fd >= 0 && src_buf && *src_buf) + die("BUG: multiple sources given to packet_read"); + + /* Read up to "size" bytes from our source, whatever it is. */ + if (src_buf && *src_buf) { + ret = size < *src_size ? size : *src_size; + memcpy(dst, *src_buf, ret); + *src_buf += ret; + *src_size -= ret; + } else { + ret = read_in_full(fd, dst, size); + if (ret < 0) + die_errno("read error"); + } + + /* And complain if we didn't get enough bytes to satisfy the read. */ + if (ret < size) { + if (options & PACKET_READ_GENTLE_ON_EOF) return -1; die("The remote end hung up unexpectedly"); @@ -175,13 +160,14 @@ static int packet_length(const char *linelen) return len; } -static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail) +int packet_read(int fd, char **src_buf, size_t *src_len, + char *buffer, unsigned size, int options) { int len, ret; char linelen[4]; - ret = safe_read(fd, linelen, 4, return_line_fail); - if (return_line_fail && ret < 0) + ret = get_packet_data(fd, src_buf, src_len, linelen, 4, options); + if (ret < 0) return ret; len = packet_length(linelen); if (len < 0) @@ -193,50 +179,37 @@ static int packet_read_internal(int fd, char *buffer, unsigned size, int return_ len -= 4; if (len >= size) die("protocol error: bad line length %d", len); - ret = safe_read(fd, buffer, len, return_line_fail); - if (return_line_fail && ret < 0) + ret = get_packet_data(fd, src_buf, src_len, buffer, len, options); + if (ret < 0) return ret; + + if ((options & PACKET_READ_CHOMP_NEWLINE) && + len && buffer[len-1] == '\n') + len--; + buffer[len] = 0; packet_trace(buffer, len, 0); return len; } -int packet_read(int fd, char *buffer, unsigned size) +static char *packet_read_line_generic(int fd, + char **src, size_t *src_len, + int *dst_len) { - return packet_read_internal(fd, buffer, size, 1); + int len = packet_read(fd, src, src_len, + packet_buffer, sizeof(packet_buffer), + PACKET_READ_CHOMP_NEWLINE); + if (dst_len) + *dst_len = len; + return len ? packet_buffer : NULL; } -int packet_read_line(int fd, char *buffer, unsigned size) +char *packet_read_line(int fd, int *len_p) { - return packet_read_internal(fd, buffer, size, 0); + return packet_read_line_generic(fd, NULL, NULL, len_p); } -int packet_get_line(struct strbuf *out, - char **src_buf, size_t *src_len) +char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len) { - int len; - - if (*src_len < 4) - return -1; - len = packet_length(*src_buf); - if (len < 0) - return -1; - if (!len) { - *src_buf += 4; - *src_len -= 4; - packet_trace("0000", 4, 0); - return 0; - } - if (*src_len < len) - return -2; - - *src_buf += 4; - *src_len -= 4; - len -= 4; - - strbuf_add(out, *src_buf, len); - *src_buf += len; - *src_len -= len; - packet_trace(out->buf, out->len, 0); - return len; + return packet_read_line_generic(-1, src, src_len, dst_len); } diff --git a/pkt-line.h b/pkt-line.h index 8cfeb0c31c..0a838d1656 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -5,16 +5,78 @@ #include "strbuf.h" /* - * Silly packetized line writing interface + * Write a packetized stream, where each line is preceded by + * its length (including the header) as a 4-byte hex number. + * A length of 'zero' means end of stream (and a length of 1-3 + * would be an error). + * + * This is all pretty stupid, but we use this packetized line + * format to make a streaming format possible without ever + * over-running the read buffers. That way we'll never read + * into what might be the pack data (which should go to another + * process entirely). + * + * The writing side could use stdio, but since the reading + * side can't, we stay with pure read/write interfaces. */ void packet_flush(int fd); void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); -int packet_read_line(int fd, char *buffer, unsigned size); -int packet_read(int fd, char *buffer, unsigned size); -int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); -ssize_t safe_write(int, const void *, ssize_t); +/* + * Read a packetized line into the buffer, which must be at least size bytes + * long. The return value specifies the number of bytes read into the buffer. + * + * If src_buffer is not NULL (and nor is *src_buffer), it should point to a + * buffer containing the packet data to parse, of at least *src_len bytes. + * After the function returns, src_buf will be incremented and src_len + * decremented by the number of bytes consumed. + * + * If src_buffer (or *src_buffer) is NULL, then data is read from the + * descriptor "fd". + * + * If options does not contain PACKET_READ_GENTLE_ON_EOF, we will die under any + * of the following conditions: + * + * 1. Read error from descriptor. + * + * 2. Protocol error from the remote (e.g., bogus length characters). + * + * 3. Receiving a packet larger than "size" bytes. + * + * 4. Truncated output from the remote (e.g., we expected a packet but got + * EOF, or we got a partial packet followed by EOF). + * + * If options does contain PACKET_READ_GENTLE_ON_EOF, we will not die on + * condition 4 (truncated input), but instead return -1. However, we will still + * die for the other 3 conditions. + * + * If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if + * present) is removed from the buffer before returning. + */ +#define PACKET_READ_GENTLE_ON_EOF (1u<<0) +#define PACKET_READ_CHOMP_NEWLINE (1u<<1) +int packet_read(int fd, char **src_buffer, size_t *src_len, char + *buffer, unsigned size, int options); + +/* + * Convenience wrapper for packet_read that is not gentle, and sets the + * CHOMP_NEWLINE option. The return value is NULL for a flush packet, + * and otherwise points to a static buffer (that may be overwritten by + * subsequent calls). If the size parameter is not NULL, the length of the + * packet is written to it. + */ +char *packet_read_line(int fd, int *size); + +/* + * Same as packet_read_line, but read from a buf rather than a descriptor; + * see packet_read for details on how src_* is used. + */ +char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size); + +#define DEFAULT_PACKET_MAX 1000 +#define LARGE_PACKET_MAX 65520 +extern char packet_buffer[LARGE_PACKET_MAX]; #endif diff --git a/read-cache.c b/read-cache.c index 670a06bc79..5a9704f4e5 100644 --- a/read-cache.c +++ b/read-cache.c @@ -46,7 +46,7 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache { struct cache_entry *old = istate->cache[nr]; - remove_name_hash(old); + remove_name_hash(istate, old); set_index_entry(istate, nr, ce); istate->cache_changed = 1; } @@ -460,7 +460,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); + remove_name_hash(istate, ce); istate->cache_changed = 1; istate->cache_nr--; if (pos >= istate->cache_nr) @@ -483,7 +483,7 @@ void remove_marked_cache_entries(struct index_state *istate) for (i = j = 0; i < istate->cache_nr; i++) { if (ce_array[i]->ce_flags & CE_REMOVE) - remove_name_hash(ce_array[i]); + remove_name_hash(istate, ce_array[i]); else ce_array[j++] = ce_array[i]; } @@ -1515,8 +1515,7 @@ int discard_index(struct index_state *istate) istate->cache_changed = 0; istate->timestamp.sec = 0; istate->timestamp.nsec = 0; - istate->name_hash_initialized = 0; - free_hash(&istate->name_hash); + free_name_hash(istate); cache_tree_free(&(istate->cache_tree)); istate->initialized = 0; diff --git a/refs.c b/refs.c index e2b760d0ba..de2d8eb866 100644 --- a/refs.c +++ b/refs.c @@ -2332,59 +2332,117 @@ int read_ref_at(const char *refname, unsigned long at_time, int cnt, return 1; } -int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data) +static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) +{ + unsigned char osha1[20], nsha1[20]; + char *email_end, *message; + unsigned long timestamp; + int tz; + + /* old SP new SP name SP time TAB msg LF */ + if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' || + get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' || + get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' || + !(email_end = strchr(sb->buf + 82, '>')) || + email_end[1] != ' ' || + !(timestamp = strtoul(email_end + 2, &message, 10)) || + !message || message[0] != ' ' || + (message[1] != '+' && message[1] != '-') || + !isdigit(message[2]) || !isdigit(message[3]) || + !isdigit(message[4]) || !isdigit(message[5])) + return 0; /* corrupt? */ + email_end[1] = '\0'; + tz = strtol(message + 1, NULL, 10); + if (message[6] != '\t') + message += 6; + else + message += 7; + return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data); +} + +static char *find_beginning_of_line(char *bob, char *scan) +{ + while (bob < scan && *(--scan) != '\n') + ; /* keep scanning backwards */ + /* + * Return either beginning of the buffer, or LF at the end of + * the previous line. + */ + return scan; +} + +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data) { - const char *logfile; - FILE *logfp; struct strbuf sb = STRBUF_INIT; - int ret = 0; + FILE *logfp; + long pos; + int ret = 0, at_tail = 1; - logfile = git_path("logs/%s", refname); - logfp = fopen(logfile, "r"); + logfp = fopen(git_path("logs/%s", refname), "r"); if (!logfp) return -1; - if (ofs) { - struct stat statbuf; - if (fstat(fileno(logfp), &statbuf) || - statbuf.st_size < ofs || - fseek(logfp, -ofs, SEEK_END) || - strbuf_getwholeline(&sb, logfp, '\n')) { - fclose(logfp); - strbuf_release(&sb); - return -1; + /* Jump to the end */ + if (fseek(logfp, 0, SEEK_END) < 0) + return error("cannot seek back reflog for %s: %s", + refname, strerror(errno)); + pos = ftell(logfp); + while (!ret && 0 < pos) { + int cnt; + size_t nread; + char buf[BUFSIZ]; + char *endp, *scanp; + + /* Fill next block from the end */ + cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos; + if (fseek(logfp, pos - cnt, SEEK_SET)) + return error("cannot seek back reflog for %s: %s", + refname, strerror(errno)); + nread = fread(buf, cnt, 1, logfp); + if (nread != 1) + return error("cannot read %d bytes from reflog for %s: %s", + cnt, refname, strerror(errno)); + pos -= cnt; + + scanp = endp = buf + cnt; + if (at_tail && scanp[-1] == '\n') + /* Looking at the final LF at the end of the file */ + scanp--; + at_tail = 0; + + while (buf < scanp) { + /* + * terminating LF of the previous line, or the beginning + * of the buffer. + */ + char *bp; + + bp = find_beginning_of_line(buf, scanp); + + if (*bp != '\n') { + strbuf_splice(&sb, 0, 0, buf, endp - buf); + if (pos) + break; /* need to fill another block */ + scanp = buf - 1; /* leave loop */ + } else { + /* + * (bp + 1) thru endp is the beginning of the + * current line we have in sb + */ + strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1)); + scanp = bp; + endp = bp + 1; + } + ret = show_one_reflog_ent(&sb, fn, cb_data); + strbuf_reset(&sb); + if (ret) + break; } - } - while (!strbuf_getwholeline(&sb, logfp, '\n')) { - unsigned char osha1[20], nsha1[20]; - char *email_end, *message; - unsigned long timestamp; - int tz; - - /* old SP new SP name SP time TAB msg LF */ - if (sb.len < 83 || sb.buf[sb.len - 1] != '\n' || - get_sha1_hex(sb.buf, osha1) || sb.buf[40] != ' ' || - get_sha1_hex(sb.buf + 41, nsha1) || sb.buf[81] != ' ' || - !(email_end = strchr(sb.buf + 82, '>')) || - email_end[1] != ' ' || - !(timestamp = strtoul(email_end + 2, &message, 10)) || - !message || message[0] != ' ' || - (message[1] != '+' && message[1] != '-') || - !isdigit(message[2]) || !isdigit(message[3]) || - !isdigit(message[4]) || !isdigit(message[5])) - continue; /* corrupt? */ - email_end[1] = '\0'; - tz = strtol(message + 1, NULL, 10); - if (message[6] != '\t') - message += 6; - else - message += 7; - ret = fn(osha1, nsha1, sb.buf + 82, timestamp, tz, message, - cb_data); - if (ret) - break; } + if (!ret && sb.len) + ret = show_one_reflog_ent(&sb, fn, cb_data); + fclose(logfp); strbuf_release(&sb); return ret; @@ -2392,9 +2450,20 @@ int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data) { - return for_each_recent_reflog_ent(refname, fn, 0, cb_data); -} + FILE *logfp; + struct strbuf sb = STRBUF_INIT; + int ret = 0; + + logfp = fopen(git_path("logs/%s", refname), "r"); + if (!logfp) + return -1; + while (!ret && !strbuf_getwholeline(&sb, logfp, '\n')) + ret = show_one_reflog_ent(&sb, fn, cb_data); + fclose(logfp); + strbuf_release(&sb); + return ret; +} /* * Call fn for each reflog in the namespace indicated by name. name * must be empty or end with '/'. Name will be used as a scratch diff --git a/refs.h b/refs.h index 1b2e2d3a98..a35eafc4ee 100644 --- a/refs.h +++ b/refs.h @@ -103,7 +103,7 @@ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, /* iterate over reflog entries */ typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data); -int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data); +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data); /* * Calls the specified function for each reflog file until it returns nonzero, diff --git a/remote-curl.c b/remote-curl.c index 933c69ac26..93a09a64c3 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -76,21 +76,82 @@ struct discovery { char *buf_alloc; char *buf; size_t len; + struct ref *refs; unsigned proto_git : 1; }; static struct discovery *last_discovery; +static struct ref *parse_git_refs(struct discovery *heads, int for_push) +{ + struct ref *list = NULL; + get_remote_heads(-1, heads->buf, heads->len, &list, + for_push ? REF_NORMAL : 0, NULL); + return list; +} + +static struct ref *parse_info_refs(struct discovery *heads) +{ + char *data, *start, *mid; + char *ref_name; + int i = 0; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = heads->buf; + start = NULL; + mid = data; + while (i < heads->len) { + if (!start) { + start = &data[i]; + } + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + if (mid - start != 40) + die("%sinfo/refs not valid: is this a git repository?", url); + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + ref = alloc_ref("HEAD"); + if (!http_fetch_ref(url, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + + return refs; +} + static void free_discovery(struct discovery *d) { if (d) { if (d == last_discovery) last_discovery = NULL; free(d->buf_alloc); + free_refs(d->refs); free(d); } } -static struct discovery* discover_refs(const char *service) +static struct discovery* discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; struct strbuf type = STRBUF_INIT; @@ -138,32 +199,35 @@ static struct discovery* discover_refs(const char *service) if (maybe_smart && (5 <= last->len && last->buf[4] == '#') && !strbuf_cmp(&exp, &type)) { + char *line; + /* * smart HTTP response; validate that the service * pkt-line matches our request. */ - if (packet_get_line(&buffer, &last->buf, &last->len) <= 0) - die("%s has invalid packet header", refs_url); - if (buffer.len && buffer.buf[buffer.len - 1] == '\n') - strbuf_setlen(&buffer, buffer.len - 1); + line = packet_read_line_buf(&last->buf, &last->len, NULL); strbuf_reset(&exp); strbuf_addf(&exp, "# service=%s", service); - if (strbuf_cmp(&exp, &buffer)) - die("invalid server response; got '%s'", buffer.buf); + if (strcmp(line, exp.buf)) + die("invalid server response; got '%s'", line); strbuf_release(&exp); /* The header can include additional metadata lines, up * until a packet flush marker. Ignore these now, but * in the future we might start to scan them. */ - strbuf_reset(&buffer); - while (packet_get_line(&buffer, &last->buf, &last->len) > 0) - strbuf_reset(&buffer); + while (packet_read_line_buf(&last->buf, &last->len, NULL)) + ; last->proto_git = 1; } + if (last->proto_git) + last->refs = parse_git_refs(last, for_push); + else + last->refs = parse_info_refs(last); + free(refs_url); strbuf_release(&exp); strbuf_release(&type); @@ -172,99 +236,16 @@ static struct discovery* discover_refs(const char *service) return last; } -static int write_discovery(int in, int out, void *data) -{ - struct discovery *heads = data; - int err = 0; - if (write_in_full(out, heads->buf, heads->len) != heads->len) - err = 1; - close(out); - return err; -} - -static struct ref *parse_git_refs(struct discovery *heads, int for_push) -{ - struct ref *list = NULL; - struct async async; - - memset(&async, 0, sizeof(async)); - async.proc = write_discovery; - async.data = heads; - async.out = -1; - - if (start_async(&async)) - die("cannot start thread to parse advertised refs"); - get_remote_heads(async.out, &list, - for_push ? REF_NORMAL : 0, NULL); - close(async.out); - if (finish_async(&async)) - die("ref parsing thread failed"); - return list; -} - -static struct ref *parse_info_refs(struct discovery *heads) -{ - char *data, *start, *mid; - char *ref_name; - int i = 0; - - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; - - data = heads->buf; - start = NULL; - mid = data; - while (i < heads->len) { - if (!start) { - start = &data[i]; - } - if (data[i] == '\t') - mid = &data[i]; - if (data[i] == '\n') { - if (mid - start != 40) - die("%sinfo/refs not valid: is this a git repository?", url); - data[i] = 0; - ref_name = mid + 1; - ref = xmalloc(sizeof(struct ref) + - strlen(ref_name) + 1); - memset(ref, 0, sizeof(struct ref)); - strcpy(ref->name, ref_name); - get_sha1_hex(start, ref->old_sha1); - if (!refs) - refs = ref; - if (last_ref) - last_ref->next = ref; - last_ref = ref; - start = NULL; - } - i++; - } - - ref = alloc_ref("HEAD"); - if (!http_fetch_ref(url, ref) && - !resolve_remote_symref(ref, refs)) { - ref->next = refs; - refs = ref; - } else { - free(ref); - } - - return refs; -} - static struct ref *get_refs(int for_push) { struct discovery *heads; if (for_push) - heads = discover_refs("git-receive-pack"); + heads = discover_refs("git-receive-pack", for_push); else - heads = discover_refs("git-upload-pack"); + heads = discover_refs("git-upload-pack", for_push); - if (heads->proto_git) - return parse_git_refs(heads, for_push); - return parse_info_refs(heads); + return heads->refs; } static void output_refs(struct ref *refs) @@ -278,7 +259,6 @@ static void output_refs(struct ref *refs) } printf("\n"); fflush(stdout); - free_refs(refs); } struct rpc_state { @@ -308,7 +288,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, if (!avail) { rpc->initial_buffer = 0; - avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!avail) return 0; rpc->pos = 0; @@ -425,7 +405,7 @@ static int post_rpc(struct rpc_state *rpc) break; } - n = packet_read_line(rpc->out, buf, left); + n = packet_read(rpc->out, NULL, NULL, buf, left, 0); if (!n) break; rpc->len += n; @@ -579,7 +559,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->hdr_accept = strbuf_detach(&buf, NULL); while (!err) { - int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!n) break; rpc->pos = 0; @@ -685,7 +665,7 @@ static int fetch_git(struct discovery *heads, err = rpc_service(&rpc, heads); if (rpc.result.len) - safe_write(1, rpc.result.buf, rpc.result.len); + write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); strbuf_release(&preamble); free(depth_arg); @@ -694,7 +674,7 @@ static int fetch_git(struct discovery *heads, static int fetch(int nr_heads, struct ref **to_fetch) { - struct discovery *d = discover_refs("git-upload-pack"); + struct discovery *d = discover_refs("git-upload-pack", 0); if (d->proto_git) return fetch_git(d, nr_heads, to_fetch); else @@ -805,7 +785,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) err = rpc_service(&rpc, heads); if (rpc.result.len) - safe_write(1, rpc.result.buf, rpc.result.len); + write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); free(argv); return err; @@ -813,7 +793,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) static int push(int nr_spec, char **specs) { - struct discovery *heads = discover_refs("git-receive-pack"); + struct discovery *heads = discover_refs("git-receive-pack", 1); int ret; if (heads->proto_git) diff --git a/remote.c b/remote.c index 57f36e14da..ca1edd901e 100644 --- a/remote.c +++ b/remote.c @@ -1322,9 +1322,6 @@ int match_push_refs(struct ref *src, struct ref **dst, const struct refspec *pat = NULL; char *dst_name; - if (ref->peer_ref) - continue; - dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat); if (!dst_name) continue; diff --git a/resolve-undo.c b/resolve-undo.c index 72b46125b7..639eb9c59f 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -118,7 +118,7 @@ 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; + int i, err = 0, matched; if (!istate->resolve_undo) return pos; @@ -137,6 +137,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) ru = item->util; if (!ru) return pos; + matched = ce->ce_flags & CE_MATCHED; remove_index_entry_at(istate, pos); for (i = 0; i < 3; i++) { struct cache_entry *nce; @@ -144,6 +145,8 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) continue; nce = make_cache_entry(ru->mode[i], ru->sha1[i], ce->name, i + 1, 0); + if (matched) + nce->ce_flags |= CE_MATCHED; if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) { err = 1; error("cannot unmerge '%s'", ce->name); @@ -156,6 +159,20 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) return unmerge_index_entry_at(istate, pos); } +void unmerge_marked_index(struct index_state *istate) +{ + int i; + + if (!istate->resolve_undo) + return; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (ce->ce_flags & CE_MATCHED) + i = unmerge_index_entry_at(istate, i); + } +} + void unmerge_index(struct index_state *istate, const char **pathspec) { int i; diff --git a/resolve-undo.h b/resolve-undo.h index 845876911d..7a30206aad 100644 --- a/resolve-undo.h +++ b/resolve-undo.h @@ -12,5 +12,6 @@ extern struct string_list *resolve_undo_read(const char *, 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 **); +extern void unmerge_marked_index(struct index_state *); #endif diff --git a/revision.c b/revision.c index ef60205412..71e62d8312 100644 --- a/revision.c +++ b/revision.c @@ -709,7 +709,7 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl * Does the destination list contain entries with a date * before the source list? Definitely _not_ done. */ - if (date < src->item->date) + if (date <= src->item->date) return SLOP; /* @@ -1970,6 +1970,22 @@ static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs, return st; } +static void remove_treesame_parents(struct commit *commit) +{ + struct commit_list **pp, *p; + + pp = &commit->parents; + while ((p = *pp) != NULL) { + struct commit *parent = p->item; + if (parent->object.flags & TREESAME) { + *pp = p->next; + free(p); + continue; + } + pp = &p->next; + } +} + static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail) { struct commit_list *p; @@ -2022,10 +2038,18 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c if (revs->first_parent_only) break; } - if (!revs->first_parent_only) - cnt = remove_duplicate_parents(commit); - else + + if (revs->first_parent_only) { cnt = 1; + } else { + /* + * A merge with a tree-same parent is useless + */ + if (commit->parents && commit->parents->next) + remove_treesame_parents(commit); + + cnt = remove_duplicate_parents(commit); + } /* * It is possible that we are a merge and one side branch diff --git a/revision.h b/revision.h index 5da09ee3ef..01bd2b7c07 100644 --- a/revision.h +++ b/revision.h @@ -138,7 +138,7 @@ struct rev_info { int reroll_count; char *message_id; struct string_list *ref_message_ids; - const char *add_signoff; + int add_signoff; const char *extra_headers; const char *log_reencode; const char *subject_prefix; diff --git a/send-pack.c b/send-pack.c index 97ab336097..7d172ef37f 100644 --- a/send-pack.c +++ b/send-pack.c @@ -106,15 +106,11 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext static int receive_status(int in, struct ref *refs) { struct ref *hint; - char line[1000]; int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) + char *line = packet_read_line(in, NULL); + if (prefixcmp(line, "unpack ")) return error("did not receive remote status"); - if (memcmp(line, "unpack ok\n", 10)) { - char *p = line + strlen(line) - 1; - if (*p == '\n') - *p = '\0'; + if (strcmp(line, "unpack ok")) { error("unpack failed: %s", line + 7); ret = -1; } @@ -122,17 +118,15 @@ static int receive_status(int in, struct ref *refs) while (1) { char *refname; char *msg; - len = packet_read_line(in, line, sizeof(line)); - if (!len) + line = packet_read_line(in, NULL); + if (!line) break; - if (len < 3 || - (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { - fprintf(stderr, "protocol error: %s\n", line); + if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) { + error("invalid ref status from remote: %s", line); ret = -1; break; } - line[strlen(line)-1] = '\0'; refname = line + 3; msg = strchr(refname, ' '); if (msg) @@ -281,7 +275,7 @@ int send_pack(struct send_pack_args *args, send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); } } else { - safe_write(out, req_buf.buf, req_buf.len); + write_or_die(out, req_buf.buf, req_buf.len); packet_flush(out); } strbuf_release(&req_buf); diff --git a/sequencer.c b/sequencer.c index aef5e8a017..baa031052e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -18,6 +18,89 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" const char sign_off_header[] = "Signed-off-by: "; +static const char cherry_picked_prefix[] = "(cherry picked from commit "; + +static int is_rfc2822_line(const char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int ch = buf[i]; + if (ch == ':') + return 1; + if (!isalnum(ch) && ch != '-') + break; + } + + return 0; +} + +static int is_cherry_picked_from_line(const char *buf, int len) +{ + /* + * We only care that it looks roughly like (cherry picked from ...) + */ + return len > strlen(cherry_picked_prefix) + 1 && + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; +} + +/* + * Returns 0 for non-conforming footer + * Returns 1 for conforming footer + * Returns 2 when sob exists within conforming footer + * Returns 3 when sob exists within conforming footer as last entry + */ +static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, + int ignore_footer) +{ + char prev; + int i, k; + int len = sb->len - ignore_footer; + const char *buf = sb->buf; + int found_sob = 0; + + /* footer must end with newline */ + if (!len || buf[len - 1] != '\n') + return 0; + + prev = '\0'; + for (i = len - 1; i > 0; i--) { + char ch = buf[i]; + if (prev == '\n' && ch == '\n') /* paragraph break */ + break; + prev = ch; + } + + /* require at least one blank line */ + if (prev != '\n' || buf[i] != '\n') + return 0; + + /* advance to start of last paragraph */ + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + int found_rfc2822; + + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1); + if (found_rfc2822 && sob && + !strncmp(buf + i, sob->buf, sob->len)) + found_sob = k; + + if (!(found_rfc2822 || + is_cherry_picked_from_line(buf + i, k - i - 1))) + return 0; + } + if (found_sob == i) + return 3; + if (found_sob) + return 2; + return 1; +} static void remove_sequencer_state(void) { @@ -237,7 +320,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, rollback_lock_file(&index_lock); if (opts->signoff) - append_signoff(msgbuf, 0); + append_signoff(msgbuf, 0, 0); if (!clean) { int i; @@ -496,7 +579,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } if (opts->record_origin) { - strbuf_addstr(&msgbuf, "(cherry picked from commit "); + if (!has_conforming_footer(&msgbuf, NULL, 0)) + strbuf_addch(&msgbuf, '\n'); + strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } @@ -1021,62 +1106,67 @@ int sequencer_pick_revisions(struct replay_opts *opts) return pick_commits(todo_list, opts); } -static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len - ignore_footer; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; -} - -void append_signoff(struct strbuf *msgbuf, int ignore_footer) +void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) { + unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; struct strbuf sob = STRBUF_INIT; - int i; + int has_footer; strbuf_addstr(&sob, sign_off_header); strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"))); strbuf_addch(&sob, '\n'); - for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(msgbuf->buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer)) - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); + + /* + * If the whole message buffer is equal to the sob, pretend that we + * found a conforming footer with a matching sob + */ + if (msgbuf->len - ignore_footer == sob.len && + !strncmp(msgbuf->buf, sob.buf, sob.len)) + has_footer = 3; + else + has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer); + + if (!has_footer) { + const char *append_newlines = NULL; + size_t len = msgbuf->len - ignore_footer; + + if (!len) { + /* + * The buffer is completely empty. Leave foom for + * the title and body to be filled in by the user. + */ + append_newlines = "\n\n"; + } else if (msgbuf->buf[len - 1] != '\n') { + /* + * Incomplete line. Complete the line and add a + * blank one so that there is an empty line between + * the message body and the sob. + */ + append_newlines = "\n\n"; + } else if (len == 1) { + /* + * Buffer contains a single newline. Add another + * so that we leave room for the title and body. + */ + append_newlines = "\n"; + } else if (msgbuf->buf[len - 2] != '\n') { + /* + * Buffer ends with a single newline. Add another + * so that there is an empty line between the message + * body and the sob. + */ + append_newlines = "\n"; + } /* else, the buffer already ends with two newlines. */ + + if (append_newlines) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + append_newlines, strlen(append_newlines)); } + + if (has_footer != 3 && (!no_dup_sob || has_footer != 2)) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + sob.buf, sob.len); + strbuf_release(&sob); } diff --git a/sequencer.h b/sequencer.h index 9d57d57524..1fc22dcabe 100644 --- a/sequencer.h +++ b/sequencer.h @@ -6,6 +6,8 @@ #define SEQ_TODO_FILE "sequencer/todo" #define SEQ_OPTS_FILE "sequencer/opts" +#define APPEND_SIGNOFF_DEDUP (1u << 0) + enum replay_action { REPLAY_REVERT, REPLAY_PICK @@ -48,6 +50,6 @@ int sequencer_pick_revisions(struct replay_opts *opts); extern const char sign_off_header[]; -void append_signoff(struct strbuf *msgbuf, int ignore_footer); +void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); #endif diff --git a/sha1_file.c b/sha1_file.c index 0b99f336e6..0ed23981b3 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -124,8 +124,13 @@ int safe_create_leading_directories(char *path) } } else if (mkdir(path, 0777)) { - *pos = '/'; - return -1; + if (errno == EEXIST && + !stat(path, &st) && S_ISDIR(st.st_mode)) { + ; /* somebody created it since we checked */ + } else { + *pos = '/'; + return -1; + } } else if (adjust_shared_perm(path)) { *pos = '/'; diff --git a/sha1_name.c b/sha1_name.c index c50630a3ea..2fbda48e02 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -856,8 +856,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1, } struct grab_nth_branch_switch_cbdata { - long cnt, alloc; - struct strbuf *buf; + int remaining; + struct strbuf buf; }; static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, @@ -867,7 +867,6 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, struct grab_nth_branch_switch_cbdata *cb = cb_data; const char *match = NULL, *target = NULL; size_t len; - int nth; if (!prefixcmp(message, "checkout: moving from ")) { match = message + strlen("checkout: moving from "); @@ -876,11 +875,12 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, if (!match || !target) return 0; - - len = target - match; - nth = cb->cnt++ % cb->alloc; - strbuf_reset(&cb->buf[nth]); - strbuf_add(&cb->buf[nth], match, len); + if (--(cb->remaining) == 0) { + len = target - match; + strbuf_reset(&cb->buf); + strbuf_add(&cb->buf, match, len); + return 1; /* we are done */ + } return 0; } @@ -891,7 +891,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf) { long nth; - int i, retval; + int retval; struct grab_nth_branch_switch_cbdata cb; const char *brace; char *num_end; @@ -901,34 +901,22 @@ static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf) brace = strchr(name, '}'); if (!brace) return -1; - nth = strtol(name+3, &num_end, 10); + nth = strtol(name + 3, &num_end, 10); if (num_end != brace) return -1; if (nth <= 0) return -1; - cb.alloc = nth; - cb.buf = xmalloc(nth * sizeof(struct strbuf)); - for (i = 0; i < nth; i++) - strbuf_init(&cb.buf[i], 20); - cb.cnt = 0; + cb.remaining = nth; + strbuf_init(&cb.buf, 20); + retval = 0; - for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb); - if (cb.cnt < nth) { - cb.cnt = 0; - for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) { + strbuf_reset(buf); + strbuf_add(buf, cb.buf.buf, cb.buf.len); + retval = brace - name + 1; } - if (cb.cnt < nth) - goto release_return; - i = cb.cnt % nth; - strbuf_reset(buf); - strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len); - retval = brace-name+1; - -release_return: - for (i = 0; i < nth; i++) - strbuf_release(&cb.buf[i]); - free(cb.buf); + strbuf_release(&cb.buf); return retval; } diff --git a/sideband.c b/sideband.c index d5ffa1c891..d1125f5c52 100644 --- a/sideband.c +++ b/sideband.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "pkt-line.h" #include "sideband.h" @@ -37,7 +38,7 @@ int recv_sideband(const char *me, int in_stream, int out) while (1) { int band, len; - len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX); + len = packet_read(in_stream, NULL, NULL, buf + pf, LARGE_PACKET_MAX, 0); if (len == 0) break; if (len < 1) { @@ -108,7 +109,7 @@ int recv_sideband(const char *me, int in_stream, int out) } while (len); continue; case 1: - safe_write(out, buf + pf+1, len); + write_or_die(out, buf + pf+1, len); continue; default: fprintf(stderr, "%s: protocol error: bad band #%d\n", @@ -138,12 +139,12 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet if (0 <= band) { sprintf(hdr, "%04x", n + 5); hdr[4] = band; - safe_write(fd, hdr, 5); + write_or_die(fd, hdr, 5); } else { sprintf(hdr, "%04x", n + 4); - safe_write(fd, hdr, 4); + write_or_die(fd, hdr, 4); } - safe_write(fd, p, n); + write_or_die(fd, p, n); p += n; sz -= n; } diff --git a/sideband.h b/sideband.h index d72db35d1e..e46bed0b01 100644 --- a/sideband.h +++ b/sideband.h @@ -4,9 +4,6 @@ #define SIDEBAND_PROTOCOL_ERROR -2 #define SIDEBAND_REMOTE_ERROR -1 -#define DEFAULT_PACKET_MAX 1000 -#define LARGE_PACKET_MAX 65520 - int recv_sideband(const char *me, int in_stream, int out); ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); diff --git a/submodule.c b/submodule.c index 9ba1496543..975bc87e48 100644 --- a/submodule.c +++ b/submodule.c @@ -261,7 +261,7 @@ void show_submodule_summary(FILE *f, const char *path, const char *del, const char *add, const char *reset) { struct rev_info rev; - struct commit *left = left, *right = right; + struct commit *left = NULL, *right = NULL; const char *message = NULL; struct strbuf sb = STRBUF_INIT; int fast_forward = 0, fast_backward = 0; @@ -275,10 +275,8 @@ void show_submodule_summary(FILE *f, const char *path, else if (!(left = lookup_commit_reference(one)) || !(right = lookup_commit_reference(two))) message = "(commits not present)"; - - if (!message && - prepare_submodule_summary(&rev, path, left, right, - &fast_forward, &fast_backward)) + else if (prepare_submodule_summary(&rev, path, left, right, + &fast_forward, &fast_backward)) message = "(revision walker failed)"; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) @@ -302,11 +300,12 @@ void show_submodule_summary(FILE *f, const char *path, strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset); fwrite(sb.buf, sb.len, 1, f); - if (!message) { + if (!message) /* only NULL if we succeeded in setting up the walk */ print_submodule_summary(&rev, f, del, add, reset); + if (left) clear_commit_marks(left, ~0); + if (right) clear_commit_marks(right, ~0); - } strbuf_release(&sb); } diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3c96fda548..c4a7d84f46 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1087,4 +1087,39 @@ test_expect_success 'barf on incomplete string' ' grep " line 3 " error ' +# good section hygiene +test_expect_failure 'unsetting the last key in a section removes header' ' + cat >.git/config <<-\EOF && + # some generic comment on the configuration file itself + # a comment specific to this "section" section. + [section] + # some intervening lines + # that should also be dropped + + key = value + # please be careful when you update the above variable + EOF + + cat >expect <<-\EOF && + # some generic comment on the configuration file itself + EOF + + git config --unset section.key && + test_cmp expect .git/config +' + +test_expect_failure 'adding a key into an empty section reuses header' ' + cat >.git/config <<-\EOF && + [section] + EOF + + q_to_tab >expect <<-\EOF && + [section] + Qkey = value + EOF + + git config section.key value + test_cmp expect .git/config +' + test_done diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh index 02a4fc5d36..ff163cf675 100755 --- a/t/t2003-checkout-cache-mkdir.sh +++ b/t/t2003-checkout-cache-mkdir.sh @@ -12,85 +12,108 @@ the GIT controlled paths. . ./test-lib.sh -test_expect_success \ - 'setup' \ - 'mkdir path1 && - echo frotz >path0 && - echo rezrov >path1/file1 && - git update-index --add path0 path1/file1' +test_expect_success 'setup' ' + mkdir path1 && + echo frotz >path0 && + echo rezrov >path1/file1 && + git update-index --add path0 path1/file1 +' + +test_expect_success SYMLINKS 'have symlink in place where dir is expected.' ' + rm -fr path0 path1 && + mkdir path2 && + ln -s path2 path1 && + git checkout-index -f -a && + test ! -h path1 && test -d path1 && + test -f path1/file1 && test ! -f path2/file1 +' -test_expect_success SYMLINKS \ - 'have symlink in place where dir is expected.' \ - 'rm -fr path0 path1 && - mkdir path2 && - ln -s path2 path1 && - git checkout-index -f -a && - test ! -h path1 && test -d path1 && - test -f path1/file1 && test ! -f path2/file1' +test_expect_success 'use --prefix=path2/' ' + rm -fr path0 path1 path2 && + mkdir path2 && + git checkout-index --prefix=path2/ -f -a && + test -f path2/path0 && + test -f path2/path1/file1 && + test ! -f path0 && + test ! -f path1/file1 +' + +test_expect_success 'use --prefix=tmp-' ' + rm -fr path0 path1 path2 tmp* && + git checkout-index --prefix=tmp- -f -a && + test -f tmp-path0 && + test -f tmp-path1/file1 && + test ! -f path0 && + test ! -f path1/file1 +' -test_expect_success \ - 'use --prefix=path2/' \ - 'rm -fr path0 path1 path2 && - mkdir path2 && - git checkout-index --prefix=path2/ -f -a && - test -f path2/path0 && - test -f path2/path1/file1 && - test ! -f path0 && - test ! -f path1/file1' +test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' ' + rm -fr path0 path1 path2 tmp* && + echo nitfol >tmp-path1 && + mkdir tmp-path0 && + git checkout-index --prefix=tmp- -f -a && + test -f tmp-path0 && + test -f tmp-path1/file1 && + test ! -f path0 && + test ! -f path1/file1 +' -test_expect_success \ - 'use --prefix=tmp-' \ - 'rm -fr path0 path1 path2 tmp* && - git checkout-index --prefix=tmp- -f -a && - test -f tmp-path0 && - test -f tmp-path1/file1 && - test ! -f path0 && - test ! -f path1/file1' +test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' ' + rm -fr path0 path1 path2 tmp* && + mkdir tmp1 tmp1/orary && + ln -s tmp1 tmp && + git checkout-index --prefix=tmp/orary/ -f -a && + test -d tmp1/orary && + test -f tmp1/orary/path0 && + test -f tmp1/orary/path1/file1 && + test -h tmp +' -test_expect_success \ - 'use --prefix=tmp- but with a conflicting file and dir' \ - 'rm -fr path0 path1 path2 tmp* && - echo nitfol >tmp-path1 && - mkdir tmp-path0 && - git checkout-index --prefix=tmp- -f -a && - test -f tmp-path0 && - test -f tmp-path1/file1 && - test ! -f path0 && - test ! -f path1/file1' +test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' ' + rm -fr path0 path1 path2 tmp* && + mkdir tmp1 && + ln -s tmp1 tmp && + git checkout-index --prefix=tmp/orary- -f -a && + test -f tmp1/orary-path0 && + test -f tmp1/orary-path1/file1 && + test -h tmp +' -# Linus fix #1 -test_expect_success SYMLINKS \ - 'use --prefix=tmp/orary/ where tmp is a symlink' \ - 'rm -fr path0 path1 path2 tmp* && - mkdir tmp1 tmp1/orary && - ln -s tmp1 tmp && - git checkout-index --prefix=tmp/orary/ -f -a && - test -d tmp1/orary && - test -f tmp1/orary/path0 && - test -f tmp1/orary/path1/file1 && - test -h tmp' +test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' ' + rm -fr path0 path1 path2 tmp* && + mkdir tmp1 && + ln -s tmp1 tmp-path1 && + git checkout-index --prefix=tmp- -f -a && + test -f tmp-path0 && + test ! -h tmp-path1 && + test -d tmp-path1 && + test -f tmp-path1/file1 +' -# Linus fix #2 -test_expect_success SYMLINKS \ - 'use --prefix=tmp/orary- where tmp is a symlink' \ - 'rm -fr path0 path1 path2 tmp* && - mkdir tmp1 && - ln -s tmp1 tmp && - git checkout-index --prefix=tmp/orary- -f -a && - test -f tmp1/orary-path0 && - test -f tmp1/orary-path1/file1 && - test -h tmp' +test_expect_success 'apply filter from working tree .gitattributes with --prefix' ' + rm -fr path0 path1 path2 tmp* && + mkdir path1 && + mkdir tmp && + git config filter.replace-all.smudge "sed -e s/./,/g" && + git config filter.replace-all.clean cat && + git config filter.replace-all.required true && + echo "file1 filter=replace-all" >path1/.gitattributes && + git checkout-index --prefix=tmp/ -f -a && + echo frotz >expected && + test_cmp expected tmp/path0 && + echo ,,,,,, >expected && + test_cmp expected tmp/path1/file1 +' -# Linus fix #3 -test_expect_success SYMLINKS \ - 'use --prefix=tmp- where tmp-path1 is a symlink' \ - 'rm -fr path0 path1 path2 tmp* && - mkdir tmp1 && - ln -s tmp1 tmp-path1 && - git checkout-index --prefix=tmp- -f -a && - test -f tmp-path0 && - test ! -h tmp-path1 && - test -d tmp-path1 && - test -f tmp-path1/file1' +test_expect_success 'apply CRLF filter from working tree .gitattributes with --prefix' ' + rm -fr path0 path1 path2 tmp* && + mkdir path1 && + mkdir tmp && + echo "file1 eol=crlf" >path1/.gitattributes && + git checkout-index --prefix=tmp/ -f -a && + echo rezrovQ >expected && + tr \\015 Q actual && + test_cmp expected actual +' test_done diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh index 56090d2eba..8e3545d868 100755 --- a/t/t2022-checkout-paths.sh +++ b/t/t2022-checkout-paths.sh @@ -39,4 +39,26 @@ test_expect_success 'checking out paths out of a tree does not clobber unrelated test_cmp expect.next2 dir/next2 ' +test_expect_success 'do not touch unmerged entries matching $path but not in $tree' ' + git checkout next && + git reset --hard && + + cat dir/common >expect.common && + EMPTY_SHA1=$(git hash-object -w --stdin expect.next0 <<-EOF && + 100644 $EMPTY_SHA1 1 dir/next0 + 100644 $EMPTY_SHA1 2 dir/next0 + EOF + git update-index --index-info actual.next0 && + test_cmp expect.next0 actual.next0 +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 12f1e4a63c..b08c9f2295 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -7,20 +7,18 @@ test_description='git branch assorted tests' . ./test-lib.sh -test_expect_success \ - 'prepare a trivial repository' \ - 'echo Hello > A && - git update-index --add A && - git commit -m "Initial commit." && - echo World >> A && - git update-index --add A && - git commit -m "Second commit." && - HEAD=$(git rev-parse --verify HEAD)' - -test_expect_success \ - 'git branch --help should not have created a bogus branch' ' - test_might_fail git branch --help /dev/null 2>/dev/null && - test_path_is_missing .git/refs/heads/--help +test_expect_success 'prepare a trivial repository' ' + echo Hello >A && + git update-index --add A && + git commit -m "Initial commit." && + echo World >>A && + git update-index --add A && + git commit -m "Second commit." && + HEAD=$(git rev-parse --verify HEAD)' + +test_expect_success 'git branch --help should not have created a bogus branch' ' + test_might_fail git branch --help /dev/null 2>/dev/null && + test_path_is_missing .git/refs/heads/--help ' test_expect_success 'branch -h in broken repository' ' @@ -34,67 +32,67 @@ test_expect_success 'branch -h in broken repository' ' test_i18ngrep "[Uu]sage" broken/usage ' -test_expect_success \ - 'git branch abc should create a branch' \ - 'git branch abc && test_path_is_file .git/refs/heads/abc' +test_expect_success 'git branch abc should create a branch' ' + git branch abc && test_path_is_file .git/refs/heads/abc +' -test_expect_success \ - 'git branch a/b/c should create a branch' \ - 'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c' +test_expect_success 'git branch a/b/c should create a branch' ' + git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c +' -test_expect_success \ - 'git branch HEAD should fail' \ - 'test_must_fail git branch HEAD' +test_expect_success 'git branch HEAD should fail' ' + test_must_fail git branch HEAD +' cat >expect < 1117150200 +0000 branch: Created from master EOF -test_expect_success \ - 'git branch -l d/e/f should create a branch and a log' \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git branch -l d/e/f && - test_path_is_file .git/refs/heads/d/e/f && - test_path_is_file .git/logs/refs/heads/d/e/f && - test_cmp expect .git/logs/refs/heads/d/e/f' - -test_expect_success \ - 'git branch -d d/e/f should delete a branch and a log' \ - 'git branch -d d/e/f && - test_path_is_missing .git/refs/heads/d/e/f && - test_path_is_missing .git/logs/refs/heads/d/e/f' - -test_expect_success \ - 'git branch j/k should work after branch j has been deleted' \ - 'git branch j && - git branch -d j && - git branch j/k' - -test_expect_success \ - 'git branch l should work after branch l/m has been deleted' \ - 'git branch l/m && - git branch -d l/m && - git branch l' - -test_expect_success \ - 'git branch -m dumps usage' \ - 'test_expect_code 128 git branch -m 2>err && - test_i18ngrep "too many branches for a rename operation" err' - -test_expect_success \ - 'git branch -m m m/m should work' \ - 'git branch -l m && - git branch -m m m/m && - test_path_is_file .git/logs/refs/heads/m/m' - -test_expect_success \ - 'git branch -m n/n n should work' \ - 'git branch -l n/n && +test_expect_success 'git branch -l d/e/f should create a branch and a log' ' + GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git branch -l d/e/f && + test_path_is_file .git/refs/heads/d/e/f && + test_path_is_file .git/logs/refs/heads/d/e/f && + test_cmp expect .git/logs/refs/heads/d/e/f +' + +test_expect_success 'git branch -d d/e/f should delete a branch and a log' ' + git branch -d d/e/f && + test_path_is_missing .git/refs/heads/d/e/f && + test_path_is_missing .git/logs/refs/heads/d/e/f +' + +test_expect_success 'git branch j/k should work after branch j has been deleted' ' + git branch j && + git branch -d j && + git branch j/k +' + +test_expect_success 'git branch l should work after branch l/m has been deleted' ' + git branch l/m && + git branch -d l/m && + git branch l +' + +test_expect_success 'git branch -m dumps usage' ' + test_expect_code 128 git branch -m 2>err && + test_i18ngrep "too many branches for a rename operation" err +' + +test_expect_success 'git branch -m m m/m should work' ' + git branch -l m && + git branch -m m m/m && + test_path_is_file .git/logs/refs/heads/m/m +' + +test_expect_success 'git branch -m n/n n should work' ' + git branch -l n/n && git branch -m n/n n && - test_path_is_file .git/logs/refs/heads/n' + test_path_is_file .git/logs/refs/heads/n +' test_expect_success 'git branch -m o/o o should fail when o/p exists' ' git branch o/o && - git branch o/p && + git branch o/p && test_must_fail git branch -m o/o o ' @@ -252,19 +250,20 @@ mv .git/config-saved .git/config git config branch.s/s.dummy Hello -test_expect_success \ - 'git branch -m s/s s should work when s/t is deleted' \ - 'git branch -l s/s && +test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' + git branch -l s/s && test_path_is_file .git/logs/refs/heads/s/s && - git branch -l s/t && + git branch -l s/t && test_path_is_file .git/logs/refs/heads/s/t && - git branch -d s/t && - git branch -m s/s s && - test_path_is_file .git/logs/refs/heads/s' + git branch -d s/t && + git branch -m s/s s && + test_path_is_file .git/logs/refs/heads/s +' -test_expect_success 'config information was renamed, too' \ - "test $(git config branch.s.dummy) = Hello && - test_must_fail git config branch.s/s/dummy" +test_expect_success 'config information was renamed, too' ' + test $(git config branch.s.dummy) = Hello && + test_must_fail git config branch.s/s/dummy +' test_expect_success 'deleting a symref' ' git branch target && @@ -285,8 +284,7 @@ test_expect_success 'deleting a dangling symref' ' test_i18ncmp expect actual ' -test_expect_success 'renaming a symref is not allowed' \ -' +test_expect_success 'renaming a symref is not allowed' ' git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && git symbolic-ref refs/heads/master2 && @@ -294,163 +292,179 @@ test_expect_success 'renaming a symref is not allowed' \ test_path_is_missing .git/refs/heads/master3 ' -test_expect_success SYMLINKS \ - 'git branch -m u v should fail when the reflog for u is a symlink' ' - git branch -l u && - mv .git/logs/refs/heads/u real-u && - ln -s real-u .git/logs/refs/heads/u && - test_must_fail git branch -m u v -' - -test_expect_success 'test tracking setup via --track' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my1 local/master && - test $(git config branch.my1.remote) = local && - test $(git config branch.my1.merge) = refs/heads/master' - -test_expect_success 'test tracking setup (non-wildcard, matching)' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/master:refs/remotes/local/master && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my4 local/master && - test $(git config branch.my4.remote) = local && - test $(git config branch.my4.merge) = refs/heads/master' - -test_expect_success 'test tracking setup (non-wildcard, not matching)' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/s:refs/remotes/local/s && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my5 local/master && - ! test "$(git config branch.my5.remote)" = local && - ! test "$(git config branch.my5.merge)" = refs/heads/master' - -test_expect_success 'test tracking setup via config' \ - 'git config branch.autosetupmerge true && - git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch my3 local/master && - test $(git config branch.my3.remote) = local && - test $(git config branch.my3.merge) = refs/heads/master' - -test_expect_success 'test overriding tracking setup via --no-track' \ - 'git config branch.autosetupmerge true && - git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --no-track my2 local/master && - git config branch.autosetupmerge false && - ! test "$(git config branch.my2.remote)" = local && - ! test "$(git config branch.my2.merge)" = refs/heads/master' - -test_expect_success 'no tracking without .fetch entries' \ - 'git config branch.autosetupmerge true && - git branch my6 s && - git config branch.automsetupmerge false && - test -z "$(git config branch.my6.remote)" && - test -z "$(git config branch.my6.merge)"' - -test_expect_success 'test tracking setup via --track but deeper' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/o/o || git fetch local) && - git branch --track my7 local/o/o && - test "$(git config branch.my7.remote)" = local && - test "$(git config branch.my7.merge)" = refs/heads/o/o' - -test_expect_success 'test deleting branch deletes branch config' \ - 'git branch -d my7 && - test -z "$(git config branch.my7.remote)" && - test -z "$(git config branch.my7.merge)"' - -test_expect_success 'test deleting branch without config' \ - 'git branch my7 s && - sha1=$(git rev-parse my7 | cut -c 1-7) && - echo "Deleted branch my7 (was $sha1)." >expect && - git branch -d my7 >actual 2>&1 && - test_i18ncmp expect actual' - -test_expect_success 'test --track without .fetch entries' \ - 'git branch --track my8 && - test "$(git config branch.my8.remote)" && - test "$(git config branch.my8.merge)"' - -test_expect_success \ - 'branch from non-branch HEAD w/autosetupmerge=always' \ - 'git config branch.autosetupmerge always && - git branch my9 HEAD^ && - git config branch.autosetupmerge false' - -test_expect_success \ - 'branch from non-branch HEAD w/--track causes failure' \ - 'test_must_fail git branch --track my10 HEAD^' - -test_expect_success \ - 'branch from tag w/--track causes failure' \ - 'git tag foobar && - test_must_fail git branch --track my11 foobar' - -test_expect_success '--set-upstream-to fails on multiple branches' \ - 'test_must_fail git branch --set-upstream-to master a b c' - -test_expect_success '--set-upstream-to fails on detached HEAD' \ - 'git checkout HEAD^{} && - test_must_fail git branch --set-upstream-to master && - git checkout -' - -test_expect_success 'use --set-upstream-to modify HEAD' \ - 'test_config branch.master.remote foo && - test_config branch.master.merge foo && - git branch my12 - git branch --set-upstream-to my12 && - test "$(git config branch.master.remote)" = "." && - test "$(git config branch.master.merge)" = "refs/heads/my12"' - -test_expect_success 'use --set-upstream-to modify a particular branch' \ - 'git branch my13 - git branch --set-upstream-to master my13 && - test "$(git config branch.my13.remote)" = "." && - test "$(git config branch.my13.merge)" = "refs/heads/master"' - -test_expect_success '--unset-upstream should fail if given a non-existent branch' \ - 'test_must_fail git branch --unset-upstream i-dont-exist' - -test_expect_success 'test --unset-upstream on HEAD' \ - 'git branch my14 - test_config branch.master.remote foo && - test_config branch.master.merge foo && - git branch --set-upstream-to my14 && - git branch --unset-upstream && - test_must_fail git config branch.master.remote && - test_must_fail git config branch.master.merge && - # fail for a branch without upstream set - test_must_fail git branch --unset-upstream -' - -test_expect_success '--unset-upstream should fail on multiple branches' \ - 'test_must_fail git branch --unset-upstream a b c' - -test_expect_success '--unset-upstream should fail on detached HEAD' \ - 'git checkout HEAD^{} && - test_must_fail git branch --unset-upstream && - git checkout - -' - -test_expect_success 'test --unset-upstream on a particular branch' \ - 'git branch my15 - git branch --set-upstream-to master my14 && - git branch --unset-upstream my14 && - test_must_fail git config branch.my14.remote && - test_must_fail git config branch.my14.merge' - -test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' \ - 'git update-ref refs/remotes/origin/master HEAD && - git branch --set-upstream origin/master 2>actual && - test_when_finished git update-ref -d refs/remotes/origin/master && - test_when_finished git branch -d origin/master && - cat >expected <expect && + git branch -d my7 >actual 2>&1 && + test_i18ncmp expect actual +' + +test_expect_success 'test --track without .fetch entries' ' + git branch --track my8 && + test "$(git config branch.my8.remote)" && + test "$(git config branch.my8.merge)" +' + +test_expect_success 'branch from non-branch HEAD w/autosetupmerge=always' ' + git config branch.autosetupmerge always && + git branch my9 HEAD^ && + git config branch.autosetupmerge false +' + +test_expect_success 'branch from non-branch HEAD w/--track causes failure' ' + test_must_fail git branch --track my10 HEAD^ +' + +test_expect_success 'branch from tag w/--track causes failure' ' + git tag foobar && + test_must_fail git branch --track my11 foobar +' + +test_expect_success '--set-upstream-to fails on multiple branches' ' + test_must_fail git branch --set-upstream-to master a b c +' + +test_expect_success '--set-upstream-to fails on detached HEAD' ' + git checkout HEAD^{} && + test_must_fail git branch --set-upstream-to master && + git checkout - +' + +test_expect_success 'use --set-upstream-to modify HEAD' ' + test_config branch.master.remote foo && + test_config branch.master.merge foo && + git branch my12 + git branch --set-upstream-to my12 && + test "$(git config branch.master.remote)" = "." && + test "$(git config branch.master.merge)" = "refs/heads/my12" +' + +test_expect_success 'use --set-upstream-to modify a particular branch' ' + git branch my13 + git branch --set-upstream-to master my13 && + test "$(git config branch.my13.remote)" = "." && + test "$(git config branch.my13.merge)" = "refs/heads/master" +' + +test_expect_success '--unset-upstream should fail if given a non-existent branch' ' + test_must_fail git branch --unset-upstream i-dont-exist +' + +test_expect_success 'test --unset-upstream on HEAD' ' + git branch my14 + test_config branch.master.remote foo && + test_config branch.master.merge foo && + git branch --set-upstream-to my14 && + git branch --unset-upstream && + test_must_fail git config branch.master.remote && + test_must_fail git config branch.master.merge && + # fail for a branch without upstream set + test_must_fail git branch --unset-upstream +' + +test_expect_success '--unset-upstream should fail on multiple branches' ' + test_must_fail git branch --unset-upstream a b c +' + +test_expect_success '--unset-upstream should fail on detached HEAD' ' + git checkout HEAD^{} && + test_must_fail git branch --unset-upstream && + git checkout - +' + +test_expect_success 'test --unset-upstream on a particular branch' ' + git branch my15 + git branch --set-upstream-to master my14 && + git branch --unset-upstream my14 && + test_must_fail git config branch.my14.remote && + test_must_fail git config branch.my14.merge +' + +test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' ' + git update-ref refs/remotes/origin/master HEAD && + git branch --set-upstream origin/master 2>actual && + test_when_finished git update-ref -d refs/remotes/origin/master && + test_when_finished git branch -d origin/master && + cat >expected <actual && - test_when_finished git branch --unset-upstream master && - cat >expected <actual && + test_when_finished git branch --unset-upstream master && + cat >expected <actual && - test_when_finished git branch --unset-upstream my13 && - cat >expected <actual && + test_when_finished git branch --unset-upstream my13 && + cat >expected <expect < 1117150200 +0000 branch: Created from master EOF -test_expect_success \ - 'git checkout -b g/h/i -l should create a branch and a log' \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git checkout -b g/h/i -l master && - test_path_is_file .git/refs/heads/g/h/i && - test_path_is_file .git/logs/refs/heads/g/h/i && - test_cmp expect .git/logs/refs/heads/g/h/i' +test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' ' + GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git checkout -b g/h/i -l master && + test_path_is_file .git/refs/heads/g/h/i && + test_path_is_file .git/logs/refs/heads/g/h/i && + test_cmp expect .git/logs/refs/heads/g/h/i +' test_expect_success 'checkout -b makes reflog by default' ' git checkout master && @@ -760,7 +774,7 @@ test_expect_success 'detect misconfigured autosetuprebase (bad value)' ' test_expect_success 'detect misconfigured autosetuprebase (no value)' ' git config --unset branch.autosetuprebase && - echo "[branch] autosetuprebase" >> .git/config && + echo "[branch] autosetuprebase" >>.git/config && test_must_fail git branch && git config --unset branch.autosetuprebase ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 76fe7e0060..ba4f98e800 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -94,13 +94,13 @@ test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch* ' -cat >expect <<'EOF' -* (no branch) +test_expect_success 'git branch shows detached HEAD properly' ' + cat >expect <actual && test_i18ncmp expect actual diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 1de0ebda25..f6cc102657 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -138,8 +138,7 @@ test_expect_success 'rebase a single mode change' ' ' test_expect_success 'rebase is not broken by diff.renames' ' - git config diff.renames copies && - test_when_finished "git config --unset diff.renames" && + test_config diff.renames copies && git checkout filemove && GIT_TRACE=1 git rebase force-3way ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 15dcbd42d3..a58406d12f 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -937,8 +937,7 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' ' test_expect_success 'rebase -i respects core.commentchar' ' git reset --hard && git checkout E^0 && - git config core.commentchar "\\" && - test_when_finished "git config --unset core.commentchar" && + test_config core.commentchar "\\" && write_script remove-all-but-first.sh <<-\EOF && sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" && mv "$1.tmp" "$1" diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh new file mode 100755 index 0000000000..f97727975b --- /dev/null +++ b/t/t3511-cherry-pick-x.sh @@ -0,0 +1,219 @@ +#!/bin/sh + +test_description='Test cherry-pick -x and -s' + +. ./test-lib.sh + +pristine_detach () { + git cherry-pick --quit && + git checkout -f "$1^0" && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x +} + +mesg_one_line='base: commit message' + +mesg_no_footer="$mesg_one_line + +OneWordBodyThatsNotA-S-o-B" + +mesg_with_footer="$mesg_no_footer + +Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> +Signed-off-by: A.U. Thor +Signed-off-by: B.U. Thor " + +mesg_broken_footer="$mesg_no_footer + +The signed-off-by string should begin with the words Signed-off-by followed +by a colon and space, and then the signers name and email address. e.g. +Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + +mesg_with_footer_sob="$mesg_with_footer +Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + +mesg_with_cherry_footer="$mesg_with_footer_sob +(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709) +Tested-by: C.U. Thor " + + +test_expect_success setup ' + git config advice.detachedhead false && + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit "$mesg_one_line" foo b mesg-one-line && + git reset --hard initial && + test_commit "$mesg_no_footer" foo b mesg-no-footer && + git reset --hard initial && + test_commit "$mesg_broken_footer" foo b mesg-broken-footer && + git reset --hard initial && + test_commit "$mesg_with_footer" foo b mesg-with-footer && + git reset --hard initial && + test_commit "$mesg_with_footer_sob" foo b mesg-with-footer-sob && + git reset --hard initial && + test_commit "$mesg_with_cherry_footer" foo b mesg-with-cherry-footer && + pristine_detach initial && + test_commit conflicting unrelated +' + +test_expect_success 'cherry-pick -x inserts blank line after one line subject' ' + pristine_detach initial && + sha1=`git rev-parse mesg-one-line^0` && + git cherry-pick -x mesg-one-line && + cat <<-EOF >expect && + $mesg_one_line + + (cherry picked from commit $sha1) + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s inserts blank line after one line subject' ' + pristine_detach initial && + git cherry-pick -s mesg-one-line && + cat <<-EOF >expect && + $mesg_one_line + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s inserts blank line after non-conforming footer' ' + pristine_detach initial && + git cherry-pick -s mesg-broken-footer && + cat <<-EOF >expect && + $mesg_broken_footer + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' ' + pristine_detach initial && + sha1=`git rev-parse mesg-no-footer^0` && + git cherry-pick -x mesg-no-footer && + cat <<-EOF >expect && + $mesg_no_footer + + (cherry picked from commit $sha1) + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s inserts blank line when conforming footer not found' ' + pristine_detach initial && + git cherry-pick -s mesg-no-footer && + cat <<-EOF >expect && + $mesg_no_footer + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s inserts blank line when conforming footer not found' ' + pristine_detach initial && + sha1=`git rev-parse mesg-no-footer^0` && + git cherry-pick -x -s mesg-no-footer && + cat <<-EOF >expect && + $mesg_no_footer + + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committer' ' + pristine_detach initial && + git cherry-pick -s mesg-with-footer && + cat <<-EOF >expect && + $mesg_with_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s adds sob when last sob doesnt match committer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-footer^0` && + git cherry-pick -x -s mesg-with-footer && + cat <<-EOF >expect && + $mesg_with_footer + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob' ' + pristine_detach initial && + git cherry-pick -s mesg-with-footer-sob && + cat <<-EOF >expect && + $mesg_with_footer_sob + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists for committer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-footer-sob^0` && + git cherry-pick -x -s mesg-with-footer-sob && + cat <<-EOF >expect && + $mesg_with_footer_sob + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x treats "(cherry picked from..." line as part of footer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-cherry-footer^0` && + git cherry-pick -x mesg-with-cherry-footer && + cat <<-EOF >expect && + $mesg_with_cherry_footer + (cherry picked from commit $sha1) + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part of footer' ' + pristine_detach initial && + git cherry-pick -s mesg-with-cherry-footer && + cat <<-EOF >expect && + $mesg_with_cherry_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as part of footer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-cherry-footer^0` && + git cherry-pick -x -s mesg-with-cherry-footer && + cat <<-EOF >expect && + $mesg_with_cherry_footer + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index bb1fc47fe8..b993dae645 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -972,6 +972,268 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' +append_signoff() +{ + C=$(git commit-tree HEAD^^{tree} -p HEAD) && + git format-patch --stdout --signoff $C^..$C >append_signoff.patch && + sed -n -e "1,/^---$/p" append_signoff.patch | + egrep -n "^Subject|Sign|^$" +} + +test_expect_success 'signoff: commit with no body' ' + append_signoff actual && + cat <<\EOF | sed "s/EOL$//" >expected && +4:Subject: [PATCH] EOL +8: +9:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: commit with only subject' ' + echo subject | append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: commit with only subject that does not end with NL' ' + printf subject | append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: no existing signoffs' ' + append_signoff <<\EOF >actual && +subject + +body +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: no existing signoffs and no trailing NL' ' + printf "subject\n\nbody" | append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: some random signoff' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: my@house +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: my@house +12:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: misc conforming footer elements' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: my@house +(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709) +Tested-by: Some One +Bug: 1234 +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: my@house +15:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: some random signoff-alike' ' + append_signoff <<\EOF >actual && +subject + +body +Fooled-by-me: my@house +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11: +12:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: not really a signoff' ' + append_signoff <<\EOF >actual && +subject + +I want to mention about Signed-off-by: here. +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:I want to mention about Signed-off-by: here. +10: +11:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: not really a signoff (2)' ' + append_signoff <<\EOF >actual && +subject + +My unfortunate +Signed-off-by: example happens to be wrapped here. +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10:Signed-off-by: example happens to be wrapped here. +11: +12:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: valid S-o-b paragraph in the middle' ' + append_signoff <<\EOF >actual && +subject + +Signed-off-by: my@house +Signed-off-by: your@house + +A lot of houses. +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: my@house +10:Signed-off-by: your@house +11: +13: +14:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: the same signoff at the end' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: C O Mitter +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: the same signoff at the end, no trailing NL' ' + printf "subject\n\nSigned-off-by: C O Mitter " | + append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: the same signoff NOT at the end' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: C O Mitter +Signed-off-by: my@house +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter +12:Signed-off-by: my@house +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: detect garbage in non-conforming footer' ' + append_signoff <<\EOF >actual && +subject + +body + +Tested-by: my@house +Some Trash +Signed-off-by: C O Mitter +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +13:Signed-off-by: C O Mitter +14: +15:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: footer begins with non-signoff without @ sign' ' + append_signoff <<\EOF >actual && +subject + +body + +Reviewed-id: Noone +Tested-by: my@house +Change-id: Ideadbeef +Signed-off-by: C O Mitter +Bug: 1234 +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +14:Signed-off-by: C O Mitter +EOF + test_cmp expected actual +' + test_expect_success 'format patch ignores color.ui' ' test_unconfig color.ui && git format-patch --stdout -1 >expect && diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 082d3e83bd..38a092a0da 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -93,11 +93,6 @@ sed -e ' s/song;/song();/ ' Beer-correct.perl -test_config () { - git config "$1" "$2" && - test_when_finished "git config --unset $1" -} - test_expect_funcname () { lang=${2-java} test_expect_code 1 git diff --no-index -U1 \ diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 40ab333a8a..f2f55fc51c 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -230,7 +230,7 @@ test_expect_success '.gitattributes override config' ' ' test_expect_success 'setup: remove diff driver regex' ' - test_might_fail git config --unset diff.testdriver.wordRegex + test_unconfig diff.testdriver.wordRegex ' test_expect_success 'use configured regex' ' @@ -335,8 +335,7 @@ test_expect_success 'word-diff with diff.sbe' ' c EOF - test_when_finished "git config --unset diff.suppress-blank-empty" && - git config diff.suppress-blank-empty true && + test_config diff.suppress-blank-empty true && word_diff --word-diff=plain ' @@ -368,7 +367,7 @@ test_expect_success 'setup history with two files' ' test_expect_success 'wordRegex for the first file does not apply to the second' ' echo "*.tex diff=tex" >.gitattributes && - git config diff.tex.wordRegex "[a-z]+|." && + test_config diff.tex.wordRegex "[a-z]+|." && cat >expect <<-\EOF && diff --git a/a.tex b/a.tex --- a/a.tex diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 614425adac..b7e16a7840 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -3,6 +3,7 @@ test_description='combined diff' . ./test-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh setup_helper () { one=$1 branch=$2 side=$3 && @@ -113,4 +114,114 @@ test_expect_success 'check --cc --raw with forty trees' ' grep "^::::::::::::::::::::::::::::::::::::::::[^:]" out ' +test_expect_success 'setup combined ignore spaces' ' + git checkout master && + >test && + git add test && + git commit -m initial && + + tr -d Q <<-\EOF >test && + always coalesce + eol space coalesce Q + space change coalesce + all spa ces coalesce + eol spaces Q + space change + all spa ces + EOF + git commit -m "test space change" -a && + + git checkout -b side HEAD^ && + tr -d Q <<-\EOF >test && + always coalesce + eol space coalesce + space change coalesce + all spaces coalesce + eol spaces + space change + all spaces + EOF + git commit -m "test other space changes" -a && + + test_must_fail git merge master && + tr -d Q <<-\EOF >test && + eol spaces Q + space change + all spa ces + EOF + git commit -m merged -a +' + +test_expect_success 'check combined output (no ignore space)' ' + git show >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + - eol space coalesce + - space change coalesce + - all spaces coalesce + - eol spaces + - space change + - all spaces + -eol space coalesce Q + -space change coalesce + -all spa ces coalesce + + eol spaces Q + + space change + + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'check combined output (ignore space at eol)' ' + git show --ignore-space-at-eol >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + --eol space coalesce + - space change coalesce + - all spaces coalesce + -space change coalesce + -all spa ces coalesce + eol spaces Q + - space change + - all spaces + + space change + + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'check combined output (ignore space change)' ' + git show -b >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + --eol space coalesce + --space change coalesce + - all spaces coalesce + -all spa ces coalesce + eol spaces Q + space change + - all spaces + + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'check combined output (ignore all spaces)' ' + git show -w >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + --eol space coalesce + --space change coalesce + --all spaces coalesce + eol spaces Q + space change + all spa ces + EOF + compare_diff_patch expected actual +' + test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 6f6ee88b28..0bbcf0603d 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -486,4 +486,30 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' ' test_cmp one expect ' +test_expect_success 'whitespace=fix to expand' ' + qz_to_tab_space >preimage <<-\EOF && + QQa + QQb + QQc + ZZZZZZZZZZZZZZZZd + QQe + QQf + QQg + EOF + qz_to_tab_space >patch <<-\EOF && + diff --git a/preimage b/preimage + --- a/preimage + +++ b/preimage + @@ -1,7 +1,6 @@ + QQa + QQb + QQc + -QQd + QQe + QQf + QQg + EOF + git -c core.whitespace=tab-in-indent apply --whitespace=fix patch +' + test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index cdafd7e7c1..12f6b027ac 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -17,7 +17,7 @@ test_expect_success 'setup: messages' ' vero eos et accusam et justo duo dolores et ea rebum. EOF - q_to_tab <<-\EOF >>msg && + qz_to_tab_space <<-\EOF >>msg && QDuis autem vel eum iriure dolor in hendrerit in vulputate velit Qesse molestie consequat, vel illum dolore eu feugiat nulla facilisis Qat vero eros et accumsan et iusto odio dignissim qui blandit diff --git a/t/t4202-log.sh b/t/t4202-log.sh index fa686b887d..9243a97993 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -419,8 +419,6 @@ test_expect_success 'log --graph with merge' ' ' test_expect_success 'log.decorate configuration' ' - test_might_fail git config --unset-all log.decorate && - git log --oneline >expect.none && git log --oneline --decorate >expect.short && git log --oneline --decorate=full >expect.full && @@ -429,8 +427,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline >actual && test_cmp expect.short actual && - git config --unset-all log.decorate && - git config log.decorate true && + test_config log.decorate true && git log --oneline >actual && test_cmp expect.short actual && git log --oneline --decorate=full >actual && @@ -438,8 +435,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=no >actual && test_cmp expect.none actual && - git config --unset-all log.decorate && - git config log.decorate no && + test_config log.decorate no && git log --oneline >actual && test_cmp expect.none actual && git log --oneline --decorate >actual && @@ -447,8 +443,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=full >actual && test_cmp expect.full actual && - git config --unset-all log.decorate && - git config log.decorate 1 && + test_config log.decorate 1 && git log --oneline >actual && test_cmp expect.short actual && git log --oneline --decorate=full >actual && @@ -456,8 +451,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=no >actual && test_cmp expect.none actual && - git config --unset-all log.decorate && - git config log.decorate short && + test_config log.decorate short && git log --oneline >actual && test_cmp expect.short actual && git log --oneline --no-decorate >actual && @@ -465,8 +459,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=full >actual && test_cmp expect.full actual && - git config --unset-all log.decorate && - git config log.decorate full && + test_config log.decorate full && git log --oneline >actual && test_cmp expect.full actual && git log --oneline --no-decorate >actual && @@ -474,16 +467,15 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate >actual && test_cmp expect.short actual - git config --unset-all log.decorate && + test_unconfig log.decorate && git log --pretty=raw >expect.raw && - git config log.decorate full && + test_config log.decorate full && git log --pretty=raw >actual && test_cmp expect.raw actual ' test_expect_success 'reflog is expected format' ' - test_might_fail git config --remove-section log && git log -g --abbrev-commit --pretty=oneline >expect && git reflog >actual && test_cmp expect actual @@ -496,10 +488,6 @@ test_expect_success 'whatchanged is expected format' ' ' test_expect_success 'log.abbrevCommit configuration' ' - test_when_finished "git config --unset log.abbrevCommit" && - - test_might_fail git config --unset log.abbrevCommit && - git log --abbrev-commit >expect.log.abbrev && git log --no-abbrev-commit >expect.log.full && git log --pretty=raw >expect.log.raw && @@ -508,7 +496,7 @@ test_expect_success 'log.abbrevCommit configuration' ' git whatchanged --abbrev-commit >expect.whatchanged.abbrev && git whatchanged --no-abbrev-commit >expect.whatchanged.full && - git config log.abbrevCommit true && + test_config log.abbrevCommit true && git log >actual && test_cmp expect.log.abbrev actual && diff --git a/t/t5002-archive-attr-pattern.sh b/t/t5002-archive-attr-pattern.sh index 0c847fb454..6667d159ab 100755 --- a/t/t5002-archive-attr-pattern.sh +++ b/t/t5002-archive-attr-pattern.sh @@ -27,6 +27,25 @@ test_expect_success 'setup' ' echo ignored-only-if-dir/ export-ignore >>.git/info/attributes && git add ignored-only-if-dir && + mkdir -p ignored-without-slash && + echo "ignored without slash" >ignored-without-slash/foo && + git add ignored-without-slash/foo && + echo "ignored-without-slash export-ignore" >>.git/info/attributes && + + mkdir -p wildcard-without-slash && + echo "ignored without slash" >wildcard-without-slash/foo && + git add wildcard-without-slash/foo && + echo "wild*-without-slash export-ignore" >>.git/info/attributes && + + mkdir -p deep/and/slashless && + echo "ignored without slash" >deep/and/slashless/foo && + git add deep/and/slashless/foo && + echo "deep/and/slashless export-ignore" >>.git/info/attributes && + + mkdir -p deep/with/wildcard && + echo "ignored without slash" >deep/with/wildcard/foo && + git add deep/with/wildcard/foo && + echo "deep/*t*/wildcard export-ignore" >>.git/info/attributes && mkdir -p one-level-lower/two-levels-lower/ignored-only-if-dir && echo ignored by ignored dir >one-level-lower/two-levels-lower/ignored-only-if-dir/ignored-by-ignored-dir && @@ -49,6 +68,14 @@ test_expect_exists archive/not-ignored-dir/ignored-only-if-dir test_expect_exists archive/not-ignored-dir/ test_expect_missing archive/ignored-only-if-dir/ test_expect_missing archive/ignored-ony-if-dir/ignored-by-ignored-dir +test_expect_missing archive/ignored-without-slash/ && +test_expect_missing archive/ignored-without-slash/foo && +test_expect_missing archive/wildcard-without-slash/ +test_expect_missing archive/wildcard-without-slash/foo && +test_expect_missing archive/deep/and/slashless/ && +test_expect_missing archive/deep/and/slashless/foo && +test_expect_missing archive/deep/with/wildcard/ && +test_expect_missing archive/deep/with/wildcard/foo && test_expect_exists archive/one-level-lower/ test_expect_missing archive/one-level-lower/two-levels-lower/ignored-only-if-dir/ test_expect_missing archive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 60de2d6ede..f30c03885c 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -4,10 +4,6 @@ test_description='test automatic tag following' . ./test-lib.sh -if ! test_have_prereq NOT_MINGW; then - say "GIT_DEBUG_SEND_PACK not supported - skipping tests" -fi - # End state of the repository: # # T - tag1 S - tag2 @@ -17,7 +13,7 @@ fi # \ C - origin/cat \ # origin/master master -test_expect_success NOT_MINGW setup ' +test_expect_success setup ' test_tick && echo ichi >file && git add file && @@ -39,28 +35,35 @@ test_expect_success NOT_MINGW setup ' ' U=UPLOAD_LOG +UPATH="$(pwd)/$U" -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect -#S want $A -#E EOF ' -test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' ' +get_needs () { + test -s "$1" && + perl -alne ' + next unless $F[1] eq "upload-pack<"; + last if $F[2] eq "0000"; + print $F[2], " ", $F[3]; + ' "$1" +} + +test_expect_success 'fetch A (new commit : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $A = $(git rev-parse --verify origin/master) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' +test_expect_success "create tag T on A, create C on branch cat" ' git tag -a -m tag1 tag1 $A && T=$(git rev-parse --verify tag1) && @@ -72,30 +75,27 @@ test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' git checkout master ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect -#S want $C want $T -#E EOF ' -test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' ' +test_expect_success 'fetch C, T (new branch, tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $C = $(git rev-parse --verify origin/cat) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' +test_expect_success "create commits O, B, tag S on B" ' test_tick && echo O >file && git add file && @@ -111,39 +111,34 @@ test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' S=$(git rev-parse --verify tag2) ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect -#S want $B want $S -#E EOF ' -test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' ' +test_expect_success 'fetch B, S (commit and tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $B = $(git rev-parse --verify origin/master) && test $B = $(git rev-parse --verify tag2^0) && test $S = $(git rev-parse --verify tag2) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <expect -#S want $B want $S -#E EOF ' -test_expect_success NOT_MINGW 'new clone fetch master and tags' ' +test_expect_success 'new clone fetch master and tags' ' git branch -D cat rm -f $U ( @@ -151,15 +146,14 @@ test_expect_success NOT_MINGW 'new clone fetch master and tags' ' cd clone2 && git init && git remote add origin .. && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $B = $(git rev-parse --verify origin/master) && test $S = $(git rev-parse --verify tag2) && test $B = $(git rev-parse --verify tag2^0) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index d3dc5df7b1..383a2eb1ea 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -22,19 +22,16 @@ mk_test () { ( for ref in "$@" do - git push testrepo $the_first_commit:refs/$ref || { - echo "Oops, push refs/$ref failure" - exit 1 - } + git push testrepo $the_first_commit:refs/$ref || + exit done && cd testrepo && for ref in "$@" do - r=$(git show-ref -s --verify refs/$ref) && - test "z$r" = "z$the_first_commit" || { - echo "Oops, refs/$ref is wrong" - exit 1 - } + echo "$the_first_commit" >expect && + git show-ref -s --verify refs/$ref >actual && + test_cmp expect actual || + exit done && git fsck --full ) @@ -82,15 +79,13 @@ mk_child() { check_push_result () { ( cd testrepo && - it="$1" && - shift + echo "$1" >expect && + shift && for ref in "$@" do - r=$(git show-ref -s --verify refs/$ref) && - test "z$r" = "z$it" || { - echo "Oops, refs/$ref is wrong" - exit 1 - } + git show-ref -s --verify refs/$ref >actual && + test_cmp expect actual || + exit done && git fsck --full ) @@ -118,10 +113,9 @@ test_expect_success 'fetch without wildcard' ' cd testrepo && git fetch .. refs/heads/master:refs/remotes/origin/master && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' @@ -133,10 +127,9 @@ test_expect_success 'fetch with wildcard' ' git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' @@ -150,10 +143,9 @@ test_expect_success 'fetch with insteadOf' ' git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' @@ -167,10 +159,9 @@ test_expect_success 'fetch with pushInsteadOf (should not rewrite)' ' git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' @@ -180,10 +171,9 @@ test_expect_success 'push without wildcard' ' git push testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' @@ -193,54 +183,50 @@ test_expect_success 'push with wildcard' ' git push testrepo "refs/heads/*:refs/remotes/origin/*" && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with insteadOf' ' mk_empty && TRASH="$(pwd)/" && - git config "url.$TRASH.insteadOf" trash/ && + test_config "url.$TRASH.insteadOf" trash/ && git push trash/testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with pushInsteadOf' ' mk_empty && TRASH="$(pwd)/" && - git config "url.$TRASH.pushInsteadOf" trash/ && + test_config "url.$TRASH.pushInsteadOf" trash/ && git push trash/testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' ' mk_empty && TRASH="$(pwd)/" && - git config "url.trash2/.pushInsteadOf" trash/ && - git config remote.r.url trash/wrong && - git config remote.r.pushurl "$TRASH/testrepo" && + test_config "url.trash2/.pushInsteadOf" trash/ && + test_config remote.r.url trash/wrong && + test_config remote.r.pushurl "$TRASH/testrepo" && git push r refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' @@ -340,13 +326,8 @@ test_expect_success 'push with weak ambiguity (2)' ' test_expect_success 'push with ambiguity' ' mk_test heads/frotz tags/frotz && - if git push testrepo master:frotz - then - echo "Oops, should have failed" - false - else - check_push_result $the_first_commit heads/frotz tags/frotz - fi + test_must_fail git push testrepo master:frotz && + check_push_result $the_first_commit heads/frotz tags/frotz ' @@ -489,31 +470,24 @@ test_expect_success 'push with config remote.*.push = HEAD' ' git checkout local && git reset --hard $the_first_commit ) && - git config remote.there.url testrepo && - git config remote.there.push HEAD && - git config branch.master.remote there && + test_config remote.there.url testrepo && + test_config remote.there.push HEAD && + test_config branch.master.remote there && git push && check_push_result $the_commit heads/master && check_push_result $the_first_commit heads/local ' -# clean up the cruft left with the previous one -git config --remove-section remote.there -git config --remove-section branch.master - test_expect_success 'push with config remote.*.pushurl' ' mk_test heads/master && git checkout master && - git config remote.there.url test2repo && - git config remote.there.pushurl testrepo && + test_config remote.there.url test2repo && + test_config remote.there.pushurl testrepo && git push there && check_push_result $the_commit heads/master ' -# clean up the cruft left with the previous one -git config --remove-section remote.there - test_expect_success 'push with dry-run' ' mk_test heads/master && @@ -834,9 +808,9 @@ test_expect_success 'fetch with branches' ' ( cd testrepo && git fetch branch1 && - r=$(git show-ref -s --verify refs/heads/branch1) && - test "z$r" = "z$the_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_commit commit refs/heads/branch1" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) && git checkout master ' @@ -847,9 +821,9 @@ test_expect_success 'fetch with branches containing #' ' ( cd testrepo && git fetch branch2 && - r=$(git show-ref -s --verify refs/heads/branch2) && - test "z$r" = "z$the_first_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_first_commit commit refs/heads/branch2" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) && git checkout master ' @@ -861,9 +835,9 @@ test_expect_success 'push with branches' ' git push branch1 && ( cd testrepo && - r=$(git show-ref -s --verify refs/heads/master) && - test "z$r" = "z$the_first_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_first_commit commit refs/heads/master" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) ' @@ -873,9 +847,9 @@ test_expect_success 'push with branches containing #' ' git push branch2 && ( cd testrepo && - r=$(git show-ref -s --verify refs/heads/branch3) && - test "z$r" = "z$the_first_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_first_commit commit refs/heads/branch3" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) && git checkout master ' @@ -958,9 +932,9 @@ test_expect_success 'push --porcelain' ' git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) && test_cmp .git/foo .git/bar ' diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 35304b41e9..6af6c63350 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -96,8 +96,7 @@ test_expect_success '--rebase' ' ' test_expect_success 'pull.rebase' ' git reset --hard before-rebase && - git config --bool pull.rebase true && - test_when_finished "git config --unset pull.rebase" && + test_config pull.rebase true && git pull . copy && test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) @@ -105,8 +104,7 @@ test_expect_success 'pull.rebase' ' test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && - git config --bool branch.to-rebase.rebase true && - test_when_finished "git config --unset branch.to-rebase.rebase" && + test_config branch.to-rebase.rebase true && git pull . copy && test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) @@ -114,10 +112,8 @@ test_expect_success 'branch.to-rebase.rebase' ' test_expect_success 'branch.to-rebase.rebase should override pull.rebase' ' git reset --hard before-rebase && - git config --bool pull.rebase true && - test_when_finished "git config --unset pull.rebase" && - git config --bool branch.to-rebase.rebase false && - test_when_finished "git config --unset branch.to-rebase.rebase" && + test_config pull.rebase true && + test_config branch.to-rebase.rebase false && git pull . copy && test $(git rev-parse HEAD^) != $(git rev-parse copy) && test new = $(git show HEAD:file2) @@ -171,9 +167,9 @@ test_expect_success 'pull --rebase dies early with dirty working directory' ' git update-ref refs/remotes/me/copy copy^ && COPY=$(git rev-parse --verify me/copy) && git rebase --onto $COPY copy && - git config branch.to-rebase.remote me && - git config branch.to-rebase.merge refs/heads/copy && - git config branch.to-rebase.rebase true && + test_config branch.to-rebase.remote me && + test_config branch.to-rebase.merge refs/heads/copy && + test_config branch.to-rebase.rebase true && echo dirty >> file && git add file && test_must_fail git pull && diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 4b4b4a604f..4086f02bc1 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -181,8 +181,7 @@ test_expect_success 'push (chunked)' ' git checkout master && test_commit commit path3 && HEAD=$(git rev-parse --verify HEAD) && - git config http.postbuffer 4 && - test_when_finished "git config --unset http.postbuffer" && + test_config http.postbuffer 4 && git push -v -v origin $BRANCH 2>err && grep "POST git-receive-pack (chunked)" err && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index c47d450cc3..60f1552ade 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -54,11 +54,14 @@ cd "$base_dir" rm -f "$U.D" -test_expect_success 'cloning with reference (no -l -s)' \ -'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"' +test_expect_success 'cloning with reference (no -l -s)' ' + GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D +' -test_expect_success 'fetched no objects' \ -'! grep "^want" "$U.D"' +test_expect_success 'fetched no objects' ' + test -s "$U.D" && + ! grep " want" "$U.D" +' cd "$base_dir" @@ -173,12 +176,13 @@ test_expect_success 'fetch with incomplete alternates' ' ( cd K && git remote add J "file://$base_dir/J" && - GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K" + GIT_TRACE_PACKET=$U.K git fetch J ) && master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) && - ! grep "^want $master_object" "$U.K" && + test -s "$U.K" && + ! grep " want $master_object" "$U.K" && tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && - ! grep "^want $tag_object" "$U.K" + ! grep " want $tag_object" "$U.K" ' test_done diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 30507407ff..66cda17ef3 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -133,4 +133,17 @@ test_expect_success 'dodecapus' ' check_revlist "--min-parents=13" && check_revlist "--min-parents=4 --max-parents=11" tetrapus ' + +test_expect_success 'ancestors with the same commit time' ' + + test_tick_keep=$test_tick && + for i in 1 2 3 4 5 6 7 8; do + test_tick=$test_tick_keep + test_commit t$i + done && + git rev-list t1^! --not t$i >result && + >expect && + test_cmp expect result +' + test_done diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 839ad97b79..8e2ff13423 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -56,7 +56,23 @@ test_expect_success setup ' echo "Final change" >file && test_tick && git commit -a -m "Final change" && - note I + note I && + + git symbolic-ref HEAD refs/heads/unrelated && + git rm -f "*" && + echo "Unrelated branch" >side && + git add side && + test_tick && git commit -m "Side root" && + note J && + + git checkout master && + test_tick && git merge -m "Coolest" unrelated && + note K && + + echo "Immaterial" >elif && + git add elif && + test_tick && git commit -m "Last" && + note L ' FMT='tformat:%P %H | %s' @@ -79,10 +95,10 @@ check_result () { ' } -check_result 'I H G F E D C B A' --full-history -check_result 'I H E C B A' --full-history -- file -check_result 'I H E C B A' --full-history --topo-order -- file -check_result 'I H E C B A' --full-history --date-order -- file +check_result 'L K J I H G F E D C B A' --full-history +check_result 'K I H E C B A' --full-history -- file +check_result 'K I H E C B A' --full-history --topo-order -- file +check_result 'K I H E C B A' --full-history --date-order -- file check_result 'I E C B A' --simplify-merges -- file check_result 'I B A' -- file check_result 'I B A' --topo-order -- file diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 3e0e15fb3e..2fce99a075 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if cp .git/BISECT_START saved && test_must_fail git bisect start $HASH4 foo -- && git branch > branch.output && - test_i18ngrep "* (no branch)" branch.output > /dev/null && + test_i18ngrep "* (no branch, bisect started on other)" branch.output > /dev/null && test_cmp saved .git/BISECT_START ' test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' ' diff --git a/t/t7062-wtstatus-ignorecase.sh b/t/t7062-wtstatus-ignorecase.sh new file mode 100755 index 0000000000..73709dbeee --- /dev/null +++ b/t/t7062-wtstatus-ignorecase.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='git-status with core.ignorecase=true' + +. ./test-lib.sh + +test_expect_success 'status with hash collisions' ' + # note: "V/", "V/XQANY/" and "WURZAUP/" produce the same hash code + # in name-hash.c::hash_name + mkdir V && + mkdir V/XQANY && + mkdir WURZAUP && + touch V/XQANY/test && + git config core.ignorecase true && + git add . && + # test is successful if git status completes (no endless loop) + git status +' + +test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 1a3d20bdc3..2a0cfaac32 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -665,8 +665,10 @@ test_expect_success 'submodule add properly re-creates deeper level submodules' test_expect_success 'submodule update properly revives a moved submodule' ' (cd super && + H=$(git rev-parse --short HEAD) && git commit -am "pre move" && - git status >expect&& + H2=$(git rev-parse --short HEAD) && + git status | sed "s/$H/XXX/" >expect && H=$(cd submodule2; git rev-parse HEAD) && git rm --cached submodule2 && rm -rf submodule2 && @@ -675,7 +677,7 @@ test_expect_success 'submodule update properly revives a moved submodule' ' git config -f .gitmodules submodule.submodule2.path "moved/sub module" git commit -am "post move" && git submodule update && - git status >actual && + git status | sed "s/$H2/XXX/" >actual && test_cmp expect actual ) ' diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 1c908f4d39..436b7b606e 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -36,8 +36,7 @@ test_expect_success 'nonexistent template file should return error' ' ' test_expect_success 'nonexistent template file in config should return error' ' - git config commit.template "$PWD"/notexist && - test_when_finished "git config --unset commit.template" && + test_config commit.template "$PWD"/notexist && ( GIT_EDITOR="echo hello >\"\$1\"" && export GIT_EDITOR && @@ -93,14 +92,13 @@ test_expect_success '-t option should be short for --template' ' test_expect_success 'config-specified template should commit' ' echo "new template" > "$TEMPLATE" && - git config commit.template "$TEMPLATE" && + test_config commit.template "$TEMPLATE" && echo "more content" >> foo && git add foo && ( test_set_editor "$TEST_DIRECTORY"/t7500/add-content && git commit ) && - git config --unset commit.template && commit_msg_is "new templatecommit message" ' diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index f9b44b7244..a4938b1e45 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -171,10 +171,9 @@ test_expect_success 'verbose' ' test_expect_success 'verbose respects diff config' ' - git config color.diff always && + test_config color.diff always && git status -v >actual && - grep "\[1mdiff --git" actual && - git config --unset color.diff + grep "\[1mdiff --git" actual ' mesg_with_comment_and_newlines=' @@ -425,6 +424,18 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo ' +test_expect_success 'commit -s places sob on third line after two empty lines' ' + git commit -s --allow-empty --allow-empty-message && + cat <<-EOF >expect && + + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + + EOF + sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual && + test_cmp expect actual +' + write_script .git/FAKE_EDITOR <<\EOF mv "$1" "$1.orig" ( @@ -435,16 +446,6 @@ EOF echo '## Custom template' >template -clear_config () { - ( - git config --unset-all "$1" - case $? in - 0|5) exit 0 ;; - *) exit 1 ;; - esac - ) -} - try_commit () { git reset --hard && echo >>negative && @@ -460,67 +461,57 @@ try_commit () { try_commit_status_combo () { test_expect_success 'commit' ' - clear_config commit.status && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit' ' - clear_config commit.status && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status' ' - clear_config commit.status && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status' ' - clear_config commit.status && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit "" && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' @@ -534,8 +525,7 @@ use_template="-t template" try_commit_status_combo test_expect_success 'commit --status with custom comment character' ' - test_when_finished "git config --unset core.commentchar" && - git config core.commentchar ";" && + test_config core.commentchar ";" && try_commit --status && test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index aecb4d1e5f..e2ffdacc26 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -131,8 +131,7 @@ cat >expect <<\EOF EOF test_expect_success 'status (advice.statusHints false)' ' - test_when_finished "git config --unset advice.statusHints" && - git config advice.statusHints false && + test_config advice.statusHints false && git status >output && test_i18ncmp expect output @@ -332,8 +331,7 @@ test_expect_success 'status -uno' ' ' test_expect_success 'status (status.showUntrackedFiles no)' ' - git config status.showuntrackedfiles no - test_when_finished "git config --unset status.showuntrackedfiles" && + test_config status.showuntrackedfiles no && git status >output && test_i18ncmp expect output ' @@ -348,12 +346,11 @@ cat >expect <output && test_i18ncmp expect output ' -git config --unset advice.statusHints cat >expect << EOF M dir1/modified @@ -400,8 +397,7 @@ test_expect_success 'status -unormal' ' ' test_expect_success 'status (status.showUntrackedFiles normal)' ' - git config status.showuntrackedfiles normal - test_when_finished "git config --unset status.showuntrackedfiles" && + test_config status.showuntrackedfiles normal git status >output && test_i18ncmp expect output ' @@ -459,8 +455,7 @@ test_expect_success 'status -uall' ' ' test_expect_success 'status (status.showUntrackedFiles all)' ' - git config status.showuntrackedfiles all - test_when_finished "git config --unset status.showuntrackedfiles" && + test_config status.showuntrackedfiles all git status >output && test_i18ncmp expect output ' @@ -485,10 +480,9 @@ test_expect_success 'status -s -uall' ' test_cmp expect output ' test_expect_success 'status -s (status.showUntrackedFiles all)' ' - git config status.showuntrackedfiles all + test_config status.showuntrackedfiles all && git status -s >output && rm -rf dir3 && - git config --unset status.showuntrackedfiles && test_cmp expect output ' @@ -588,15 +582,13 @@ cat >expect <<\EOF EOF test_expect_success 'status with color.ui' ' - git config color.ui always && - test_when_finished "git config --unset color.ui" && + test_config color.ui always && git status | test_decode_color >output && test_i18ncmp expect output ' test_expect_success 'status with color.status' ' - git config color.status always && - test_when_finished "git config --unset color.status" && + test_config color.status always && git status | test_decode_color >output && test_i18ncmp expect output ' @@ -720,8 +712,7 @@ EOF test_expect_success 'status without relative paths' ' - git config status.relativePaths false && - test_when_finished "git config --unset status.relativePaths" && + test_config status.relativePaths false && (cd dir1 && git status) >output && test_i18ncmp expect output @@ -740,8 +731,7 @@ EOF test_expect_success 'status -s without relative paths' ' - git config status.relativePaths false && - test_when_finished "git config --unset status.relativePaths" && + test_config status.relativePaths false && (cd dir1 && git status -s) >output && test_cmp expect output @@ -1038,15 +1028,14 @@ test_expect_success '--ignore-submodules=untracked suppresses submodules with un ' test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' ' - git config diff.ignoreSubmodules dirty && + test_config diff.ignoreSubmodules dirty && git status >output && test_i18ncmp expect output && git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && git status >output && test_i18ncmp expect output && - git config -f .gitmodules --remove-section submodule.subname && - git config --unset diff.ignoreSubmodules + git config -f .gitmodules --remove-section submodule.subname ' test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' ' @@ -1066,15 +1055,14 @@ test_expect_success '--ignore-submodules=dirty suppresses submodules with untrac ' test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' ' - git config diff.ignoreSubmodules dirty && + test_config diff.ignoreSubmodules dirty && git status >output && ! test -s actual && git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && git status >output && test_i18ncmp expect output && - git config -f .gitmodules --remove-section submodule.subname && - git config --unset diff.ignoreSubmodules + git config -f .gitmodules --remove-section submodule.subname ' test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' ' @@ -1291,15 +1279,13 @@ cat > expect << EOF EOF test_expect_success "status (core.commentchar with submodule summary)" ' - test_when_finished "git config --unset core.commentchar" && - git config core.commentchar ";" && + test_config core.commentchar ";" && git status >output && test_i18ncmp expect output ' test_expect_success "status (core.commentchar with two chars with submodule summary)" ' - test_when_finished "git config --unset core.commentchar" && - git config core.commentchar ";;" && + test_config core.commentchar ";;" && git status >output && test_i18ncmp expect output ' diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 9d4610629d..06749a6aa0 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -77,7 +77,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts' ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) @@ -104,7 +104,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' ' echo three >main.txt && git add main.txt && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # @@ -136,7 +136,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' ' ONTO=$(git rev-parse --short rebase_i_conflicts) && test_must_fail git rebase -i rebase_i_conflicts && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) @@ -162,7 +162,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' ' test_must_fail git rebase -i rebase_i_conflicts && git add main.txt && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # @@ -188,9 +188,10 @@ test_expect_success 'status when rebasing -i in edit mode' ' export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~2) && + TGT=$(git rev-parse --short two_rebase_i) && git rebase -i HEAD~2 && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $TGT # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -215,8 +216,9 @@ test_expect_success 'status when splitting a commit' ' ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && + TGT=$(git rev-parse --short HEAD) && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $TGT # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -244,10 +246,11 @@ test_expect_success 'status after editing the last commit with --amend during a export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~3) && + TGT=$(git rev-parse --short three_amend) && git rebase -i HEAD~3 && git commit --amend -m "foo" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $TGT # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -277,7 +280,7 @@ test_expect_success 'status: (continue first edit) second edit' ' git rebase -i HEAD~3 && git rebase --continue && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -299,7 +302,7 @@ test_expect_success 'status: (continue first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -326,7 +329,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "foo" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -348,7 +351,7 @@ test_expect_success 'status: (amend first edit) second edit' ' git commit --amend -m "a" && git rebase --continue && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -371,7 +374,7 @@ test_expect_success 'status: (amend first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -399,7 +402,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "d" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -423,7 +426,7 @@ test_expect_success 'status: (split first edit) second edit' ' git commit -m "e" && git rebase --continue && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -448,7 +451,7 @@ test_expect_success 'status: (split first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -478,7 +481,7 @@ test_expect_success 'status: (split first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "h" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -573,9 +576,10 @@ test_expect_success 'status when bisecting' ' git bisect start && git bisect bad && git bisect good one_bisect && - cat >expected <<-\EOF && - # Not currently on any branch. - # You are currently bisecting branch '\''bisect'\''. + TGT=$(git rev-parse --short two_bisect) && + cat >expected <<-EOF && + # HEAD detached at $TGT + # You are currently bisecting, started from branch '\''bisect'\''. # (use "git bisect reset" to get back to the original branch) # nothing to commit (use -u to show untracked files) @@ -597,7 +601,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' ' ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''. # # Unmerged paths: @@ -663,5 +667,15 @@ test_expect_success 'status when cherry-picking after resolving conflicts' ' test_i18ncmp expected actual ' +test_expect_success 'status showing detached from a tag' ' + test_commit atag tagging && + git checkout atag && + cat >expected <<-\EOF + # HEAD detached at atag + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 5e19598fe7..2f70433568 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -56,7 +56,8 @@ create_merge_msgs () { echo && git log --no-merges ^HEAD c2 c3 } >squash.1-5-9 && - echo >msg.nolog && + : >msg.nologff && + echo >msg.nolognoff && { echo "* tag 'c3':" && echo " commit 3" && @@ -244,8 +245,7 @@ test_expect_success 'merges with --ff-only' ' test_expect_success 'merges with merge.ff=only' ' git reset --hard c1 && test_tick && - test_when_finished "git config --unset merge.ff" && - git config merge.ff only && + test_config merge.ff "only" && test_must_fail git merge c2 && test_must_fail git merge c3 && test_must_fail git merge c2 c3 && @@ -336,7 +336,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (no-commit in config)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--no-commit" && + test_config branch.master.mergeoptions "--no-commit" && git merge c2 && verify_merge file result.1-5 && verify_head $c1 && @@ -346,12 +346,11 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' ' test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (log in config)' ' - git config branch.master.mergeoptions "" && git reset --hard c1 && git merge --log c2 && git show -s --pretty=tformat:%s%n%b >expect && - git config branch.master.mergeoptions --log && + test_config branch.master.mergeoptions "--log" && git reset --hard c1 && git merge c2 && git show -s --pretty=tformat:%s%n%b >actual && @@ -360,17 +359,12 @@ test_expect_success 'merge c1 with c2 (log in config)' ' ' test_expect_success 'merge c1 with c2 (log in config gets overridden)' ' - test_when_finished "git config --remove-section branch.master" && - test_when_finished "git config --remove-section merge" && - test_might_fail git config --remove-section branch.master && - test_might_fail git config --remove-section merge && - git reset --hard c1 && git merge c2 && git show -s --pretty=tformat:%s%n%b >expect && - git config branch.master.mergeoptions "--no-log" && - git config merge.log true && + test_config branch.master.mergeoptions "--no-log" && + test_config merge.log "true" && git reset --hard c1 && git merge c2 && git show -s --pretty=tformat:%s%n%b >actual && @@ -380,7 +374,7 @@ test_expect_success 'merge c1 with c2 (log in config gets overridden)' ' test_expect_success 'merge c1 with c2 (squash in config)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--squash" && + test_config branch.master.mergeoptions "--squash" && git merge c2 && verify_merge file result.1-5 && verify_head $c1 && @@ -392,7 +386,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'override config option -n with --summary' ' git reset --hard c1 && - git config branch.master.mergeoptions "-n" && + test_config branch.master.mergeoptions "-n" && test_tick && git merge --summary c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -406,7 +400,7 @@ test_expect_success 'override config option -n with --summary' ' test_expect_success 'override config option -n with --stat' ' git reset --hard c1 && - git config branch.master.mergeoptions "-n" && + test_config branch.master.mergeoptions "-n" && test_tick && git merge --stat c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -422,7 +416,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'override config option --stat' ' git reset --hard c1 && - git config branch.master.mergeoptions "--stat" && + test_config branch.master.mergeoptions "--stat" && test_tick && git merge -n c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -438,7 +432,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (override --no-commit)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--no-commit" && + test_config branch.master.mergeoptions "--no-commit" && test_tick && git merge --commit c2 && verify_merge file result.1-5 msg.1-5 && @@ -449,7 +443,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (override --squash)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--squash" && + test_config branch.master.mergeoptions "--squash" && test_tick && git merge --no-squash c2 && verify_merge file result.1-5 msg.1-5 && @@ -460,7 +454,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (no-ff)' ' git reset --hard c0 && - git config branch.master.mergeoptions "" && test_tick && git merge --no-ff c1 && verify_merge file result.1 && @@ -471,10 +464,9 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (merge.ff=false)' ' git reset --hard c0 && - git config merge.ff false && + test_config merge.ff "false" && test_tick && git merge c1 && - git config --remove-section merge && verify_merge file result.1 && verify_parents $c0 $c1 ' @@ -482,22 +474,19 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'combine branch.master.mergeoptions with merge.ff' ' git reset --hard c0 && - git config branch.master.mergeoptions --ff && - git config merge.ff false && + test_config branch.master.mergeoptions "--ff" && + test_config merge.ff "false" && test_tick && git merge c1 && - git config --remove-section "branch.master" && - git config --remove-section "merge" && verify_merge file result.1 && verify_parents "$c0" ' test_expect_success 'tolerate unknown values for merge.ff' ' git reset --hard c0 && - git config merge.ff something-new && + test_config merge.ff "something-new" && test_tick && git merge c1 2>message && - git config --remove-section "merge" && verify_head "$c1" && test_cmp empty message ' @@ -515,7 +504,7 @@ test_expect_success 'combining --ff-only and --no-ff is refused' ' test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' git reset --hard c0 && - git config branch.master.mergeoptions "--no-ff" && + test_config branch.master.mergeoptions "--no-ff" && git merge --ff c1 && verify_merge file result.1 && verify_head $c1 @@ -525,14 +514,20 @@ test_expect_success 'merge log message' ' git reset --hard c0 && git merge --no-log c2 && git show -s --pretty=format:%b HEAD >msg.act && - test_cmp msg.nolog msg.act && + test_cmp msg.nologff msg.act && + + git reset --hard c0 && + test_config branch.master.mergeoptions "--no-ff" && + git merge --no-log c2 && + git show -s --pretty=format:%b HEAD >msg.act && + test_cmp msg.nolognoff msg.act && git merge --log c3 && git show -s --pretty=format:%b HEAD >msg.act && test_cmp msg.log msg.act && git reset --hard HEAD^ && - git config merge.log yes && + test_config merge.log "yes" && git merge c3 && git show -s --pretty=format:%b HEAD >msg.act && test_cmp msg.log msg.act @@ -542,7 +537,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c0, c2, c0, and c1' ' git reset --hard c1 && - git config branch.master.mergeoptions "" && test_tick && git merge c0 c2 c0 c1 && verify_merge file result.1-5 && @@ -553,7 +547,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c0, c2, c0, and c1' ' git reset --hard c1 && - git config branch.master.mergeoptions "" && test_tick && git merge c0 c2 c0 c1 && verify_merge file result.1-5 && @@ -564,7 +557,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c1 and c2' ' git reset --hard c1 && - git config branch.master.mergeoptions "" && test_tick && git merge c1 c2 && verify_merge file result.1-5 && diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 3aab6e1500..a6bd99eaf5 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,16 +23,6 @@ prompt_given () test "$prompt" = "Launch 'test-tool' [Y/n]: branch" } -stdin_contains () -{ - grep >/dev/null "$1" -} - -stdin_doesnot_contain () -{ - ! stdin_contains "$1" -} - # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && @@ -296,24 +286,24 @@ test_expect_success PERL 'setup with 2 files different' ' test_expect_success PERL 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch output && - stdin_contains m2 input && git difftool -x cat branch output && - stdin_contains master output && - stdin_contains tool >file && echo test >>sub/sub && - git add . && + git add file sub/sub && git commit -m "modified both" ' -test_expect_success PERL 'difftool -d' ' - git difftool -d --extcmd ls branch >output && - stdin_contains sub output && + grep sub output && + grep file output ' -test_expect_success PERL 'difftool --dir-diff' ' - git difftool --dir-diff --extcmd ls branch >output && - stdin_contains sub output && + grep sub output && + grep file output ' -test_expect_success PERL 'difftool --dir-diff ignores --prompt' ' - git difftool --dir-diff --prompt --extcmd ls branch >output && - stdin_contains sub output && + grep sub output && + grep file output ' -test_expect_success PERL 'difftool --dir-diff from subdirectory' ' +run_dir_diff_test 'difftool --dir-diff from subdirectory' ' ( cd sub && - git difftool --dir-diff --extcmd ls branch >output && - stdin_contains sub output && + grep sub output && + grep file output + ) +' + +write_script .git/CHECK_SYMLINKS <<\EOF +for f in file file2 sub/sub +do + echo "$f" + readlink "$2/$f" +done >actual +EOF + +test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' + cat >expect <<-EOF && + file + $(pwd)/file + file2 + $(pwd)/file2 + sub/sub + $(pwd)/sub/sub + EOF + git difftool --dir-diff --symlink \ + --extcmd "./.git/CHECK_SYMLINKS" branch HEAD && + test_cmp actual expect +' + +write_script modify-file <<\EOF +echo "new content" >file +EOF + +test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' + echo "orig content" >file && + git difftool --dir-diff --no-symlinks --extcmd "$(pwd)/modify-file" branch && + echo "new content" >expect && + test_cmp expect file +' + +write_script modify-both-files <<\EOF +echo "wt content" >file && +echo "tmp content" >"$2/file" && +echo "$2" >tmpdir +EOF + +test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' + ( + TMPDIR=$TRASH_DIRECTORY && + export TMPDIR && + echo "orig content" >file && + test_must_fail git difftool --dir-diff --no-symlinks --extcmd "$(pwd)/modify-both-files" branch && + echo "wt content" >expect && + test_cmp expect file && + echo "tmp content" >expect && + test_cmp expect "$(cat tmpdir)/file" ) ' diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh index a8957782cf..e1951a5cbb 100755 --- a/t/t7811-grep-open.sh +++ b/t/t7811-grep-open.sh @@ -125,11 +125,6 @@ test_expect_success 'modified file' ' test_cmp empty out ' -test_config() { - git config "$1" "$2" && - test_when_finished "git config --unset $1" -} - test_expect_success 'copes with color settings' ' rm -f actual && echo grep.h >expect && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 90bb6050c1..6783c14c1a 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -539,8 +539,7 @@ test_expect_success \ test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "ISO-8859-1" >> file && git add file && - git config i18n.commitencoding ISO-8859-1 && - test_when_finished "git config --unset i18n.commitencoding" && + test_config i18n.commitencoding ISO-8859-1 && git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt && gitweb_run "p=.git;a=commit"' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index fa62d010f6..42c2258b4f 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -91,6 +91,10 @@ q_to_tab () { tr Q '\011' } +qz_to_tab_space () { + tr QZ '\011\040' +} + append_cr () { sed -e 's/$/Q/' | tr Q '\015' } @@ -135,12 +139,12 @@ test_pause () { fi } -# Call test_commit with the arguments " [ []]" +# Call test_commit with the arguments " [ [ []]]" # # This will commit a file with the given contents and the given commit -# message. It will also add a tag with as name. +# message, and tag the resulting commit with the given tag name. # -# Both and default to . +# , , and all default to . test_commit () { notick= && @@ -168,7 +172,7 @@ test_commit () { test_tick fi && git commit $signoff -m "$1" && - git tag "$1" + git tag "${4:-$1}" } # Call test_merge with the arguments " ", where diff --git a/transport.c b/transport.c index eb9eb33e0f..ba5d8afb1b 100644 --- a/transport.c +++ b/transport.c @@ -508,7 +508,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus struct ref *refs; connect_setup(transport, for_push, 0); - get_remote_heads(data->fd[0], &refs, + get_remote_heads(data->fd[0], NULL, 0, &refs, for_push ? REF_NORMAL : 0, &data->extra_have); data->got_remote_heads = 1; @@ -537,7 +537,7 @@ static int fetch_refs_via_pack(struct transport *transport, if (!data->got_remote_heads) { connect_setup(transport, 0, 0); - get_remote_heads(data->fd[0], &refs_tmp, 0, NULL); + get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, NULL); data->got_remote_heads = 1; } @@ -795,7 +795,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re struct ref *tmp_refs; connect_setup(transport, 1, 0); - get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL); + get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL); data->got_remote_heads = 1; } diff --git a/upload-pack.c b/upload-pack.c index f5673ee4c2..bfa6279cc4 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -44,7 +44,6 @@ static unsigned int timeout; * otherwise maximum packet size (up to 65520 bytes). */ static int use_sideband; -static int debug_fd; static int advertise_refs; static int stateless_rpc; @@ -53,13 +52,6 @@ static void reset_timeout(void) alarm(timeout); } -static int strip(char *line, int len) -{ - if (len && line[len-1] == '\n') - line[--len] = 0; - return len; -} - static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { if (use_sideband) @@ -72,7 +64,8 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz) xwrite(fd, data, sz); return sz; } - return safe_write(fd, data, sz); + write_or_die(fd, data, sz); + return sz; } static FILE *pack_pipe = NULL; @@ -415,7 +408,6 @@ static int ok_to_give_up(void) static int get_common_commits(void) { - static char line[1000]; unsigned char sha1[20]; char last_hex[41]; int got_common = 0; @@ -425,10 +417,10 @@ static int get_common_commits(void) save_commit_buffer = 0; for (;;) { - int len = packet_read_line(0, line, sizeof(line)); + char *line = packet_read_line(0, NULL); reset_timeout(); - if (!len) { + if (!line) { if (multi_ack == 2 && got_common && !got_other && ok_to_give_up()) { sent_ready = 1; @@ -447,7 +439,6 @@ static int get_common_commits(void) got_other = 0; continue; } - strip(line, len); if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ @@ -581,36 +572,33 @@ static void check_non_tip(void) static void receive_needs(void) { struct object_array shallows = OBJECT_ARRAY_INIT; - static char line[1000]; - int len, depth = 0; + int depth = 0; int has_non_tip = 0; shallow_nr = 0; - if (debug_fd) - write_str_in_full(debug_fd, "#S\n"); for (;;) { struct object *o; const char *features; unsigned char sha1_buf[20]; - len = packet_read_line(0, line, sizeof(line)); + char *line = packet_read_line(0, NULL); reset_timeout(); - if (!len) + if (!line) break; - if (debug_fd) - write_in_full(debug_fd, line, len); if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; - if (get_sha1(line + 8, sha1)) + if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); object = parse_object(sha1); if (!object) die("did not find object for %s", line); if (object->type != OBJ_COMMIT) die("invalid shallow object %s", sha1_to_hex(sha1)); - object->flags |= CLIENT_SHALLOW; - add_object_array(object, NULL, &shallows); + if (!(object->flags & CLIENT_SHALLOW)) { + object->flags |= CLIENT_SHALLOW; + add_object_array(object, NULL, &shallows); + } continue; } if (!prefixcmp(line, "deepen ")) { @@ -657,8 +645,6 @@ static void receive_needs(void) add_object_array(o, NULL, &want_obj); } } - if (debug_fd) - write_str_in_full(debug_fd, "#E\n"); /* * We have sent all our refs already, and the other end @@ -854,8 +840,6 @@ int main(int argc, char **argv) if (is_repository_shallow()) die("attempt to fetch/clone from a shallow repository"); git_config(upload_pack_config, NULL); - if (getenv("GIT_DEBUG_SEND_PACK")) - debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK")); upload_pack(); return 0; } diff --git a/write_or_die.c b/write_or_die.c index 960f448cff..b50f99a936 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -1,5 +1,15 @@ #include "cache.h" +static void check_pipe(int err) +{ + if (err == EPIPE) { + signal(SIGPIPE, SIG_DFL); + raise(SIGPIPE); + /* Should never happen, but just in case... */ + exit(141); + } +} + /* * Some cases use stdio, but want to flush after the write * to get error handling (and to get better interactive @@ -34,8 +44,7 @@ void maybe_flush_or_die(FILE *f, const char *desc) return; } if (fflush(f)) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); die_errno("write failure on '%s'", desc); } } @@ -50,8 +59,7 @@ void fsync_or_die(int fd, const char *msg) void write_or_die(int fd, const void *buf, size_t count) { if (write_in_full(fd, buf, count) < 0) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); die_errno("write error"); } } @@ -59,8 +67,7 @@ void write_or_die(int fd, const void *buf, size_t count) int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg) { if (write_in_full(fd, buf, count) < 0) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); fprintf(stderr, "%s: write error (%s)\n", msg, strerror(errno)); return 0; diff --git a/wt-status.c b/wt-status.c index 54f4391f9c..cea8e55d8b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -971,7 +971,7 @@ static void show_bisect_in_progress(struct wt_status *s, { if (state->branch) status_printf_ln(s, color, - _("You are currently bisecting branch '%s'."), + _("You are currently bisecting, started from branch '%s'."), state->branch); else status_printf_ln(s, color, @@ -985,96 +985,164 @@ static void show_bisect_in_progress(struct wt_status *s, /* * Extract branch information from rebase/bisect */ -static void read_and_strip_branch(struct strbuf *sb, - const char **branch, - const char *path) +static char *read_and_strip_branch(const char *path) { + struct strbuf sb = STRBUF_INIT; unsigned char sha1[20]; - strbuf_reset(sb); - if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0) - return; + if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0) + goto got_nothing; - while (sb->len && sb->buf[sb->len - 1] == '\n') - strbuf_setlen(sb, sb->len - 1); - if (!sb->len) - return; - if (!prefixcmp(sb->buf, "refs/heads/")) - *branch = sb->buf + strlen("refs/heads/"); - else if (!prefixcmp(sb->buf, "refs/")) - *branch = sb->buf; - else if (!get_sha1_hex(sb->buf, sha1)) { + while (&sb.len && sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + if (!sb.len) + goto got_nothing; + if (!prefixcmp(sb.buf, "refs/heads/")) + strbuf_remove(&sb,0, strlen("refs/heads/")); + else if (!prefixcmp(sb.buf, "refs/")) + ; + else if (!get_sha1_hex(sb.buf, sha1)) { const char *abbrev; abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV); - strbuf_reset(sb); - strbuf_addstr(sb, abbrev); - *branch = sb->buf; - } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */ - ; + strbuf_reset(&sb); + strbuf_addstr(&sb, abbrev); + } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */ + goto got_nothing; else /* bisect */ - *branch = sb->buf; + ; + return strbuf_detach(&sb, NULL); + +got_nothing: + strbuf_release(&sb); + return NULL; } -static void wt_status_print_state(struct wt_status *s) +struct grab_1st_switch_cbdata { + int found; + struct strbuf buf; + unsigned char nsha1[20]; +}; + +static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) { - const char *state_color = color(WT_STATUS_HEADER, s); - struct strbuf branch = STRBUF_INIT; - struct strbuf onto = STRBUF_INIT; - struct wt_status_state state; - struct stat st; + struct grab_1st_switch_cbdata *cb = cb_data; + const char *target = NULL, *end; - memset(&state, 0, sizeof(state)); + if (prefixcmp(message, "checkout: moving from ")) + return 0; + message += strlen("checkout: moving from "); + target = strstr(message, " to "); + if (!target) + return 0; + target += strlen(" to "); + strbuf_reset(&cb->buf); + hashcpy(cb->nsha1, nsha1); + for (end = target; *end && *end != '\n'; end++) + ; + strbuf_add(&cb->buf, target, end - target); + cb->found = 1; + return 1; +} + +static void wt_status_get_detached_from(struct wt_status_state *state) +{ + struct grab_1st_switch_cbdata cb; + struct commit *commit; + unsigned char sha1[20]; + char *ref = NULL; + + strbuf_init(&cb.buf, 0); + if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) { + strbuf_release(&cb.buf); + return; + } + + if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 && + /* sha1 is a commit? match without further lookup */ + (!hashcmp(cb.nsha1, sha1) || + /* perhaps sha1 is a tag, try to dereference to a commit */ + ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL && + !hashcmp(cb.nsha1, commit->object.sha1)))) { + int ofs; + if (!prefixcmp(ref, "refs/tags/")) + ofs = strlen("refs/tags/"); + else if (!prefixcmp(ref, "refs/remotes/")) + ofs = strlen("refs/remotes/"); + else + ofs = 0; + state->detached_from = xstrdup(ref + ofs); + } else + state->detached_from = + xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV)); + hashcpy(state->detached_sha1, cb.nsha1); + + free(ref); + strbuf_release(&cb.buf); +} + +void wt_status_get_state(struct wt_status_state *state, + int get_detached_from) +{ + struct stat st; if (!stat(git_path("MERGE_HEAD"), &st)) { - state.merge_in_progress = 1; + state->merge_in_progress = 1; } else if (!stat(git_path("rebase-apply"), &st)) { if (!stat(git_path("rebase-apply/applying"), &st)) { - state.am_in_progress = 1; + state->am_in_progress = 1; if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size) - state.am_empty_patch = 1; + state->am_empty_patch = 1; } else { - state.rebase_in_progress = 1; - read_and_strip_branch(&branch, &state.branch, - "rebase-apply/head-name"); - read_and_strip_branch(&onto, &state.onto, - "rebase-apply/onto"); + state->rebase_in_progress = 1; + state->branch = read_and_strip_branch("rebase-apply/head-name"); + state->onto = read_and_strip_branch("rebase-apply/onto"); } } else if (!stat(git_path("rebase-merge"), &st)) { if (!stat(git_path("rebase-merge/interactive"), &st)) - state.rebase_interactive_in_progress = 1; + state->rebase_interactive_in_progress = 1; else - state.rebase_in_progress = 1; - read_and_strip_branch(&branch, &state.branch, - "rebase-merge/head-name"); - read_and_strip_branch(&onto, &state.onto, - "rebase-merge/onto"); + state->rebase_in_progress = 1; + state->branch = read_and_strip_branch("rebase-merge/head-name"); + state->onto = read_and_strip_branch("rebase-merge/onto"); } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) { - state.cherry_pick_in_progress = 1; + state->cherry_pick_in_progress = 1; } if (!stat(git_path("BISECT_LOG"), &st)) { - state.bisect_in_progress = 1; - read_and_strip_branch(&branch, &state.branch, - "BISECT_START"); + state->bisect_in_progress = 1; + state->branch = read_and_strip_branch("BISECT_START"); } - if (state.merge_in_progress) - show_merge_in_progress(s, &state, state_color); - else if (state.am_in_progress) - show_am_in_progress(s, &state, state_color); - else if (state.rebase_in_progress || state.rebase_interactive_in_progress) - show_rebase_in_progress(s, &state, state_color); - else if (state.cherry_pick_in_progress) - show_cherry_pick_in_progress(s, &state, state_color); - if (state.bisect_in_progress) - show_bisect_in_progress(s, &state, state_color); - strbuf_release(&branch); - strbuf_release(&onto); + if (get_detached_from) + wt_status_get_detached_from(state); +} + +static void wt_status_print_state(struct wt_status *s, + struct wt_status_state *state) +{ + const char *state_color = color(WT_STATUS_HEADER, s); + if (state->merge_in_progress) + show_merge_in_progress(s, state, state_color); + else if (state->am_in_progress) + show_am_in_progress(s, state, state_color); + else if (state->rebase_in_progress || state->rebase_interactive_in_progress) + show_rebase_in_progress(s, state, state_color); + else if (state->cherry_pick_in_progress) + show_cherry_pick_in_progress(s, state, state_color); + if (state->bisect_in_progress) + show_bisect_in_progress(s, state, state_color); } void wt_status_print(struct wt_status *s) { const char *branch_color = color(WT_STATUS_ONBRANCH, s); const char *branch_status_color = color(WT_STATUS_HEADER, s); + struct wt_status_state state; + + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, + s->branch && !strcmp(s->branch, "HEAD")); if (s->branch) { const char *on_what = _("On branch "); @@ -1082,9 +1150,19 @@ void wt_status_print(struct wt_status *s) if (!prefixcmp(branch_name, "refs/heads/")) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { - branch_name = ""; branch_status_color = color(WT_STATUS_NOBRANCH, s); - on_what = _("Not currently on any branch."); + if (state.detached_from) { + unsigned char sha1[20]; + branch_name = state.detached_from; + if (!get_sha1("HEAD", sha1) && + !hashcmp(sha1, state.detached_sha1)) + on_what = _("HEAD detached at "); + else + on_what = _("HEAD detached from "); + } else { + branch_name = ""; + on_what = _("Not currently on any branch."); + } } status_printf(s, color(WT_STATUS_HEADER, s), ""); status_printf_more(s, branch_status_color, "%s", on_what); @@ -1093,7 +1171,11 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } - wt_status_print_state(s); + wt_status_print_state(s, &state); + free(state.branch); + free(state.onto); + free(state.detached_from); + if (s->is_initial) { status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit")); diff --git a/wt-status.h b/wt-status.h index 74208c06fd..be7a016173 100644 --- a/wt-status.h +++ b/wt-status.h @@ -80,13 +80,16 @@ struct wt_status_state { int rebase_interactive_in_progress; int cherry_pick_in_progress; int bisect_in_progress; - const char *branch; - const char *onto; + char *branch; + char *onto; + char *detached_from; + unsigned char detached_sha1[20]; }; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); +void wt_status_get_state(struct wt_status_state *state, int get_detached_from); void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); diff --git a/zlib.c b/zlib.c index 2b2c0c780e..bbaa0815ed 100644 --- a/zlib.c +++ b/zlib.c @@ -168,13 +168,8 @@ void git_deflate_init(git_zstream *strm, int level) strm->z.msg ? strm->z.msg : "no message"); } -void git_deflate_init_gzip(git_zstream *strm, int level) +static void do_git_deflate_init(git_zstream *strm, int level, int windowBits) { - /* - * Use default 15 bits, +16 is to generate gzip header/trailer - * instead of the zlib wrapper. - */ - const int windowBits = 15 + 16; int status; zlib_pre_call(strm); @@ -188,6 +183,24 @@ void git_deflate_init_gzip(git_zstream *strm, int level) strm->z.msg ? strm->z.msg : "no message"); } +void git_deflate_init_gzip(git_zstream *strm, int level) +{ + /* + * Use default 15 bits, +16 is to generate gzip header/trailer + * instead of the zlib wrapper. + */ + return do_git_deflate_init(strm, level, 15 + 16); +} + +void git_deflate_init_raw(git_zstream *strm, int level) +{ + /* + * Use default 15 bits, negate the value to get raw compressed + * data without zlib header and trailer. + */ + return do_git_deflate_init(strm, level, -15); +} + int git_deflate_abort(git_zstream *strm) { int status;