diff options
188 files changed, 5272 insertions, 1799 deletions
diff --git a/.gitattributes b/.gitattributes index 5e98806c6c..320e33c327 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ * whitespace=!indent,trail,space -*.[ch] whitespace=indent,trail,space +*.[ch] whitespace=indent,trail,space diff=cpp *.sh whitespace=indent,trail,space diff --git a/.travis.yml b/.travis.yml index c2b76f9b7e..477c3d2efb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ addons: packages: - language-pack-is - git-svn + - apache2 env: global: @@ -31,6 +32,7 @@ env: - DEFAULT_TEST_TARGET=prove - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" - GIT_TEST_OPTS="--verbose --tee" + - GIT_TEST_HTTPD=true - GIT_TEST_CLONE_2GB=YesPlease # t9810 occasionally fails on Travis CI OS X # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X diff --git a/Documentation/RelNotes/2.10.0.txt b/Documentation/RelNotes/2.10.0.txt index 4252eb7348..f9dfc27b33 100644 --- a/Documentation/RelNotes/2.10.0.txt +++ b/Documentation/RelNotes/2.10.0.txt @@ -81,6 +81,9 @@ UI, Workflows & Features format. (merge 5caeeb8 jk/big-and-future-archive-tar later to maint). + * A new configuration variable core.sshCommand has been added to + specify what value for GIT_SSH_COMMAND to use per repository. + Performance, Internal Implementation, Development Support etc. @@ -141,6 +144,67 @@ Performance, Internal Implementation, Development Support etc. * Allow t/perf framework to use the features from the most recent version of Git even when testing an older installed version. + * The commands in the "log/diff" family have had an FILE* pointer in the + data structure they pass around for a long time, but some codepaths + used to always write to the standard output. As a preparatory step + to make "git format-patch" available to the internal callers, these + codepaths have been updated to consistently write into that FILE* + instead. + + * Conversion from unsigned char sha1[20] to struct object_id + continues. + + * Improve the look of the way "git fetch" reports what happened to + each ref that was fetched. + (merge bc437d1 nd/fetch-ref-summary later to maint). + + * The .c/.h sources are marked as such in our .gitattributes file so + that "git diff -W" and friends would work better. + (merge e82675a rs/help-c-source-with-gitattributes later to maint). + + * Code clean-up to avoid using a variable string that compilers may + feel untrustable as printf-style format given to write_file() + helper function. + + * "git p4" used a location outside $GIT_DIR/refs/ to place its + temporary branches, which has been moved to refs/git-p4-tmp/. + + * Existing autoconf generated test for the need to link with pthread + library did not check all the functions from pthread libraries; + recent FreeBSD has some functions in libc but not others, and we + mistakenly thought linking with libc is enough when it is not. + (merge a9b02de ew/autoconf-pthread later to maint). + + * When "git fsck" reports a broken link (e.g. a tree object contains + a blob that does not exist), both containing object and the object + that is referred to were reported with their 40-hex object names. + The command learned the "--name-objects" option to show the path to + the containing object from existing refs (e.g. "HEAD~24^2:file.txt"). + + * Allow http daemon tests in Travis CI tests. + (merge d9d1426 ls/travis-enable-httpd-tests later to maint). + + * Makefile assumed that -lrt is always available on platforms that + want to use clock_gettime() and CLOCK_MONOTONIC, which is not a + case for recent Mac OS X. The necessary symbols are often found in + libc on many modern systems and having -lrt on the command line, as + long as the library exists, had no effect, but when the platform + removes librt.a that is a different matter--having -lrt will break + the linkage. + + This change could be seen as a regression for those who do need to + specify -lrt, as they now specifically ask for NEEDS_LIBRT when + building. Hopefully they are in the minority these days. + + * Further preparatory work on the refs API before the pluggable + backend series can land. + + * Error handling in the codepaths that updates refs has been + improved. + + * The API to iterate over all the refs (i.e. for_each_ref(), etc.) + has been revamped. + Also contains various documentation updates and code clean-ups. @@ -280,6 +344,76 @@ notes for details). tree files. But we did so by mistake, which has been corrected. (merge b8e47d1 nd/ita-cleanup later to maint). + * "git blame -M" missed a single line that was moved within the file. + (merge 17a07e2 dk/blame-move-no-reason-for-1-line-context later to maint). + + * Fix recently introduced codepaths that are involved in parallel + submodule operations, which gave up on reading too early, and + could have wasted CPU while attempting to write under a corner + case condition. + (merge d751dd1 sb/submodule-parallel-fetch later to maint). + + * "git grep -i" has been taught to fold case in non-ascii locales + correctly. + (merge 695f95b nd/icase later to maint). + + * A test that unconditionally used "mktemp" learned that the command + is not necessarily available everywhere. + (merge c578a09 ak/lazy-prereq-mktemp later to maint). + + * There are certain house-keeping tasks that need to be performed at + the very beginning of any Git program, and programs that are not + built-in commands had to do them exactly the same way as "git" + potty does. It was easy to make mistakes in one-off standalone + programs (like test helpers). A common "main()" function that + calls cmd_main() of individual program has been introduced to + make it harder to make mistakes. + (merge de61ceb jk/common-main later to maint). + + * The test framework learned a new helper test_match_signal to + check an exit code from getting killed by an expected signal. + (merge 03c39b3 jk/test-match-signal later to maint). + + * General code clean-up around a helper function to write a + single-liner to a file. + (merge 7eb6e10 jk/write-file later to maint). + + * One part of "git am" had an oddball helper function that called + stuff from outside "his" as opposed to calling what we have "ours", + which was not gender-neutral and also inconsistent with the rest of + the system where outside stuff is usuall called "theirs" in + contrast to "ours". + (merge 715a51b js/am-call-theirs-theirs-in-fallback-3way later to maint). + + * "git blame file" allowed the lineage of lines in the uncommitted, + unadded contents of "file" to be inspected, but it refused when + "file" did not appear in the current commit. When "file" was + created by renaming an existing file (but the change has not been + committed), this restriction was unnecessarily tight. + (merge c66b470 mh/blame-worktree later to maint). + + * "git add -N dir/file && git write-tree" produced an incorrect tree + when there are other paths in the same directory that sorts after + "file". + (merge 6d6a782 nd/cache-tree-ita later to maint). + + * "git fetch http://user:pass@host/repo..." scrubbed the userinfo + part, but "git push" didn't. + (merge 68f3c07 jk/push-scrub-url later to maint). + + * "git merge" with renormalization did not work well with + merge-recursive, due to "safer crlf" conversion kicking in when it + shouldn't. + (merge 1335d76 jc/renormalize-merge-kill-safer-crlf later to maint). + + * The use of strbuf in "git rm" to build filename to remove was a bit + suboptimal, which has been fixed. + (merge deb8e15 rs/rm-strbuf-optim later to maint). + + * An age old bug that caused "git diff --ignore-space-at-eol" + misbehave has been fixed. + (merge 044fb19 js/ignore-space-at-eol later to maint). + * Other minor clean-ups and documentation updates (merge e51b0df pb/commit-editmsg-path later to maint). (merge b333d0d jk/send-pack-stdio later to maint). @@ -287,3 +421,8 @@ notes for details). (merge c2691e2 ah/unpack-trees-advice-messages later to maint). (merge 82f6178 nd/doc-new-command later to maint). (merge fa90ab4 js/t3404-grammo-fix later to maint). + (merge c61b2af lf/recv-sideband-cleanup later to maint). + (merge 31471ba rs/use-strbuf-addbuf later to maint). + (merge 503e224 nd/test-helpers later to maint). + (merge 16726cf jc/doc-diff-filter-exclude later to maint). + (merge fd2e7da rs/worktree-use-strbuf-absolute-path later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 16dc22d9cf..8b1aee4b3b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -412,13 +412,11 @@ file with mixed line endings would be reported by the `core.safecrlf` mechanism. core.autocrlf:: - Setting this variable to "true" is almost the same as setting - the `text` attribute to "auto" on all files except that text - files are not guaranteed to be normalized: files that contain - `CRLF` in the repository will not be touched. Use this - setting if you want to have `CRLF` line endings in your - working directory even though the repository does not have - normalized line endings. This variable can be set to 'input', + Setting this variable to "true" is the same as setting + the `text` attribute to "auto" on all files and core.eol to "crlf". + Set to true if you want to have `CRLF` line endings in your + working directory and the repository has LF line endings. + This variable can be set to 'input', in which case no output conversion is performed. core.symlinks:: @@ -450,6 +448,13 @@ specify that no proxy be used for a given domain pattern. This is useful for excluding servers inside a firewall from proxy use, while defaulting to a common proxy for external domains. +core.sshCommand:: + If this variable is set, `git fetch` and `git push` will + use the specified command instead of `ssh` when they need to + connect to a remote system. The command is in the same form as + the `GIT_SSH_COMMAND` environment variable and is overridden + when the environment variable is set. + core.ignoreStat:: If true, Git will avoid using lstat() calls to detect if files have changed by setting the "assume-unchanged" bit for those tracked files @@ -1236,6 +1241,11 @@ fetch.prune:: If true, fetch will automatically behave as if the `--prune` option was given on the command line. See also `remote.<name>.prune`. +fetch.output:: + Control how ref update status is printed. Valid values are + `full` and `compact`. Default value is `full`. See section + OUTPUT in linkgit:git-fetch[1] for detail. + format.attach:: Enable multipart/mixed attachments as the default for 'format-patch'. The value can also be a double quoted string diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index d9ae681d8f..705a873942 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -419,6 +419,9 @@ ifndef::git-format-patch[] paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected. ++ +Also, these upper-case letters can be downcased to exclude. E.g. +`--diff-filter=ad` excludes added and deleted paths. -S<string>:: Look for differences that change the number of occurrences of diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index efe56e0808..9e4216999d 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -99,6 +99,57 @@ The latter use of the `remote.<repository>.fetch` values can be overridden by giving the `--refmap=<refspec>` parameter(s) on the command line. +OUTPUT +------ + +The output of "git fetch" depends on the transport method used; this +section describes the output when fetching over the Git protocol +(either locally or via ssh) and Smart HTTP protocol. + +The status of the fetch is output in tabular form, with each line +representing the status of a single ref. Each line is of the form: + +------------------------------- + <flag> <summary> <from> -> <to> [<reason>] +------------------------------- + +The status of up-to-date refs is shown only if the --verbose option is +used. + +In compact output mode, specified with configuration variable +fetch.output, if either entire `<from>` or `<to>` is found in the +other string, it will be substituted with `*` in the other string. For +example, `master -> origin/master` becomes `master -> origin/*`. + +flag:: + A single character indicating the status of the ref: +(space);; for a successfully fetched fast-forward; +`+`;; for a successful forced update; +`-`;; for a successfully pruned ref; +`t`;; for a successful tag update; +`*`;; for a successfully fetched new ref; +`!`;; for a ref that was rejected or failed to update; and +`=`;; for a ref that was up to date and did not need fetching. + +summary:: + For a successfully fetched ref, the summary shows the old and new + values of the ref in a form suitable for using as an argument to + `git log` (this is `<old>..<new>` in most cases, and + `<old>...<new>` for forced non-fast-forward updates). + +from:: + The name of the remote ref being fetched from, minus its + `refs/<type>/` prefix. In the case of deletion, the name of + the remote ref is "(none)". + +to:: + The name of the local ref being updated, minus its + `refs/<type>/` prefix. + +reason:: + A human-readable explanation. In the case of successfully fetched + refs, no explanation is needed. For a failed ref, the reason for + failure is described. EXAMPLES -------- diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 7fc68eb319..b9f060e3b2 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -11,7 +11,8 @@ SYNOPSIS [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] [--[no-]full] [--strict] [--verbose] [--lost-found] - [--[no-]dangling] [--[no-]progress] [--connectivity-only] [<object>*] + [--[no-]dangling] [--[no-]progress] [--connectivity-only] + [--[no-]name-objects] [<object>*] DESCRIPTION ----------- @@ -82,6 +83,12 @@ index file, all SHA-1 references in `refs` namespace, and all reflogs a blob, the contents are written into the file, rather than its object name. +--name-objects:: + When displaying names of reachable objects, in addition to the + SHA-1 also display a name that describes *how* they are reachable, + compatible with linkgit:git-rev-parse[1], e.g. + `HEAD@{1234567890}~25^2:src/`. + --[no-]progress:: Progress status is reported on the standard error stream by default when it is attached to a terminal, unless diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 7c4cfb0885..0aeb020d02 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -11,7 +11,9 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree list' [--porcelain] +'git worktree lock' [--reason <string>] <worktree> 'git worktree prune' [-n] [-v] [--expire <expire>] +'git worktree unlock' <worktree> DESCRIPTION ----------- @@ -38,9 +40,8 @@ section "DETAILS" for more information. If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from -being pruned by creating a file named 'locked' alongside the other -administrative files, optionally containing a plain text reason that -pruning should be suppressed. See section "DETAILS" for more information. +being pruned by issuing the `git worktree lock` command, optionally +specifying `--reason` to explain why the working tree is locked. COMMANDS -------- @@ -62,10 +63,22 @@ each of the linked worktrees. The output details include if the worktree is bare, the revision currently checked out, and the branch currently checked out (or 'detached HEAD' if none). +lock:: + +If a working tree is on a portable device or network share which +is not always mounted, lock it to prevent its administrative +files from being pruned automatically. This also prevents it from +being moved or deleted. Optionally, specify a reason for the lock +with `--reason`. + prune:: Prune working tree information in $GIT_DIR/worktrees. +unlock:: + +Unlock a working tree, allowing it to be pruned, moved or deleted. + OPTIONS ------- @@ -111,6 +124,18 @@ OPTIONS --expire <time>:: With `prune`, only expire unused working trees older than <time>. +--reason <string>:: + With `lock`, an explanation why the working tree is locked. + +<worktree>:: + Working trees can be identified by path, either relative or + absolute. ++ +If the last path components in the working tree's path is unique among +working trees, it can be used to identify worktrees. For example if +you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg", +then "ghi" or "def/ghi" is enough to point to the former working tree. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's @@ -151,7 +176,8 @@ instead. To prevent a $GIT_DIR/worktrees entry from being pruned (which can be useful in some situations, such as when the -entry's working tree is stored on a portable device), add a file named +entry's working tree is stored on a portable device), use the +`git worktree lock` command, which adds a file named 'locked' to the entry's directory. The file contains the reason in plain text. For example, if a linked working tree's `.git` file points to `/path/main/.git/worktrees/test-next` then a file named @@ -227,8 +253,6 @@ performed manually, such as: - `remove` to remove a linked working tree and its administrative files (and warn if the working tree is dirty) - `mv` to move or rename a working tree and update its administrative files -- `lock` to prevent automatic pruning of administrative files (for instance, - for a working tree on a portable device) GIT --- diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 8882a3e914..b40068bdfd 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -115,6 +115,7 @@ text file is normalized, its line endings are converted to LF in the repository. To control what line ending style is used in the working directory, use the `eol` attribute for a single file and the `core.eol` configuration variable for all text files. +Note that `core.autocrlf` overrides `core.eol` Set:: @@ -130,8 +131,9 @@ Unset:: Set to string value "auto":: When `text` is set to "auto", the path is marked for automatic - end-of-line normalization. If Git decides that the content is - text, its line endings are normalized to LF on checkin. + end-of-line conversion. If Git decides that the content is + text, its line endings are converted to LF on checkin. + When the file has been commited with CRLF, no conversion is done. Unspecified:: @@ -146,7 +148,7 @@ unspecified. ^^^^^ This attribute sets a specific line-ending style to be used in the -working directory. It enables end-of-line normalization without any +working directory. It enables end-of-line conversion without any content checks, effectively setting the `text` attribute. Set to string value "crlf":: @@ -186,9 +188,10 @@ the working directory, and prevent .jpg files from being normalized regardless of their content. ------------------------ +* text=auto *.txt text -*.vcproj eol=crlf -*.sh eol=lf +*.vcproj text eol=crlf +*.sh text eol=lf *.jpg -text ------------------------ @@ -198,7 +201,7 @@ normalization in Git. If you simply want to have CRLF line endings in your working directory regardless of the repository you are working with, you can set the -config variable "core.autocrlf" without changing any attributes. +config variable "core.autocrlf" without using any attributes. ------------------------ [core] @@ -351,9 +351,12 @@ all:: # Define GMTIME_UNRELIABLE_ERRORS if your gmtime() function does not # return NULL when it receives a bogus time_t. # -# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime in librt. +# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime. # -# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC in librt. +# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC. +# +# Define NEEDS_LIBRT if your platform requires linking with librt (glibc version +# before 2.17) for clock_gettime and CLOCK_MONOTONIC. # # Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily # compiles the following initialization: @@ -718,6 +721,7 @@ LIB_OBJS += diff-lib.o LIB_OBJS += diff-no-index.o LIB_OBJS += diff.o LIB_OBJS += dir.o +LIB_OBJS += dir-iterator.o LIB_OBJS += editor.o LIB_OBJS += entry.o LIB_OBJS += environment.o @@ -782,6 +786,7 @@ LIB_OBJS += read-cache.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += refs/files-backend.o +LIB_OBJS += refs/iterator.o LIB_OBJS += ref-filter.o LIB_OBJS += remote.o LIB_OBJS += replace_object.o @@ -939,7 +944,7 @@ BUILTIN_OBJS += builtin/verify-tag.o BUILTIN_OBJS += builtin/worktree.o BUILTIN_OBJS += builtin/write-tree.o -GITLIBS = $(LIB_FILE) $(XDIFF_LIB) +GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = GIT_USER_AGENT = git/$(GIT_VERSION) @@ -1465,13 +1470,16 @@ endif ifdef HAVE_CLOCK_GETTIME BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME - EXTLIBS += -lrt endif ifdef HAVE_CLOCK_MONOTONIC BASIC_CFLAGS += -DHAVE_CLOCK_MONOTONIC endif +ifdef NEEDS_LIBRT + EXTLIBS += -lrt +endif + ifdef HAVE_BSD_SYSCTL BASIC_CFLAGS += -DHAVE_BSD_SYSCTL endif @@ -1572,7 +1580,15 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) DIFF_SQ = $(subst ','\'',$(DIFF)) PERLLIB_EXTRA_SQ = $(subst ','\'',$(PERLLIB_EXTRA)) -LIBS = $(GITLIBS) $(EXTLIBS) +# We must filter out any object files from $(GITLIBS), +# as it is typically used like: +# +# foo: foo.o $(GITLIBS) +# $(CC) $(filter %.o,$^) $(LIBS) +# +# where we use it as a dependency. Since we also pull object files +# from the dependency list, that would make each entry appear twice. +LIBS = $(filter-out %.o, $(GITLIBS)) $(EXTLIBS) BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ $(COMPAT_CFLAGS) @@ -1708,8 +1724,8 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = \ '-DGIT_INFO_PATH="$(infodir_relative_SQ)"' git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) git.o \ - $(BUILTIN_OBJS) $(LIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) help.sp help.s help.o: common-cmds.h @@ -1902,6 +1918,7 @@ TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \ $(XDIFF_OBJS) \ $(VCSSVN_OBJS) \ + common-main.o \ git.o ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o @@ -2228,17 +2245,9 @@ perf: all .PHONY: test perf -t/helper/test-ctype$X: ctype.o - -t/helper/test-date$X: date.o ctype.o - -t/helper/test-delta$X: diff-delta.o patch-delta.o - -t/helper/test-line-buffer$X: vcs-svn/lib.a - -t/helper/test-parse-options$X: parse-options.o parse-options-cb.o +t/helper/test-line-buffer$X: $(VCSSVN_LIB) -t/helper/test-svn-fe$X: vcs-svn/lib.a +t/helper/test-svn-fe$X: $(VCSSVN_LIB) .PRECIOUS: $(TEST_OBJS) @@ -646,7 +646,10 @@ static void exit_if_skipped_commits(struct commit_list *tried, printf("There are only 'skip'ped commits left to test.\n" "The first %s commit could be any of:\n", term_bad); - print_commit_list(tried, "%s\n", "%s\n"); + + for ( ; tried; tried = tried->next) + printf("%s\n", oid_to_hex(&tried->item->object.oid)); + if (bad) printf("%s\n", oid_to_hex(bad)); printf(_("We cannot bisect more!\n")); @@ -754,7 +757,7 @@ static void handle_bad_merge_base(void) static void handle_skipped_merge_base(const unsigned char *mb) { char *mb_hex = sha1_to_hex(mb); - char *bad_hex = sha1_to_hex(current_bad_oid->hash); + char *bad_hex = oid_to_hex(current_bad_oid); char *good_hex = join_sha1_array_hex(&good_revs, ' '); warning(_("the merge base between %s and [%s] " diff --git a/builtin/am.c b/builtin/am.c index d5da5fe090..b77bf11ace 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -184,22 +184,22 @@ static inline const char *am_path(const struct am_state *state, const char *path /** * For convenience to call write_file() */ -static int write_state_text(const struct am_state *state, - const char *name, const char *string) +static void write_state_text(const struct am_state *state, + const char *name, const char *string) { - return write_file(am_path(state, name), "%s", string); + write_file(am_path(state, name), "%s", string); } -static int write_state_count(const struct am_state *state, - const char *name, int value) +static void write_state_count(const struct am_state *state, + const char *name, int value) { - return write_file(am_path(state, name), "%d", value); + write_file(am_path(state, name), "%d", value); } -static int write_state_bool(const struct am_state *state, - const char *name, int value) +static void write_state_bool(const struct am_state *state, + const char *name, int value) { - return write_state_text(state, name, value ? "t" : "f"); + write_state_text(state, name, value ? "t" : "f"); } /** @@ -403,13 +403,8 @@ static int read_commit_msg(struct am_state *state) */ static void write_commit_msg(const struct am_state *state) { - int fd; const char *filename = am_path(state, "final-commit"); - - fd = xopen(filename, O_WRONLY | O_CREAT, 0666); - if (write_in_full(fd, state->msg, state->msg_len) < 0) - die_errno(_("could not write to %s"), filename); - close(fd); + write_file_buf(filename, state->msg, state->msg_len); } /** @@ -1584,14 +1579,14 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f } /** - * Do the three-way merge using fake ancestor, his tree constructed + * Do the three-way merge using fake ancestor, their tree constructed * from the fake ancestor and the postimage of the patch, and our * state. */ static int run_fallback_merge_recursive(const struct am_state *state, unsigned char *orig_tree, unsigned char *our_tree, - unsigned char *his_tree) + unsigned char *their_tree) { struct child_process cp = CHILD_PROCESS_INIT; int status; @@ -1599,7 +1594,7 @@ static int run_fallback_merge_recursive(const struct am_state *state, cp.git_cmd = 1; argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s", - sha1_to_hex(his_tree), linelen(state->msg), state->msg); + sha1_to_hex(their_tree), linelen(state->msg), state->msg); if (state->quiet) argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0"); @@ -1607,7 +1602,7 @@ static int run_fallback_merge_recursive(const struct am_state *state, argv_array_push(&cp.args, sha1_to_hex(orig_tree)); argv_array_push(&cp.args, "--"); argv_array_push(&cp.args, sha1_to_hex(our_tree)); - argv_array_push(&cp.args, sha1_to_hex(his_tree)); + argv_array_push(&cp.args, sha1_to_hex(their_tree)); status = run_command(&cp) ? (-1) : 0; discard_cache(); @@ -1620,7 +1615,7 @@ static int run_fallback_merge_recursive(const struct am_state *state, */ static int fall_back_threeway(const struct am_state *state, const char *index_path) { - unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ], + unsigned char orig_tree[GIT_SHA1_RAWSZ], their_tree[GIT_SHA1_RAWSZ], our_tree[GIT_SHA1_RAWSZ]; if (get_sha1("HEAD", our_tree) < 0) @@ -1657,7 +1652,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa return error(_("Did you hand edit your patch?\n" "It does not apply to blobs recorded in its index.")); - if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL)) + if (write_index_as_tree(their_tree, &the_index, index_path, 0, NULL)) return error("could not write tree"); say(state, stdout, _("Falling back to patching base and 3-way merge...")); @@ -1667,13 +1662,13 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa /* * This is not so wrong. Depending on which base we picked, orig_tree - * may be wildly different from ours, but his_tree has the same set of + * may be wildly different from ours, but their_tree has the same set of * wildly different changes in parts the patch did not touch, so * recursive ends up canceling them, saying that we reverted all those * changes. */ - if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) { + if (run_fallback_merge_recursive(state, orig_tree, our_tree, their_tree)) { rerere(state->allow_rerere_autoupdate); return error(_("Failed to merge in the changes.")); } diff --git a/builtin/blame.c b/builtin/blame.c index 1e214bd4ec..ab66cde2c2 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -134,7 +134,7 @@ struct progress_info { int blamed_lines; }; -static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, +static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) { xpparam_t xpp = {0}; @@ -142,7 +142,6 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, xdemitcb_t ecb = {NULL}; xpp.flags = xdl_opts; - xecfg.ctxlen = ctxlen; xecfg.hunk_func = hunk_func; ecb.priv = cb_data; return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); @@ -599,7 +598,7 @@ static struct origin *find_origin(struct scoreboard *sb, p->status); case 'M': porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->sha1); + hashcpy(porigin->blob_sha1, p->one->oid.hash); porigin->mode = p->one->mode; break; case 'A': @@ -645,7 +644,7 @@ static struct origin *find_rename(struct scoreboard *sb, if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->sha1); + hashcpy(porigin->blob_sha1, p->one->oid.hash); porigin->mode = p->one->mode; break; } @@ -980,7 +979,7 @@ static void pass_blame_to_parent(struct scoreboard *sb, fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; - if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d)) + if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d)) die("unable to generate diff (%s -> %s)", oid_to_hex(&parent->commit->object.oid), oid_to_hex(&target->commit->object.oid)); @@ -1129,7 +1128,7 @@ static void find_copy_in_blob(struct scoreboard *sb, * file_p partially may match that image. */ memset(split, 0, sizeof(struct blame_entry [3])); - if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d)) + if (diff_hunks(file_p, &file_o, handle_split_cb, &d)) die("unable to generate diff (%s)", oid_to_hex(&parent->commit->object.oid)); /* remainder, if any, all match the preimage */ @@ -1309,7 +1308,7 @@ static void find_copy_in_parent(struct scoreboard *sb, continue; norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); + hashcpy(norigin->blob_sha1, p->one->oid.hash); norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) @@ -2230,6 +2229,7 @@ static int git_blame_config(const char *var, const char *value, void *cb) static void verify_working_tree_path(struct commit *work_tree, const char *path) { struct commit_list *parents; + int pos; for (parents = work_tree->parents; parents; parents = parents->next) { const unsigned char *commit_sha1 = parents->item->object.oid.hash; @@ -2240,7 +2240,14 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path) sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) return; } - die("no such path '%s' in HEAD", path); + + pos = cache_name_pos(path, strlen(path)); + if (pos >= 0) + ; /* path is in the index */ + else if (!strcmp(active_cache[-1 - pos]->name, path)) + ; /* path is in the index, unmerged */ + else + die("no such path '%s' in HEAD", path); } static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1) diff --git a/builtin/branch.c b/builtin/branch.c index 12203fdcc8..7df05437f1 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -212,7 +212,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } for (i = 0; i < argc; i++, strbuf_release(&bname)) { - const char *target; + char *target = NULL; int flags = 0; strbuf_branchname(&bname, argv[i]); @@ -231,11 +231,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } } - target = resolve_ref_unsafe(name, - RESOLVE_REF_READING - | RESOLVE_REF_NO_RECURSE - | RESOLVE_REF_ALLOW_BAD_NAME, - sha1, &flags); + target = resolve_refdup(name, + RESOLVE_REF_READING + | RESOLVE_REF_NO_RECURSE + | RESOLVE_REF_ALLOW_BAD_NAME, + sha1, &flags); if (!target) { error(remote_branch ? _("remote-tracking branch '%s' not found.") @@ -248,7 +248,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, check_branch_commit(bname.buf, name, sha1, head_rev, kinds, force)) { ret = 1; - continue; + goto next; } if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1, @@ -258,7 +258,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, : _("Error deleting branch '%s'"), bname.buf); ret = 1; - continue; + goto next; } if (!quiet) { printf(remote_branch @@ -270,6 +270,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); + + next: + free(target); } free(name); @@ -618,10 +621,7 @@ static int edit_branch_description(const char *branch_name) " %s\n" "Lines starting with '%c' will be stripped.\n"), branch_name, comment_line_char); - if (write_file_gently(git_path(edit_description), "%s", buf.buf)) { - strbuf_release(&buf); - return error_errno(_("could not write branch description template")); - } + write_file_buf(git_path(edit_description), buf.buf, buf.len); strbuf_reset(&buf); if (launch_editor(git_path(edit_description), &buf, NULL)) { strbuf_release(&buf); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 618103fdee..2dfe6265f7 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -131,7 +131,7 @@ struct expand_data { unsigned char sha1[20]; enum object_type type; unsigned long size; - unsigned long disk_size; + off_t disk_size; const char *rest; unsigned char delta_base_sha1[20]; @@ -191,7 +191,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, if (data->mark_query) data->info.disk_sizep = &data->disk_size; else - strbuf_addf(sb, "%lu", data->disk_size); + strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size); } else if (is_atom("rest", atom, len)) { if (data->mark_query) data->split_on_whitespace = 1; diff --git a/builtin/config.c b/builtin/config.c index 1d7c6ef558..a991a53418 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -604,7 +604,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) given_config_source.file : git_path("config")); if (use_global_config) { int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd) { + if (fd >= 0) { char *content = default_user_config(); write_str_in_full(fd, content); free(content); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 8164b581a6..c0652a7ed0 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -368,7 +368,7 @@ static void show_filemodify(struct diff_queue_struct *q, print_path(spec->path); putchar('\n'); - if (!hashcmp(ospec->sha1, spec->sha1) && + if (!oidcmp(&ospec->oid, &spec->oid) && ospec->mode == spec->mode) break; /* fallthrough */ @@ -383,10 +383,10 @@ static void show_filemodify(struct diff_queue_struct *q, if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, sha1_to_hex(anonymize ? - anonymize_sha1(spec->sha1) : - spec->sha1)); + anonymize_sha1(spec->oid.hash) : + spec->oid.hash)); else { - struct object *object = lookup_object(spec->sha1); + struct object *object = lookup_object(spec->oid.hash); printf("M %06o :%d ", spec->mode, get_object_mark(object)); } @@ -572,7 +572,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - export_blob(diff_queued_diff.queue[i]->two->sha1); + export_blob(diff_queued_diff.queue[i]->two->oid.hash); refname = commit->util; if (anonymize) { diff --git a/builtin/fetch.c b/builtin/fetch.c index f896aa1f88..acd0cf1755 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -15,6 +15,7 @@ #include "submodule.h" #include "connected.h" #include "argv-array.h" +#include "utf8.h" static const char * const builtin_fetch_usage[] = { N_("git fetch [<options>] [<repository> [<refspec>...]]"), @@ -449,7 +450,132 @@ fail: : STORE_REF_ERROR_OTHER; } -#define REFCOL_WIDTH 10 +static int refcol_width = 10; +static int compact_format; + +static void adjust_refcol_width(const struct ref *ref) +{ + int max, rlen, llen, len; + + /* uptodate lines are only shown on high verbosity level */ + if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid)) + return; + + max = term_columns(); + rlen = utf8_strwidth(prettify_refname(ref->name)); + + llen = utf8_strwidth(prettify_refname(ref->peer_ref->name)); + + /* + * rough estimation to see if the output line is too long and + * should not be counted (we can't do precise calculation + * anyway because we don't know if the error explanation part + * will be printed in update_local_ref) + */ + if (compact_format) { + llen = 0; + max = max * 2 / 3; + } + len = 21 /* flag and summary */ + rlen + 4 /* -> */ + llen; + if (len >= max) + return; + + /* + * Not precise calculation for compact mode because '*' can + * appear on the left hand side of '->' and shrink the column + * back. + */ + if (refcol_width < rlen) + refcol_width = rlen; +} + +static void prepare_format_display(struct ref *ref_map) +{ + struct ref *rm; + const char *format = "full"; + + git_config_get_string_const("fetch.output", &format); + if (!strcasecmp(format, "full")) + compact_format = 0; + else if (!strcasecmp(format, "compact")) + compact_format = 1; + else + die(_("configuration fetch.output contains invalid value %s"), + format); + + for (rm = ref_map; rm; rm = rm->next) { + if (rm->status == REF_STATUS_REJECT_SHALLOW || + !rm->peer_ref || + !strcmp(rm->name, "HEAD")) + continue; + + adjust_refcol_width(rm); + } +} + +static void print_remote_to_local(struct strbuf *display, + const char *remote, const char *local) +{ + strbuf_addf(display, "%-*s -> %s", refcol_width, remote, local); +} + +static int find_and_replace(struct strbuf *haystack, + const char *needle, + const char *placeholder) +{ + const char *p = strstr(haystack->buf, needle); + int plen, nlen; + + if (!p) + return 0; + + if (p > haystack->buf && p[-1] != '/') + return 0; + + plen = strlen(p); + nlen = strlen(needle); + if (plen > nlen && p[nlen] != '/') + return 0; + + strbuf_splice(haystack, p - haystack->buf, nlen, + placeholder, strlen(placeholder)); + return 1; +} + +static void print_compact(struct strbuf *display, + const char *remote, const char *local) +{ + struct strbuf r = STRBUF_INIT; + struct strbuf l = STRBUF_INIT; + + if (!strcmp(remote, local)) { + strbuf_addf(display, "%-*s -> *", refcol_width, remote); + return; + } + + strbuf_addstr(&r, remote); + strbuf_addstr(&l, local); + + if (!find_and_replace(&r, local, "*")) + find_and_replace(&l, remote, "*"); + print_remote_to_local(display, r.buf, l.buf); + + strbuf_release(&r); + strbuf_release(&l); +} + +static void format_display(struct strbuf *display, char code, + const char *summary, const char *error, + const char *remote, const char *local) +{ + strbuf_addf(display, "%c %-*s ", code, TRANSPORT_SUMMARY(summary)); + if (!compact_format) + print_remote_to_local(display, remote, local); + else + print_compact(display, remote, local); + if (error) + strbuf_addf(display, " (%s)", error); +} static int update_local_ref(struct ref *ref, const char *remote, @@ -467,9 +593,8 @@ static int update_local_ref(struct ref *ref, if (!oidcmp(&ref->old_oid, &ref->new_oid)) { if (verbosity > 0) - strbuf_addf(display, "= %-*s %-*s -> %s", - TRANSPORT_SUMMARY(_("[up to date]")), - REFCOL_WIDTH, remote, pretty_ref); + format_display(display, '=', _("[up to date]"), NULL, + remote, pretty_ref); return 0; } @@ -481,10 +606,9 @@ static int update_local_ref(struct ref *ref, * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ - strbuf_addf(display, - _("! %-*s %-*s -> %s (can't fetch in current branch)"), - TRANSPORT_SUMMARY(_("[rejected]")), - REFCOL_WIDTH, remote, pretty_ref); + format_display(display, '!', _("[rejected]"), + _("can't fetch in current branch"), + remote, pretty_ref); return 1; } @@ -492,11 +616,9 @@ static int update_local_ref(struct ref *ref, starts_with(ref->name, "refs/tags/")) { int r; r = s_update_ref("updating tag", ref, 0); - strbuf_addf(display, "%c %-*s %-*s -> %s%s", - r ? '!' : '-', - TRANSPORT_SUMMARY(_("[tag update]")), - REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + format_display(display, r ? '!' : 't', _("[tag update]"), + r ? _("unable to update local ref") : NULL, + remote, pretty_ref); return r; } @@ -527,11 +649,9 @@ static int update_local_ref(struct ref *ref, (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_oid.hash); r = s_update_ref(msg, ref, 0); - strbuf_addf(display, "%c %-*s %-*s -> %s%s", - r ? '!' : '*', - TRANSPORT_SUMMARY(what), - REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + format_display(display, r ? '!' : '*', what, + r ? _("unable to update local ref") : NULL, + remote, pretty_ref); return r; } @@ -545,11 +665,9 @@ static int update_local_ref(struct ref *ref, (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_oid.hash); r = s_update_ref("fast-forward", ref, 1); - strbuf_addf(display, "%c %-*s %-*s -> %s%s", - r ? '!' : ' ', - TRANSPORT_SUMMARY_WIDTH, quickref.buf, - REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + format_display(display, r ? '!' : ' ', quickref.buf, + r ? _("unable to update local ref") : NULL, + remote, pretty_ref); strbuf_release(&quickref); return r; } else if (force || ref->force) { @@ -562,18 +680,14 @@ static int update_local_ref(struct ref *ref, (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_oid.hash); r = s_update_ref("forced-update", ref, 1); - strbuf_addf(display, "%c %-*s %-*s -> %s (%s)", - r ? '!' : '+', - TRANSPORT_SUMMARY_WIDTH, quickref.buf, - REFCOL_WIDTH, remote, pretty_ref, - r ? _("unable to update local ref") : _("forced update")); + format_display(display, r ? '!' : '+', quickref.buf, + r ? _("unable to update local ref") : _("forced update"), + remote, pretty_ref); strbuf_release(&quickref); return r; } else { - strbuf_addf(display, "! %-*s %-*s -> %s %s", - TRANSPORT_SUMMARY(_("[rejected]")), - REFCOL_WIDTH, remote, pretty_ref, - _("(non-fast-forward)")); + format_display(display, '!', _("[rejected]"), _("non-fast-forward"), + remote, pretty_ref); return 1; } } @@ -620,6 +734,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, goto abort; } + prepare_format_display(ref_map); + /* * We do a pass for each fetch_head_status type in their enum order, so * merged entries are written before not-for-merge. That lets readers @@ -714,11 +830,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, rc |= update_local_ref(ref, what, rm, ¬e); free(ref); } else - strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD", - TRANSPORT_SUMMARY_WIDTH, - *kind ? kind : "branch", - REFCOL_WIDTH, - *what ? what : "HEAD"); + format_display(¬e, '*', + *kind ? kind : "branch", NULL, + *what ? what : "HEAD", + "FETCH_HEAD"); if (note.len) { if (verbosity >= 0 && !shown_url) { fprintf(stderr, _("From %.*s\n"), @@ -806,19 +921,21 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, for (ref = stale_refs; ref; ref = ref->next) string_list_append(&refnames, ref->name); - result = delete_refs(&refnames); + result = delete_refs(&refnames, 0); string_list_clear(&refnames, 0); } if (verbosity >= 0) { for (ref = stale_refs; ref; ref = ref->next) { + struct strbuf sb = STRBUF_INIT; if (!shown_url) { fprintf(stderr, _("From %.*s\n"), url_len, url); shown_url = 1; } - fprintf(stderr, " x %-*s %-*s -> %s\n", - TRANSPORT_SUMMARY(_("[deleted]")), - REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); + format_display(&sb, '-', _("[deleted]"), NULL, + _("(none)"), prettify_refname(ref->name)); + fprintf(stderr, " %s\n",sb.buf); + strbuf_release(&sb); warn_dangling_symref(stderr, dangling_msg, ref->name); } } diff --git a/builtin/fsck.c b/builtin/fsck.c index 3f27456883..2de272ea36 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -13,6 +13,7 @@ #include "dir.h" #include "progress.h" #include "streaming.h" +#include "decorate.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -35,11 +36,26 @@ static int write_lost_and_found; static int verbose; static int show_progress = -1; static int show_dangling = 1; +static int name_objects; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 #define ERROR_PACK 04 #define ERROR_REFS 010 +static const char *describe_object(struct object *obj) +{ + static struct strbuf buf = STRBUF_INIT; + char *name = name_objects ? + lookup_decoration(fsck_walk_options.object_names, obj) : NULL; + + strbuf_reset(&buf); + strbuf_addstr(&buf, oid_to_hex(&obj->oid)); + if (name) + strbuf_addf(&buf, " (%s)", name); + + return buf.buf; +} + static int fsck_config(const char *var, const char *value, void *cb) { if (strcmp(var, "fsck.skiplist") == 0) { @@ -67,7 +83,7 @@ static void objreport(struct object *obj, const char *msg_type, const char *err) { fprintf(stderr, "%s in %s %s: %s\n", - msg_type, typename(obj->type), oid_to_hex(&obj->oid), err); + msg_type, typename(obj->type), describe_object(obj), err); } static int objerror(struct object *obj, const char *err) @@ -77,7 +93,8 @@ static int objerror(struct object *obj, const char *err) return -1; } -static int fsck_error_func(struct object *obj, int type, const char *message) +static int fsck_error_func(struct fsck_options *o, + struct object *obj, int type, const char *message) { objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message); return (type == FSCK_WARN) ? 0 : 1; @@ -97,7 +114,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", - typename(parent->type), oid_to_hex(&parent->oid)); + typename(parent->type), describe_object(parent)); printf("broken link from %7s %s\n", (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); errors_found |= ERROR_REACHABLE; @@ -114,9 +131,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!(obj->flags & HAS_OBJ)) { if (parent && !has_object_file(&obj->oid)) { printf("broken link from %7s %s\n", - typename(parent->type), oid_to_hex(&parent->oid)); + typename(parent->type), describe_object(parent)); printf(" to %7s %s\n", - typename(obj->type), oid_to_hex(&obj->oid)); + typename(obj->type), describe_object(obj)); errors_found |= ERROR_REACHABLE; } return 1; @@ -190,7 +207,8 @@ static void check_reachable_object(struct object *obj) return; /* it is in pack - forget about it */ if (connectivity_only && has_object_file(&obj->oid)) return; - printf("missing %s %s\n", typename(obj->type), oid_to_hex(&obj->oid)); + printf("missing %s %s\n", typename(obj->type), + describe_object(obj)); errors_found |= ERROR_REACHABLE; return; } @@ -215,7 +233,8 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), oid_to_hex(&obj->oid)); + printf("unreachable %s %s\n", typename(obj->type), + describe_object(obj)); return; } @@ -234,11 +253,11 @@ static void check_unreachable_object(struct object *obj) if (!obj->used) { if (show_dangling) printf("dangling %s %s\n", typename(obj->type), - oid_to_hex(&obj->oid)); + describe_object(obj)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", - oid_to_hex(&obj->oid)); + describe_object(obj)); FILE *f; if (safe_create_leading_directories_const(filename)) { @@ -252,7 +271,7 @@ static void check_unreachable_object(struct object *obj) if (stream_blob_to_fd(fileno(f), obj->oid.hash, NULL, 1)) die_errno("Could not write '%s'", filename); } else - fprintf(f, "%s\n", oid_to_hex(&obj->oid)); + fprintf(f, "%s\n", describe_object(obj)); if (fclose(f)) die_errno("Could not finish '%s'", filename); @@ -271,7 +290,7 @@ static void check_unreachable_object(struct object *obj) static void check_object(struct object *obj) { if (verbose) - fprintf(stderr, "Checking %s\n", oid_to_hex(&obj->oid)); + fprintf(stderr, "Checking %s\n", describe_object(obj)); if (obj->flags & REACHABLE) check_reachable_object(obj); @@ -307,7 +326,7 @@ static int fsck_obj(struct object *obj) if (verbose) fprintf(stderr, "Checking %s %s\n", - typename(obj->type), oid_to_hex(&obj->oid)); + typename(obj->type), describe_object(obj)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); @@ -326,15 +345,17 @@ static int fsck_obj(struct object *obj) free_commit_buffer(commit); if (!commit->parents && show_root) - printf("root %s\n", oid_to_hex(&commit->object.oid)); + printf("root %s\n", describe_object(&commit->object)); } if (obj->type == OBJ_TAG) { struct tag *tag = (struct tag *) obj; if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), oid_to_hex(&tag->tagged->oid)); - printf(" (%s) in %s\n", tag->tag, oid_to_hex(&tag->object.oid)); + printf("tagged %s %s", typename(tag->tagged->type), + describe_object(tag->tagged)); + printf(" (%s) in %s\n", tag->tag, + describe_object(&tag->object)); } } @@ -356,6 +377,10 @@ static int fsck_sha1(const unsigned char *sha1) static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten) { + /* + * Note, buffer may be NULL if type is OBJ_BLOB. See + * verify_packfile(), data_valid variable for details. + */ struct object *obj; obj = parse_object_buffer(sha1, type, size, buffer, eaten); if (!obj) { @@ -368,13 +393,18 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, static int default_refs; -static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1) +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, + unsigned long timestamp) { struct object *obj; if (!is_null_sha1(sha1)) { obj = lookup_object(sha1); if (obj) { + if (timestamp && name_objects) + add_decoration(fsck_walk_options.object_names, + obj, + xstrfmt("%s@{%ld}", refname, timestamp)); obj->used = 1; mark_object_reachable(obj); } else { @@ -394,8 +424,8 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, fprintf(stderr, "Checking reflog %s->%s\n", sha1_to_hex(osha1), sha1_to_hex(nsha1)); - fsck_handle_reflog_sha1(refname, osha1); - fsck_handle_reflog_sha1(refname, nsha1); + fsck_handle_reflog_sha1(refname, osha1, 0); + fsck_handle_reflog_sha1(refname, nsha1, timestamp); return 0; } @@ -424,6 +454,9 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, } default_refs++; obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, xstrdup(refname)); mark_object_reachable(obj); return 0; @@ -539,6 +572,9 @@ static int fsck_cache_tree(struct cache_tree *it) return 1; } obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, xstrdup(":")); mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, "non-tree in cache-tree"); @@ -567,6 +603,7 @@ static struct option fsck_opts[] = { OPT_BOOL(0, "lost-found", &write_lost_and_found, N_("write dangling objects in .git/lost-found")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")), OPT_END(), }; @@ -596,6 +633,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) include_reflogs = 0; } + if (name_objects) + fsck_walk_options.object_names = + xcalloc(1, sizeof(struct decoration)); + git_config(fsck_config, NULL); fsck_head_link(); @@ -651,6 +692,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, xstrdup(arg)); mark_object_reachable(obj); heads++; continue; @@ -683,6 +727,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; obj = &blob->object; obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, + xstrfmt(":%s", active_cache[i]->name)); mark_object_reachable(obj); } if (active_cache_tree) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index e8c71fc1d2..1008d7f63c 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -338,10 +338,10 @@ static void parse_pack_header(void) use(sizeof(struct pack_header)); } -static NORETURN void bad_object(unsigned long offset, const char *format, +static NORETURN void bad_object(off_t offset, const char *format, ...) __attribute__((format (printf, 2, 3))); -static NORETURN void bad_object(unsigned long offset, const char *format, ...) +static NORETURN void bad_object(off_t offset, const char *format, ...) { va_list params; char buf[1024]; @@ -349,7 +349,8 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...) va_start(params, format); vsnprintf(buf, sizeof(buf), format, params); va_end(params); - die(_("pack has bad object at offset %lu: %s"), offset, buf); + die(_("pack has bad object at offset %"PRIuMAX": %s"), + (uintmax_t)offset, buf); } static inline struct thread_local *get_thread_data(void) @@ -429,7 +430,7 @@ static int is_delta_type(enum object_type type) return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); } -static void *unpack_entry_data(unsigned long offset, unsigned long size, +static void *unpack_entry_data(off_t offset, unsigned long size, enum object_type type, unsigned char *sha1) { static char fixed_buf[8192]; @@ -549,13 +550,13 @@ static void *unpack_data(struct object_entry *obj, void *cb_data) { off_t from = obj[0].idx.offset + obj[0].hdr_size; - unsigned long len = obj[1].idx.offset - from; + off_t len = obj[1].idx.offset - from; unsigned char *data, *inbuf; git_zstream stream; int status; data = xmallocz(consume ? 64*1024 : obj->size); - inbuf = xmalloc((len < 64*1024) ? len : 64*1024); + inbuf = xmalloc((len < 64*1024) ? (int)len : 64*1024); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); @@ -563,15 +564,15 @@ static void *unpack_data(struct object_entry *obj, stream.avail_out = consume ? 64*1024 : obj->size; do { - ssize_t n = (len < 64*1024) ? len : 64*1024; + ssize_t n = (len < 64*1024) ? (ssize_t)len : 64*1024; n = xpread(get_thread_data()->pack_fd, inbuf, n, from); if (n < 0) die_errno(_("cannot pread pack file")); if (!n) - die(Q_("premature end of pack file, %lu byte missing", - "premature end of pack file, %lu bytes missing", - len), - len); + die(Q_("premature end of pack file, %"PRIuMAX" byte missing", + "premature end of pack file, %"PRIuMAX" bytes missing", + (unsigned int)len), + (uintmax_t)len); from += n; len -= n; stream.next_in = inbuf; diff --git a/builtin/log.c b/builtin/log.c index 0b6f7392b9..fd1652f52b 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -238,16 +238,17 @@ static void show_early_header(struct rev_info *rev, const char *stage, int nr) if (rev->commit_format != CMIT_FMT_ONELINE) putchar(rev->diffopt.line_termination); } - printf(_("Final output: %d %s\n"), nr, stage); + fprintf(rev->diffopt.file, _("Final output: %d %s\n"), nr, stage); } static struct itimerval early_output_timer; static void log_show_early(struct rev_info *revs, struct commit_list *list) { - int i = revs->early_output; + int i = revs->early_output, close_file = revs->diffopt.close_file; int show_header = 1; + revs->diffopt.close_file = 0; sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; @@ -264,14 +265,19 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) case commit_ignore: break; case commit_error: + if (close_file) + fclose(revs->diffopt.file); return; } list = list->next; } /* Did we already get enough commits for the early output? */ - if (!i) + if (!i) { + if (close_file) + fclose(revs->diffopt.file); return; + } /* * ..if no, then repeat it twice a second until we @@ -333,7 +339,7 @@ static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; int saved_nrl = 0; - int saved_dcctc = 0; + int saved_dcctc = 0, close_file = rev->diffopt.close_file; if (rev->early_output) setup_early_output(rev); @@ -349,6 +355,7 @@ static int cmd_log_walk(struct rev_info *rev) * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to * retain that state information if replacing rev->diffopt in this loop */ + rev->diffopt.close_file = 0; while ((commit = get_revision(rev)) != NULL) { if (!log_tree_commit(rev, commit) && rev->max_count >= 0) /* @@ -369,6 +376,8 @@ static int cmd_log_walk(struct rev_info *rev) } rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; + if (close_file) + fclose(rev->diffopt.file); if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { @@ -451,7 +460,7 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) pp.fmt = rev->commit_format; pp.date_mode = rev->date_mode; pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding()); - printf("%s", out.buf); + fprintf(rev->diffopt.file, "%s", out.buf); strbuf_release(&out); } @@ -462,7 +471,7 @@ static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, con char *buf; unsigned long size; - fflush(stdout); + fflush(rev->diffopt.file); if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) || !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV)) return stream_blob_to_fd(1, sha1, NULL, 0); @@ -502,7 +511,7 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) } if (offset < size) - fwrite(buf + offset, size - offset, 1, stdout); + fwrite(buf + offset, size - offset, 1, rev->diffopt.file); free(buf); return 0; } @@ -511,7 +520,8 @@ static int show_tree_object(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { - printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); + FILE *file = context; + fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); return 0; } @@ -571,7 +581,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (rev.shown_one) putchar('\n'); - printf("%stag %s%s\n", + fprintf(rev.diffopt.file, "%stag %s%s\n", diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), t->tag, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); @@ -590,12 +600,12 @@ int cmd_show(int argc, const char **argv, const char *prefix) case OBJ_TREE: if (rev.shown_one) putchar('\n'); - printf("%stree %s%s\n\n", + fprintf(rev.diffopt.file, "%stree %s%s\n\n", diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, - show_tree_object, NULL); + show_tree_object, rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: @@ -801,11 +811,10 @@ static int git_format_config(const char *var, const char *value, void *cb) return git_log_config(var, value, cb); } -static FILE *realstdout = NULL; static const char *output_directory = NULL; static int outdir_offset; -static int reopen_stdout(struct commit *commit, const char *subject, +static int open_next_file(struct commit *commit, const char *subject, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; @@ -827,9 +836,9 @@ static int reopen_stdout(struct commit *commit, const char *subject, fmt_output_subject(&filename, subject, rev); if (!quiet) - fprintf(realstdout, "%s\n", filename.buf + outdir_offset); + printf("%s\n", filename.buf + outdir_offset); - if (freopen(filename.buf, "w", stdout) == NULL) + if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) return error(_("Cannot open patch file %s"), filename.buf); strbuf_release(&filename); @@ -888,15 +897,15 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id = strbuf_detach(&buf, NULL); } -static void print_signature(void) +static void print_signature(FILE *file) { if (!signature || !*signature) return; - printf("-- \n%s", signature); + fprintf(file, "-- \n%s", signature); if (signature[strlen(signature)-1] != '\n') - putchar('\n'); - putchar('\n'); + putc('\n', file); + putc('\n', file); } static void add_branch_description(struct strbuf *buf, const char *branch_name) @@ -965,7 +974,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, committer = git_committer_info(0); if (!use_stdout && - reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) + open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, @@ -988,7 +997,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); add_branch_description(&sb, branch_name); - printf("%s\n", sb.buf); + fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -997,6 +1006,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, log.wrap = 72; log.in1 = 2; log.in2 = 4; + log.file = rev->diffopt.file; for (i = 0; i < nr; i++) shortlog_add_commit(&log, list[i]); @@ -1019,8 +1029,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, diffcore_std(&opts); diff_flush(&opts); - printf("\n"); - print_signature(); + fprintf(rev->diffopt.file, "\n"); + print_signature(rev->diffopt.file); } static const char *clean_message_id(const char *msg_id) @@ -1330,7 +1340,7 @@ static void prepare_bases(struct base_tree_info *bases, } } -static void print_bases(struct base_tree_info *bases) +static void print_bases(struct base_tree_info *bases, FILE *file) { int i; @@ -1339,11 +1349,11 @@ static void print_bases(struct base_tree_info *bases) return; /* Show the base commit */ - printf("base-commit: %s\n", oid_to_hex(&bases->base_commit)); + fprintf(file, "base-commit: %s\n", oid_to_hex(&bases->base_commit)); /* Show the prerequisite patches */ for (i = bases->nr_patch_id - 1; i >= 0; i--) - printf("prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i])); + fprintf(file, "prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i])); free(bases->patch_id); bases->nr_patch_id = 0; @@ -1575,6 +1585,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) setup_pager(); if (output_directory) { + if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) + rev.diffopt.use_color = GIT_COLOR_NEVER; if (use_stdout) die(_("standard output, or directory, which one?")); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) @@ -1632,9 +1644,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) get_patch_ids(&rev, &ids); } - if (!use_stdout) - realstdout = xfdopen(xdup(1), "w"); - if (prepare_revision_walk(&rev)) die(_("revision walk setup failed")); rev.boundary = 1; @@ -1699,7 +1708,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, origin, nr, list, branch_name, quiet); - print_bases(&bases); + print_bases(&bases, rev.diffopt.file); total++; start_number--; } @@ -1745,7 +1754,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (!use_stdout && - reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) + open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free_commit_buffer(commit); @@ -1760,15 +1769,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.shown_one = 0; if (shown) { if (rev.mime_boundary) - printf("\n--%s%s--\n\n\n", + fprintf(rev.diffopt.file, "\n--%s%s--\n\n\n", mime_boundary_leader, rev.mime_boundary); else - print_signature(); - print_bases(&bases); + print_signature(rev.diffopt.file); + print_bases(&bases, rev.diffopt.file); } if (!use_stdout) - fclose(stdout); + fclose(rev.diffopt.file); } free(list); free(branch_name); @@ -1800,15 +1809,15 @@ static const char * const cherry_usage[] = { }; static void print_commit(char sign, struct commit *commit, int verbose, - int abbrev) + int abbrev, FILE *file) { if (!verbose) { - printf("%c %s\n", sign, + fprintf(file, "%c %s\n", sign, find_unique_abbrev(commit->object.oid.hash, abbrev)); } else { struct strbuf buf = STRBUF_INIT; pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf); - printf("%c %s %s\n", sign, + fprintf(file, "%c %s %s\n", sign, find_unique_abbrev(commit->object.oid.hash, abbrev), buf.buf); strbuf_release(&buf); @@ -1889,7 +1898,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) commit = list->item; if (has_commit_patch_id(commit, &ids)) sign = '-'; - print_commit(sign, commit, verbose, abbrev); + print_commit(sign, commit, verbose, abbrev, revs.diffopt.file); list = list->next; } diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 491efd556e..fd2c4556e1 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -9,10 +9,10 @@ static const char builtin_merge_recursive_usage[] = static const char *better_branch_name(const char *branch) { - static char githead_env[8 + 40 + 1]; + static char githead_env[8 + GIT_SHA1_HEXSZ + 1]; char *name; - if (strlen(branch) != 40) + if (strlen(branch) != GIT_SHA1_HEXSZ) return branch; xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch); name = getenv(githead_env); @@ -21,10 +21,10 @@ static const char *better_branch_name(const char *branch) int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { - const unsigned char *bases[21]; + const struct object_id *bases[21]; unsigned bases_count = 0; int i, failed; - unsigned char h1[20], h2[20]; + struct object_id h1, h2; struct merge_options o; struct commit *result; @@ -46,10 +46,10 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) continue; } if (bases_count < ARRAY_SIZE(bases)-1) { - unsigned char *sha = xmalloc(20); - if (get_sha1(argv[i], sha)) + struct object_id *oid = xmalloc(sizeof(struct object_id)); + if (get_oid(argv[i], oid)) die("Could not parse object '%s'", argv[i]); - bases[bases_count++] = sha; + bases[bases_count++] = oid; } else warning("Cannot handle more than %d bases. " @@ -62,9 +62,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) o.branch1 = argv[++i]; o.branch2 = argv[++i]; - if (get_sha1(o.branch1, h1)) + if (get_oid(o.branch1, &h1)) die("Could not resolve ref '%s'", o.branch1); - if (get_sha1(o.branch2, h2)) + if (get_oid(o.branch2, &h2)) die("Could not resolve ref '%s'", o.branch2); o.branch1 = better_branch_name(o.branch1); @@ -73,7 +73,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (o.verbosity >= 3) printf("Merging %s with %s\n", o.branch1, o.branch2); - failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result); + failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result); if (failed < 0) return 128; /* die() error code */ return failed; diff --git a/builtin/merge.c b/builtin/merge.c index d82f6c19a3..19b3bc2f2f 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -336,15 +336,9 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead struct rev_info rev; struct strbuf out = STRBUF_INIT; struct commit_list *j; - const char *filename; - int fd; struct pretty_print_context ctx = {0}; printf(_("Squash commit -- not updating HEAD\n")); - filename = git_path_squash_msg(); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not write to '%s'"), filename); init_revisions(&rev, NULL); rev.ignore_merges = 1; @@ -371,10 +365,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead oid_to_hex(&commit->object.oid)); pretty_print_commit(&ctx, commit, &out); } - if (write_in_full(fd, out.buf, out.len) != out.len) - die_errno(_("Writing SQUASH_MSG")); - if (close(fd)) - die_errno(_("Finishing SQUASH_MSG")); + write_file_buf(git_path_squash_msg(), out.buf, out.len); strbuf_release(&out); } @@ -501,7 +492,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (ref_exists(truname.buf)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->object.oid.hash), + oid_to_hex(&remote_head->object.oid), truname.buf + 11, (early ? " (early part)" : "")); strbuf_release(&truname); @@ -515,7 +506,7 @@ static void merge_name(const char *remote, struct strbuf *msg) desc = merge_remote_util(remote_head); if (desc && desc->obj && desc->obj->type == OBJ_TAG) { strbuf_addf(msg, "%s\t\t%s '%s'\n", - sha1_to_hex(desc->obj->oid.hash), + oid_to_hex(&desc->obj->oid), typename(desc->obj->type), remote); goto cleanup; @@ -523,7 +514,7 @@ static void merge_name(const char *remote, struct strbuf *msg) } strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->object.oid.hash), remote); + oid_to_hex(&remote_head->object.oid), remote); cleanup: strbuf_release(&buf); strbuf_release(&bname); @@ -756,18 +747,6 @@ static void add_strategies(const char *string, unsigned attr) } -static void write_merge_msg(struct strbuf *msg) -{ - const char *filename = git_path_merge_msg(); - int fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - filename); - if (write_in_full(fd, msg->buf, msg->len) != msg->len) - die_errno(_("Could not write to '%s'"), filename); - close(fd); -} - static void read_merge_msg(struct strbuf *msg) { const char *filename = git_path_merge_msg(); @@ -801,7 +780,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_addch(&msg, '\n'); if (0 < option_edit) strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); - write_merge_msg(&msg); + write_file_buf(git_path_merge_msg(), msg.buf, msg.len); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", git_path_merge_msg(), "merge", NULL)) abort_commit(remoteheads, NULL); @@ -964,8 +943,6 @@ static int setup_with_upstream(const char ***argv) static void write_merge_state(struct commit_list *remoteheads) { - const char *filename; - int fd; struct commit_list *j; struct strbuf buf = STRBUF_INIT; @@ -979,26 +956,14 @@ static void write_merge_state(struct commit_list *remoteheads) } strbuf_addf(&buf, "%s\n", oid_to_hex(oid)); } - filename = git_path_merge_head(); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), filename); - close(fd); + write_file_buf(git_path_merge_head(), buf.buf, buf.len); strbuf_addch(&merge_msg, '\n'); - write_merge_msg(&merge_msg); + write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.len); - filename = git_path_merge_mode(); - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); strbuf_reset(&buf); if (fast_forward == FF_NO) strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), filename); - close(fd); + write_file_buf(git_path_merge_mode(), buf.buf, buf.len); } static int default_edit_option(void) @@ -1366,7 +1331,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (p = remoteheads; p; p = p->next) { struct commit *commit = p->item; strbuf_addf(&buf, "GITHEAD_%s", - sha1_to_hex(commit->object.oid.hash)); + oid_to_hex(&commit->object.oid)); setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); if (fast_forward != FF_ONLY && @@ -1425,7 +1390,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && - !hashcmp(common->item->object.oid.hash, head_commit->object.oid.hash)) { + !oidcmp(&common->item->object.oid, &head_commit->object.oid)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; struct commit *commit; @@ -1499,8 +1464,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * HEAD^^" would be missed. */ common_one = get_merge_bases(head_commit, j->item); - if (hashcmp(common_one->item->object.oid.hash, - j->item->object.oid.hash)) { + if (oidcmp(&common_one->item->object.oid, &j->item->object.oid)) { up_to_date = 0; break; } @@ -1530,7 +1494,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Stash away the local changes so that we can try more than one. */ save_state(stash)) - hashcpy(stash, null_sha1); + hashclr(stash); for (i = 0; i < use_strategies_nr; i++) { int ret; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index a2f8cfdec0..92e2e5f7a8 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -342,15 +342,15 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent } /* Return 0 if we will bust the pack-size limit */ -static unsigned long write_reuse_object(struct sha1file *f, struct object_entry *entry, - unsigned long limit, int usable_delta) +static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, + unsigned long limit, int usable_delta) { struct packed_git *p = entry->in_pack; struct pack_window *w_curs = NULL; struct revindex_entry *revidx; off_t offset; enum object_type type = entry->type; - unsigned long datalen; + off_t datalen; unsigned char header[10], dheader[10]; unsigned hdrlen; @@ -416,11 +416,12 @@ static unsigned long write_reuse_object(struct sha1file *f, struct object_entry } /* Return 0 if we will bust the pack-size limit */ -static unsigned long write_object(struct sha1file *f, - struct object_entry *entry, - off_t write_offset) +static off_t write_object(struct sha1file *f, + struct object_entry *entry, + off_t write_offset) { - unsigned long limit, len; + unsigned long limit; + off_t len; int usable_delta, to_reuse; if (!pack_to_stdout) @@ -492,7 +493,7 @@ static enum write_one_status write_one(struct sha1file *f, struct object_entry *e, off_t *offset) { - unsigned long size; + off_t size; int recursing; /* diff --git a/builtin/remote.c b/builtin/remote.c index a4d9c1a8e9..9f6a6b3a9c 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -539,10 +539,6 @@ static int add_branch_for_removal(const char *refname, return 0; } - /* make sure that symrefs are deleted */ - if (flags & REF_ISSYMREF) - return unlink(git_path("%s", refname)); - string_list_append(branches->branches, refname); return 0; @@ -788,7 +784,7 @@ static int rm(int argc, const char **argv) strbuf_release(&buf); if (!result) - result = delete_refs(&branches); + result = delete_refs(&branches, REF_NODEREF); string_list_clear(&branches, 0); if (skipped.nr) { @@ -1304,7 +1300,7 @@ static int prune_remote(const char *remote, int dry_run) string_list_sort(&refs_to_prune); if (!dry_run) - result |= delete_refs(&refs_to_prune); + result |= delete_refs(&refs_to_prune, 0); for_each_string_list_item(item, &states.stale) { const char *refname = item->util; diff --git a/builtin/reset.c b/builtin/reset.c index 5c6206bc1c..9020ec66c8 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -121,7 +121,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filespec *one = q->queue[i]->one; - int is_missing = !(one->mode && !is_null_sha1(one->sha1)); + int is_missing = !(one->mode && !is_null_oid(&one->oid)); struct cache_entry *ce; if (is_missing && !intent_to_add) { @@ -129,7 +129,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, continue; } - ce = make_cache_entry(one->mode, one->sha1, one->path, + ce = make_cache_entry(one->mode, one->oid.hash, one->path, 0, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), diff --git a/builtin/rm.c b/builtin/rm.c index 8abb0207fa..b2fee3e90a 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -387,6 +387,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) */ if (!index_only) { int removed = 0, gitmodules_modified = 0; + struct strbuf buf = STRBUF_INIT; for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { @@ -398,7 +399,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) continue; } } else { - struct strbuf buf = STRBUF_INIT; + strbuf_reset(&buf); strbuf_addstr(&buf, path); if (!remove_dir_recursively(&buf, 0)) { removed = 1; @@ -410,7 +411,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) /* Submodule was removed by user */ if (!remove_path_from_gitmodules(path)) gitmodules_modified = 1; - strbuf_release(&buf); /* Fallthrough and let remove_path() fail. */ } } @@ -421,6 +421,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!removed) die_errno("git rm: '%s'", path); } + strbuf_release(&buf); if (gitmodules_modified) stage_updated_gitmodules(); } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index f83984e8a1..25fa8a6aed 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -276,6 +276,7 @@ parse_done: log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; log.abbrev = rev.abbrev; + log.file = rev.diffopt.file; /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) @@ -289,6 +290,8 @@ parse_done: get_from_rev(&rev, &log); shortlog_output(&log); + if (log.file != stdout) + fclose(log.file); return 0; } @@ -310,22 +313,24 @@ void shortlog_output(struct shortlog *log) for (i = 0; i < log->list.nr; i++) { const struct string_list_item *item = &log->list.items[i]; if (log->summary) { - printf("%6d\t%s\n", (int)UTIL_TO_INT(item), item->string); + fprintf(log->file, "%6d\t%s\n", + (int)UTIL_TO_INT(item), item->string); } else { struct string_list *onelines = item->util; - printf("%s (%d):\n", item->string, onelines->nr); + fprintf(log->file, "%s (%d):\n", + item->string, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) { const char *msg = onelines->items[j].string; if (log->wrap_lines) { strbuf_reset(&sb); add_wrapped_shortlog_msg(&sb, msg, log); - fwrite(sb.buf, sb.len, 1, stdout); + fwrite(sb.buf, sb.len, 1, log->file); } else - printf(" %s\n", msg); + fprintf(log->file, " %s\n", msg); } - putchar('\n'); + putc('\n', log->file); onelines->strdup_strings = 1; string_list_clear(onelines, 0); free(onelines); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 875e7ed998..172470bf24 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -355,7 +355,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, return; /* we are done */ else { /* cannot resolve yet --- queue it */ - hashcpy(obj_list[nr].sha1, null_sha1); + hashclr(obj_list[nr].sha1); add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size); return; } @@ -406,7 +406,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, * The delta base object is itself a delta that * has not been resolved yet. */ - hashcpy(obj_list[nr].sha1, null_sha1); + hashclr(obj_list[nr].sha1); add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size); return; } diff --git a/builtin/worktree.c b/builtin/worktree.c index e866844685..5a41788edb 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -14,7 +14,9 @@ static const char * const worktree_usage[] = { N_("git worktree add [<options>] <path> [<branch>]"), N_("git worktree list [<options>]"), + N_("git worktree lock [<options>] <path>"), N_("git worktree prune [<options>]"), + N_("git worktree unlock <path>"), NULL }; @@ -262,7 +264,7 @@ static int add_worktree(const char *path, const char *refname, */ strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, sha1_to_hex(null_sha1)); + write_file(sb.buf, "%s", sha1_to_hex(null_sha1)); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); @@ -462,6 +464,66 @@ static int list(int ac, const char **av, const char *prefix) return 0; } +static int lock_worktree(int ac, const char **av, const char *prefix) +{ + const char *reason = "", *old_reason; + struct option options[] = { + OPT_STRING(0, "reason", &reason, N_("string"), + N_("reason for locking")), + OPT_END() + }; + struct worktree **worktrees, *wt; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 1) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("The main working tree cannot be locked or unlocked")); + + old_reason = is_worktree_locked(wt); + if (old_reason) { + if (*old_reason) + die(_("'%s' is already locked, reason: %s"), + av[0], old_reason); + die(_("'%s' is already locked"), av[0]); + } + + write_file(git_common_path("worktrees/%s/locked", wt->id), + "%s", reason); + free_worktrees(worktrees); + return 0; +} + +static int unlock_worktree(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + struct worktree **worktrees, *wt; + int ret; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 1) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("The main working tree cannot be locked or unlocked")); + if (!is_worktree_locked(wt)) + die(_("'%s' is not locked"), av[0]); + ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id)); + free_worktrees(worktrees); + return ret; +} + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -478,5 +540,9 @@ int cmd_worktree(int ac, const char **av, const char *prefix) return prune(ac - 1, av + 1, prefix); if (!strcmp(av[1], "list")) return list(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "lock")) + return lock_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "unlock")) + return unlock_worktree(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); } diff --git a/cache-tree.c b/cache-tree.c index ddf0cc9f9a..f28b1f45a4 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -319,12 +319,13 @@ static int update_one(struct cache_tree *it, i = 0; while (i < entries) { const struct cache_entry *ce = cache[i]; - struct cache_tree_sub *sub; + struct cache_tree_sub *sub = NULL; const char *path, *slash; int pathlen, entlen; const unsigned char *sha1; unsigned mode; int expected_missing = 0; + int contains_ita = 0; path = ce->name; pathlen = ce_namelen(ce); @@ -341,7 +342,8 @@ static int update_one(struct cache_tree *it, i += sub->count; sha1 = sub->cache_tree->sha1; mode = S_IFDIR; - if (sub->cache_tree->entry_count < 0) { + contains_ita = sub->cache_tree->entry_count < 0; + if (contains_ita) { to_invalidate = 1; expected_missing = 1; } @@ -375,11 +377,17 @@ static int update_one(struct cache_tree *it, * they are not part of generated trees. Invalidate up * to root to force cache-tree users to read elsewhere. */ - if (ce_intent_to_add(ce)) { + if (!sub && ce_intent_to_add(ce)) { to_invalidate = 1; continue; } + /* + * "sub" can be an empty tree if all subentries are i-t-a. + */ + if (contains_ita && !hashcmp(sha1, EMPTY_TREE_SHA1_BIN)) + continue; + strbuf_grow(&buffer, entlen + 100); strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0'); strbuf_add(&buffer, sha1, 20); @@ -632,6 +632,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); #define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg); +extern struct cache_entry *refresh_cache_entry(struct cache_entry *, unsigned int); extern void update_index_if_able(struct index_state *, struct lock_file *); @@ -1003,6 +1004,11 @@ int adjust_shared_perm(const char *path); * directory while we were working. To be robust against this kind of * race, callers might want to try invoking the function again when it * returns SCLD_VANISHED. + * + * safe_create_leading_directories() temporarily changes path while it + * is working but restores it before returning. + * safe_create_leading_directories_const() doesn't modify path, even + * temporarily. */ enum scld_error { SCLD_OK = 0, @@ -1193,6 +1199,7 @@ extern int get_oid_hex(const char *hex, struct object_id *sha1); * printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two)); */ extern char *sha1_to_hex_r(char *out, const unsigned char *sha1); +extern char *oid_to_hex_r(char *out, const struct object_id *oid); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */ @@ -1508,7 +1515,7 @@ struct object_info { /* Request */ enum object_type *typep; unsigned long *sizep; - unsigned long *disk_sizep; + off_t *disk_sizep; unsigned char *delta_base_sha1; struct strbuf *typename; @@ -1745,8 +1752,21 @@ static inline ssize_t write_str_in_full(int fd, const char *str) return write_in_full(fd, str, strlen(str)); } -extern int write_file(const char *path, const char *fmt, ...); -extern int write_file_gently(const char *path, const char *fmt, ...); +/** + * Open (and truncate) the file at path, write the contents of buf to it, + * and close it. Dies if any errors are encountered. + */ +extern void write_file_buf(const char *path, const char *buf, size_t len); + +/** + * Like write_file_buf(), but format the contents into a buffer first. + * Additionally, write_file() will append a newline if one is not already + * present, making it convenient to write text files: + * + * write_file(path, "counter: %d", ctr); + */ +__attribute__((format (printf, 2, 3))) +extern void write_file(const char *path, const char *fmt, ...); /* pager.c */ extern void setup_pager(void); diff --git a/combine-diff.c b/combine-diff.c index 5920df8b8d..8e2a577bdb 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -44,9 +44,9 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, memset(p->parent, 0, sizeof(p->parent[0]) * num_parent); - hashcpy(p->oid.hash, q->queue[i]->two->sha1); + oidcpy(&p->oid, &q->queue[i]->two->oid); p->mode = q->queue[i]->two->mode; - hashcpy(p->parent[n].oid.hash, q->queue[i]->one->sha1); + oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; *tail = p; @@ -77,7 +77,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, continue; } - hashcpy(p->parent[n].oid.hash, q->queue[i]->one->sha1); + oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; @@ -1268,16 +1268,16 @@ static struct diff_filepair *combined_pair(struct combine_diff_path *p, for (i = 0; i < num_parent; i++) { pair->one[i].path = p->path; pair->one[i].mode = p->parent[i].mode; - hashcpy(pair->one[i].sha1, p->parent[i].oid.hash); - pair->one[i].sha1_valid = !is_null_oid(&p->parent[i].oid); + oidcpy(&pair->one[i].oid, &p->parent[i].oid); + pair->one[i].oid_valid = !is_null_oid(&p->parent[i].oid); pair->one[i].has_more_entries = 1; } pair->one[num_parent - 1].has_more_entries = 0; pair->two->path = p->path; pair->two->mode = p->mode; - hashcpy(pair->two->sha1, p->oid.hash); - pair->two->sha1_valid = !is_null_oid(&p->oid); + oidcpy(&pair->two->oid, &p->oid); + pair->two->oid_valid = !is_null_oid(&p->oid); return pair; } @@ -1622,16 +1622,6 @@ struct commit_list **commit_list_append(struct commit *commit, return &new->next; } -void print_commit_list(struct commit_list *list, - const char *format_cur, - const char *format_last) -{ - for ( ; list; list = list->next) { - const char *format = list->next ? format_cur : format_last; - printf(format, oid_to_hex(&list->item->object.oid)); - } -} - const char *find_commit_header(const char *msg, const char *key, size_t *out_len) { int key_len = strlen(key); @@ -377,10 +377,6 @@ extern int parse_signed_commit(const struct commit *commit, struct strbuf *message, struct strbuf *signature); extern int remove_signature(struct strbuf *buf); -extern void print_commit_list(struct commit_list *list, - const char *format_cur, - const char *format_last); - /* * Check the signature of the given commit. The result of the check is stored * in sig->check_result, 'G' for a good signature, 'U' for a good signature diff --git a/common-main.c b/common-main.c new file mode 100644 index 0000000000..44a29e8b13 --- /dev/null +++ b/common-main.c @@ -0,0 +1,41 @@ +#include "cache.h" +#include "exec_cmd.h" + +/* + * Many parts of Git have subprograms communicate via pipe, expect the + * upstream of a pipe to die with SIGPIPE when the downstream of a + * pipe does not need to read all that is written. Some third-party + * programs that ignore or block SIGPIPE for their own reason forget + * to restore SIGPIPE handling to the default before spawning Git and + * break this carefully orchestrated machinery. + * + * Restore the way SIGPIPE is handled to default, which is what we + * expect. + */ +static void restore_sigpipe_to_default(void) +{ + sigset_t unblock; + + sigemptyset(&unblock); + sigaddset(&unblock, SIGPIPE); + sigprocmask(SIG_UNBLOCK, &unblock, NULL); + signal(SIGPIPE, SIG_DFL); +} + +int main(int argc, const char **argv) +{ + /* + * Always open file descriptors 0/1/2 to avoid clobbering files + * in die(). It also avoids messing up when the pipes are dup'ed + * onto stdin/stdout/stderr in the child processes we spawn. + */ + sanitize_stdfds(); + + git_setup_gettext(); + + argv[0] = git_extract_argv0_path(argv[0]); + + restore_sigpipe_to_default(); + + return cmd_main(argc, argv); +} diff --git a/compat/mingw.h b/compat/mingw.h index 9a8803b876..95e128fcfd 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -73,6 +73,9 @@ typedef int pid_t; #ifndef ECONNABORTED #define ECONNABORTED WSAECONNABORTED #endif +#ifndef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK +#endif struct passwd { char *pw_name; @@ -535,7 +538,7 @@ extern CRITICAL_SECTION pinfo_cs; void mingw_startup(void); #define main(c,v) dummy_decl_mingw_main(void); \ static int mingw_main(c,v); \ -int main(int argc, char **argv) \ +int main(int argc, const char **argv) \ { \ mingw_startup(); \ return mingw_main(__argc, (void *)__argv); \ diff --git a/config.mak.uname b/config.mak.uname index a88f13989a..17fed2f43a 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -36,6 +36,8 @@ ifeq ($(uname_S),Linux) HAVE_DEV_TTY = YesPlease HAVE_CLOCK_GETTIME = YesPlease HAVE_CLOCK_MONOTONIC = YesPlease + # -lrt is needed for clock_gettime on glibc <= 2.16 + NEEDS_LIBRT = YesPlease HAVE_GETDELIM = YesPlease SANE_TEXT_GREP=-a endif @@ -203,6 +205,7 @@ ifeq ($(uname_S),FreeBSD) NO_STRTOUMAX = YesPlease endif PYTHON_PATH = /usr/local/bin/python + PERL_PATH = /usr/local/bin/perl HAVE_PATHS_H = YesPlease GMTIME_UNRELIABLE_ERRORS = UnfortunatelyYes HAVE_BSD_SYSCTL = YesPlease diff --git a/configure.ac b/configure.ac index c279025747..aa9c91d20d 100644 --- a/configure.ac +++ b/configure.ac @@ -1108,14 +1108,19 @@ GIT_CONF_SUBST([HAVE_BSD_SYSCTL]) AC_DEFUN([PTHREADTEST_SRC], [ AC_LANG_PROGRAM([[ #include <pthread.h> +static void *noop(void *ignore) { return ignore; } ]], [[ pthread_mutex_t test_mutex; pthread_key_t test_key; + pthread_t th; int retcode = 0; + void *ret = (void *)0; retcode |= pthread_key_create(&test_key, (void *)0); retcode |= pthread_mutex_init(&test_mutex,(void *)0); retcode |= pthread_mutex_lock(&test_mutex); retcode |= pthread_mutex_unlock(&test_mutex); + retcode |= pthread_create(&th, ret, noop, ret); + retcode |= pthread_join(th, &ret); return retcode; ]])]) @@ -658,6 +658,19 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, static struct child_process no_fork = CHILD_PROCESS_INIT; +static const char *get_ssh_command(void) +{ + const char *ssh; + + if ((ssh = getenv("GIT_SSH_COMMAND"))) + return ssh; + + if (!git_config_get_string_const("core.sshcommand", &ssh)) + return ssh; + + return NULL; +} + /* * This returns a dummy child_process if the transport protocol does not * need fork(2), or a struct child_process object if it does. Once done, @@ -758,7 +771,7 @@ struct child_process *git_connect(int fd[2], const char *url, return NULL; } - ssh = getenv("GIT_SSH_COMMAND"); + ssh = get_ssh_command(); if (!ssh) { const char *base; char *ssh_dup; diff --git a/contrib/coccinelle/README b/contrib/coccinelle/README new file mode 100644 index 0000000000..9c2f8879c2 --- /dev/null +++ b/contrib/coccinelle/README @@ -0,0 +1,2 @@ +This directory provides examples of Coccinelle (http://coccinelle.lip6.fr/) +semantic patches that might be useful to developers. diff --git a/contrib/coccinelle/object_id.cocci b/contrib/coccinelle/object_id.cocci new file mode 100644 index 0000000000..8ccdbb5666 --- /dev/null +++ b/contrib/coccinelle/object_id.cocci @@ -0,0 +1,95 @@ +@@ +expression E1; +@@ +- is_null_sha1(E1.hash) ++ is_null_oid(&E1) + +@@ +expression E1; +@@ +- is_null_sha1(E1->hash) ++ is_null_oid(E1) + +@@ +expression E1; +@@ +- sha1_to_hex(E1.hash) ++ oid_to_hex(&E1) + +@@ +expression E1; +@@ +- sha1_to_hex(E1->hash) ++ oid_to_hex(E1) + +@@ +expression E1; +@@ +- sha1_to_hex_r(E1.hash) ++ oid_to_hex_r(&E1) + +@@ +expression E1; +@@ +- sha1_to_hex_r(E1->hash) ++ oid_to_hex_r(E1) + +@@ +expression E1; +@@ +- hashclr(E1.hash) ++ oidclr(&E1) + +@@ +expression E1; +@@ +- hashclr(E1->hash) ++ oidclr(E1) + +@@ +expression E1, E2; +@@ +- hashcmp(E1.hash, E2.hash) ++ oidcmp(&E1, &E2) + +@@ +expression E1, E2; +@@ +- hashcmp(E1->hash, E2->hash) ++ oidcmp(E1, E2) + +@@ +expression E1, E2; +@@ +- hashcmp(E1->hash, E2.hash) ++ oidcmp(E1, &E2) + +@@ +expression E1, E2; +@@ +- hashcmp(E1.hash, E2->hash) ++ oidcmp(&E1, E2) + +@@ +expression E1, E2; +@@ +- hashcpy(E1.hash, E2.hash) ++ oidcpy(&E1, &E2) + +@@ +expression E1, E2; +@@ +- hashcpy(E1->hash, E2->hash) ++ oidcpy(E1, E2) + +@@ +expression E1, E2; +@@ +- hashcpy(E1->hash, E2.hash) ++ oidcpy(E1, &E2) + +@@ +expression E1, E2; +@@ +- hashcpy(E1.hash, E2->hash) ++ oidcpy(&E1, E2) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 37888f4e57..10f6d52254 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2693,7 +2693,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list prune" + local subcommands="add list lock prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" @@ -2705,6 +2705,9 @@ _git_worktree () list,--*) __gitcomp "--porcelain" ;; + lock,--*) + __gitcomp "--reason" + ;; prune,--*) __gitcomp "--dry-run --expire --verbose" ;; @@ -176,7 +176,9 @@ static enum eol output_eol(enum crlf_action crlf_action) return EOL_LF; case CRLF_UNDEFINED: case CRLF_AUTO_CRLF: + return EOL_CRLF; case CRLF_AUTO_INPUT: + return EOL_LF; case CRLF_TEXT: case CRLF_AUTO: /* fall through */ @@ -254,17 +256,15 @@ static int crlf_to_git(const char *path, const char *src, size_t len, if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) { if (convert_is_binary(len, &stats)) return 0; - - if (crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) { - /* - * If the file in the index has any CR in it, do not convert. - * This is the new safer autocrlf handling. - */ - if (has_cr_in_index(path)) - return 0; - } + /* + * If the file in the index has any CR in it, do not convert. + * This is the new safer autocrlf handling. + */ + if (checksafe == SAFE_CRLF_RENORMALIZE) + checksafe = SAFE_CRLF_FALSE; + else if (has_cr_in_index(path)) + return 0; } - check_safe_crlf(path, crlf_action, &stats, checksafe); /* Optimization: No CRLF? Nothing to convert, regardless. */ @@ -320,12 +320,10 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len, return 0; if (crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) { - if (crlf_action == CRLF_AUTO_INPUT || crlf_action == CRLF_AUTO_CRLF) { - /* If we have any CR or CRLF line endings, we do not touch it */ - /* This is the new safer autocrlf-handling */ - if (stats.lonecr || stats.crlf ) - return 0; - } + /* If we have any CR or CRLF line endings, we do not touch it */ + /* This is the new safer autocrlf-handling */ + if (stats.lonecr || stats.crlf ) + return 0; if (convert_is_binary(len, &stats)) return 0; @@ -786,7 +784,11 @@ static void convert_attrs(struct conv_attrs *ca, const char *path) ca->drv = git_path_check_convert(ccheck + 2); if (ca->crlf_action != CRLF_BINARY) { enum eol eol_attr = git_path_check_eol(ccheck + 3); - if (eol_attr == EOL_LF) + if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_LF) + ca->crlf_action = CRLF_AUTO_INPUT; + else if (ca->crlf_action == CRLF_AUTO && eol_attr == EOL_CRLF) + ca->crlf_action = CRLF_AUTO_CRLF; + else if (eol_attr == EOL_LF) ca->crlf_action = CRLF_TEXT_INPUT; else if (eol_attr == EOL_CRLF) ca->crlf_action = CRLF_TEXT_CRLF; @@ -845,9 +847,9 @@ const char *get_convert_attr_ascii(const char *path) case CRLF_AUTO: return "text=auto"; case CRLF_AUTO_CRLF: - return "text=auto eol=crlf"; /* This is not supported yet */ + return "text=auto eol=crlf"; case CRLF_AUTO_INPUT: - return "text=auto eol=lf"; /* This is not supported yet */ + return "text=auto eol=lf"; } return ""; } @@ -949,7 +951,7 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str src = dst->buf; len = dst->len; } - return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_FALSE); + return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_RENORMALIZE); } /***************************************************************** @@ -7,7 +7,8 @@ enum safe_crlf { SAFE_CRLF_FALSE = 0, SAFE_CRLF_FAIL = 1, - SAFE_CRLF_WARN = 2 + SAFE_CRLF_WARN = 2, + SAFE_CRLF_RENORMALIZE = 3 }; extern enum safe_crlf safe_crlf; diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 1f14d56e98..1e5f16a3a1 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -257,7 +257,7 @@ static void init_socket_directory(const char *path) free(path_copy); } -int main(int argc, const char **argv) +int cmd_main(int argc, const char **argv) { const char *socket_path; int ignore_sighup = 0; diff --git a/credential-cache.c b/credential-cache.c index 86e21de49b..cc8a6ee192 100644 --- a/credential-cache.c +++ b/credential-cache.c @@ -83,7 +83,7 @@ static void do_cache(const char *socket, const char *action, int timeout, strbuf_release(&buf); } -int main(int argc, const char **argv) +int cmd_main(int argc, const char **argv) { char *socket_path = NULL; int timeout = 900; diff --git a/credential-store.c b/credential-store.c index 57141679ab..55ca1b1334 100644 --- a/credential-store.c +++ b/credential-store.c @@ -142,7 +142,7 @@ static void lookup_credential(const struct string_list *fns, struct credential * return; /* Found credential */ } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { const char * const usage[] = { "git credential-store [<options>] <action>", @@ -1,6 +1,5 @@ #include "cache.h" #include "pkt-line.h" -#include "exec_cmd.h" #include "run-command.h" #include "strbuf.h" #include "string-list.h" @@ -32,7 +31,7 @@ static const char daemon_usage[] = " [<directory>...]"; /* List of acceptable pathname prefixes */ -static char **ok_paths; +static const char **ok_paths; static int strict_paths; /* If this is set, git-daemon-export-ok is not required */ @@ -240,7 +239,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) } if ( ok_paths && *ok_paths ) { - char **pp; + const char **pp; int pathlen = strlen(path); /* The validation is done on the paths after enter_repo @@ -673,9 +672,11 @@ static void set_keep_alive(int sockfd) { int ka = 1; - if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0) - logerror("unable to set SO_KEEPALIVE on socket: %s", - strerror(errno)); + if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0) { + if (errno != ENOTSOCK) + logerror("unable to set SO_KEEPALIVE on socket: %s", + strerror(errno)); + } } static int execute(void) @@ -1192,7 +1193,7 @@ static int serve(struct string_list *listen_addr, int listen_port, return service_loop(&socklist); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { int listen_port = 0; struct string_list listen_addr = STRING_LIST_INIT_NODUP; @@ -1202,12 +1203,8 @@ int main(int argc, char **argv) struct credentials *cred = NULL; int i; - git_setup_gettext(); - - git_extract_argv0_path(argv[0]); - for (i = 1; i < argc; i++) { - char *arg = argv[i]; + const char *arg = argv[i]; const char *v; if (skip_prefix(arg, "--listen=", &v)) { @@ -1381,8 +1378,7 @@ int main(int argc, char **argv) if (detach) { if (daemonize()) die("--detach not supported on this platform"); - } else - sanitize_stdfds(); + } if (pid_file) write_file(pid_file, "%"PRIuMAX, (uintmax_t) getpid()); @@ -1933,8 +1933,8 @@ static void show_dirstat(struct diff_options *options) name = p->two->path ? p->two->path : p->one->path; - if (p->one->sha1_valid && p->two->sha1_valid) - content_changed = hashcmp(p->one->sha1, p->two->sha1); + if (p->one->oid_valid && p->two->oid_valid) + content_changed = oidcmp(&p->one->oid, &p->two->oid); else content_changed = 1; @@ -2306,7 +2306,8 @@ static void builtin_diff(const char *name_a, const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); show_submodule_summary(o->file, one->path ? one->path : two->path, line_prefix, - one->sha1, two->sha1, two->dirty_submodule, + one->oid.hash, two->oid.hash, + two->dirty_submodule, meta, del, add, reset); return; } @@ -2384,7 +2385,7 @@ static void builtin_diff(const char *name_a, if (!one->data && !two->data && S_ISREG(one->mode) && S_ISREG(two->mode) && !DIFF_OPT_TST(o, BINARY)) { - if (!hashcmp(one->sha1, two->sha1)) { + if (!oidcmp(&one->oid, &two->oid)) { if (must_show_header) fprintf(o->file, "%s", header.buf); goto free_ab_and_return; @@ -2505,7 +2506,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, return; } - same_contents = !hashcmp(one->sha1, two->sha1); + same_contents = !oidcmp(&one->oid, &two->oid); if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) { data->is_binary = 1; @@ -2638,8 +2639,8 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, { if (mode) { spec->mode = canon_mode(mode); - hashcpy(spec->sha1, sha1); - spec->sha1_valid = sha1_valid; + hashcpy(spec->oid.hash, sha1); + spec->oid_valid = sha1_valid; } } @@ -2721,7 +2722,8 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) if (s->dirty_submodule) dirty = "-dirty"; - strbuf_addf(&buf, "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty); + strbuf_addf(&buf, "Subproject commit %s%s\n", + oid_to_hex(&s->oid), dirty); s->size = buf.len; if (size_only) { s->data = NULL; @@ -2764,8 +2766,8 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) if (S_ISGITLINK(s->mode)) return diff_populate_gitlink(s, size_only); - if (!s->sha1_valid || - reuse_worktree_file(s->path, s->sha1, 0)) { + if (!s->oid_valid || + reuse_worktree_file(s->path, s->oid.hash, 0)) { struct strbuf buf = STRBUF_INIT; struct stat st; int fd; @@ -2822,9 +2824,10 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) else { enum object_type type; if (size_only || (flags & CHECK_BINARY)) { - type = sha1_object_info(s->sha1, &s->size); + type = sha1_object_info(s->oid.hash, &s->size); if (type < 0) - die("unable to read %s", sha1_to_hex(s->sha1)); + die("unable to read %s", + oid_to_hex(&s->oid)); if (size_only) return 0; if (s->size > big_file_threshold && s->is_binary == -1) { @@ -2832,9 +2835,9 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) return 0; } } - s->data = read_sha1_file(s->sha1, &type, &s->size); + s->data = read_sha1_file(s->oid.hash, &type, &s->size); if (!s->data) - die("unable to read %s", sha1_to_hex(s->sha1)); + die("unable to read %s", oid_to_hex(&s->oid)); s->should_free = 1; } return 0; @@ -2863,7 +2866,7 @@ void diff_free_filespec_data(struct diff_filespec *s) static void prep_temp_blob(const char *path, struct diff_tempfile *temp, void *blob, unsigned long size, - const unsigned char *sha1, + const struct object_id *oid, int mode) { int fd; @@ -2888,7 +2891,7 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, die_errno("unable to write temp-file"); close_tempfile(&temp->tempfile); temp->name = get_tempfile_path(&temp->tempfile); - sha1_to_hex_r(temp->hex, sha1); + oid_to_hex_r(temp->hex, oid); xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode); strbuf_release(&buf); strbuf_release(&template); @@ -2912,8 +2915,8 @@ static struct diff_tempfile *prepare_temp_file(const char *name, } if (!S_ISGITLINK(one->mode) && - (!one->sha1_valid || - reuse_worktree_file(name, one->sha1, 1))) { + (!one->oid_valid || + reuse_worktree_file(name, one->oid.hash, 1))) { struct stat st; if (lstat(name, &st) < 0) { if (errno == ENOENT) @@ -2925,19 +2928,19 @@ static struct diff_tempfile *prepare_temp_file(const char *name, if (strbuf_readlink(&sb, name, st.st_size) < 0) die_errno("readlink(%s)", name); prep_temp_blob(name, temp, sb.buf, sb.len, - (one->sha1_valid ? - one->sha1 : null_sha1), - (one->sha1_valid ? + (one->oid_valid ? + &one->oid : &null_oid), + (one->oid_valid ? one->mode : S_IFLNK)); strbuf_release(&sb); } else { /* we can borrow from the file in the work tree */ temp->name = name; - if (!one->sha1_valid) + if (!one->oid_valid) sha1_to_hex_r(temp->hex, null_sha1); else - sha1_to_hex_r(temp->hex, one->sha1); + sha1_to_hex_r(temp->hex, one->oid.hash); /* Even though we may sometimes borrow the * contents from the work tree, we always want * one->mode. mode is trustworthy even when @@ -2952,7 +2955,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name, if (diff_populate_filespec(one, 0)) die("cannot read data blob for %s", one->path); prep_temp_blob(name, temp, one->data, one->size, - one->sha1, one->mode); + &one->oid, one->mode); } return temp; } @@ -3065,7 +3068,7 @@ static void fill_metainfo(struct strbuf *msg, default: *must_show_header = 0; } - if (one && two && hashcmp(one->sha1, two->sha1)) { + if (one && two && oidcmp(&one->oid, &two->oid)) { int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; if (DIFF_OPT_TST(o, BINARY)) { @@ -3075,8 +3078,8 @@ static void fill_metainfo(struct strbuf *msg, abbrev = 40; } strbuf_addf(msg, "%s%sindex %s..", line_prefix, set, - find_unique_abbrev(one->sha1, abbrev)); - strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev)); + find_unique_abbrev(one->oid.hash, abbrev)); + strbuf_addstr(msg, find_unique_abbrev(two->oid.hash, abbrev)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); strbuf_addf(msg, "%s\n", reset); @@ -3131,20 +3134,20 @@ static void run_diff_cmd(const char *pgm, static void diff_fill_sha1_info(struct diff_filespec *one) { if (DIFF_FILE_VALID(one)) { - if (!one->sha1_valid) { + if (!one->oid_valid) { struct stat st; if (one->is_stdin) { - hashcpy(one->sha1, null_sha1); + oidclr(&one->oid); return; } if (lstat(one->path, &st) < 0) die_errno("stat '%s'", one->path); - if (index_path(one->sha1, one->path, &st, 0)) + if (index_path(one->oid.hash, one->path, &st, 0)) die("cannot hash %s", one->path); } } else - hashclr(one->sha1); + oidclr(&one->oid); } static void strip_prefix(int prefix_length, const char **namep, const char **otherp) @@ -3977,6 +3980,8 @@ int diff_opt_parse(struct diff_options *options, if (!options->file) die_errno("Could not open '%s'", path); options->close_file = 1; + if (options->use_color != GIT_COLOR_ALWAYS) + options->use_color = GIT_COLOR_NEVER; return argcount; } else return 0; @@ -4118,8 +4123,9 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) fprintf(opt->file, "%s", diff_line_prefix(opt)); if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, - diff_unique_abbrev(p->one->sha1, opt->abbrev)); - fprintf(opt->file, "%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev)); + diff_unique_abbrev(p->one->oid.hash, opt->abbrev)); + fprintf(opt->file, "%s ", + diff_unique_abbrev(p->two->oid.hash, opt->abbrev)); } if (p->score) { fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p), @@ -4168,11 +4174,11 @@ int diff_unmodified_pair(struct diff_filepair *p) /* both are valid and point at the same path. that is, we are * dealing with a change. */ - if (one->sha1_valid && two->sha1_valid && - !hashcmp(one->sha1, two->sha1) && + if (one->oid_valid && two->oid_valid && + !oidcmp(&one->oid, &two->oid) && !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ - if (!one->sha1_valid && !two->sha1_valid) + if (!one->oid_valid && !two->oid_valid) return 1; /* both look at the same file on the filesystem. */ return 0; } @@ -4233,7 +4239,7 @@ void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) s->path, DIFF_FILE_VALID(s) ? "valid" : "invalid", s->mode, - s->sha1_valid ? sha1_to_hex(s->sha1) : ""); + s->oid_valid ? oid_to_hex(&s->oid) : ""); fprintf(stderr, "queue[%d] %s size %lu\n", x, one ? one : "", s->size); @@ -4303,11 +4309,11 @@ static void diff_resolve_rename_copy(void) else p->status = DIFF_STATUS_RENAMED; } - else if (hashcmp(p->one->sha1, p->two->sha1) || + else if (oidcmp(&p->one->oid, &p->two->oid) || p->one->mode != p->two->mode || p->one->dirty_submodule || p->two->dirty_submodule || - is_null_sha1(p->one->sha1)) + is_null_oid(&p->one->oid)) p->status = DIFF_STATUS_MODIFIED; else { /* This is a "no-change" entry and should not @@ -4523,8 +4529,10 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) if (diff_filespec_is_binary(p->one) || diff_filespec_is_binary(p->two)) { - git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40); - git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40); + git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid), + 40); + git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid), + 40); continue; } @@ -4816,7 +4824,7 @@ static int diff_filespec_check_stat_unmatch(struct diff_filepair *p) */ if (!DIFF_FILE_VALID(p->one) || /* (1) */ !DIFF_FILE_VALID(p->two) || - (p->one->sha1_valid && p->two->sha1_valid) || + (p->one->oid_valid && p->two->oid_valid) || (p->one->mode != p->two->mode) || diff_populate_filespec(p->one, CHECK_SIZE_ONLY) || diff_populate_filespec(p->two, CHECK_SIZE_ONLY) || @@ -5112,8 +5120,9 @@ size_t fill_textconv(struct userdiff_driver *driver, if (!driver->textconv) die("BUG: fill_textconv called with non-textconv driver"); - if (driver->textconv_cache && df->sha1_valid) { - *outbuf = notes_cache_get(driver->textconv_cache, df->sha1, + if (driver->textconv_cache && df->oid_valid) { + *outbuf = notes_cache_get(driver->textconv_cache, + df->oid.hash, &size); if (*outbuf) return size; @@ -5123,9 +5132,9 @@ size_t fill_textconv(struct userdiff_driver *driver, if (!*outbuf) die("unable to read files to diff"); - if (driver->textconv_cache && df->sha1_valid) { + if (driver->textconv_cache && df->oid_valid) { /* ignore errors, as we might be in a readonly repository */ - notes_cache_put(driver->textconv_cache, df->sha1, *outbuf, + notes_cache_put(driver->textconv_cache, df->oid.hash, *outbuf, size); /* * we could save up changes and flush them all at the end, diff --git a/diffcore-break.c b/diffcore-break.c index 5473493e10..881a74f29e 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -57,8 +57,8 @@ static int should_break(struct diff_filespec *src, return 1; /* even their types are different */ } - if (src->sha1_valid && dst->sha1_valid && - !hashcmp(src->sha1, dst->sha1)) + if (src->oid_valid && dst->oid_valid && + !oidcmp(&src->oid, &dst->oid)) return 0; /* they are the same */ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index 7715c13ec4..55067cab6c 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -7,6 +7,8 @@ #include "diffcore.h" #include "xdiff-interface.h" #include "kwset.h" +#include "commit.h" +#include "quote.h" typedef int (*pickaxe_fn)(mmfile_t *one, mmfile_t *two, struct diff_options *o, @@ -198,6 +200,18 @@ static void pickaxe(struct diff_queue_struct *q, struct diff_options *o, *q = outq; } +static void regcomp_or_die(regex_t *regex, const char *needle, int cflags) +{ + int err = regcomp(regex, needle, cflags); + if (err) { + /* The POSIX.2 people are surely sick */ + char errbuf[1024]; + regerror(err, regex, errbuf, 1024); + regfree(regex); + die("invalid regex: %s", errbuf); + } +} + void diffcore_pickaxe(struct diff_options *o) { const char *needle = o->pickaxe; @@ -206,18 +220,19 @@ void diffcore_pickaxe(struct diff_options *o) kwset_t kws = NULL; if (opts & (DIFF_PICKAXE_REGEX | DIFF_PICKAXE_KIND_G)) { - int err; int cflags = REG_EXTENDED | REG_NEWLINE; if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE)) cflags |= REG_ICASE; - err = regcomp(®ex, needle, cflags); - if (err) { - /* The POSIX.2 people are surely sick */ - char errbuf[1024]; - regerror(err, ®ex, errbuf, 1024); - regfree(®ex); - die("invalid regex: %s", errbuf); - } + regcomp_or_die(®ex, needle, cflags); + regexp = ®ex; + } else if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE) && + has_non_ascii(needle)) { + struct strbuf sb = STRBUF_INIT; + int cflags = REG_NEWLINE | REG_ICASE; + + basic_regex_quote_buf(&sb, needle); + regcomp_or_die(®ex, sb.buf, cflags); + strbuf_release(&sb); regexp = ®ex; } else { kws = kwsalloc(DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE) diff --git a/diffcore-rename.c b/diffcore-rename.c index 7f03eb5a04..58ac0a531b 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -60,7 +60,8 @@ static int add_rename_dst(struct diff_filespec *two) memmove(rename_dst + first + 1, rename_dst + first, (rename_dst_nr - first - 1) * sizeof(*rename_dst)); rename_dst[first].two = alloc_filespec(two->path); - fill_filespec(rename_dst[first].two, two->sha1, two->sha1_valid, two->mode); + fill_filespec(rename_dst[first].two, two->oid.hash, two->oid_valid, + two->mode); rename_dst[first].pair = NULL; return 0; } @@ -260,12 +261,13 @@ struct file_similarity { static unsigned int hash_filespec(struct diff_filespec *filespec) { - if (!filespec->sha1_valid) { + if (!filespec->oid_valid) { if (diff_populate_filespec(filespec, 0)) return 0; - hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1); + hash_sha1_file(filespec->data, filespec->size, "blob", + filespec->oid.hash); } - return sha1hash(filespec->sha1); + return sha1hash(filespec->oid.hash); } static int find_identical_files(struct hashmap *srcs, @@ -287,7 +289,7 @@ static int find_identical_files(struct hashmap *srcs, struct diff_filespec *source = p->filespec; /* False hash collision? */ - if (hashcmp(source->sha1, target->sha1)) + if (oidcmp(&source->oid, &target->oid)) continue; /* Non-regular files? If so, the modes must match! */ if (!S_ISREG(source->mode) || !S_ISREG(target->mode)) { @@ -466,7 +468,7 @@ void diffcore_rename(struct diff_options *options) strcmp(options->single_follow, p->two->path)) continue; /* not interested */ else if (!DIFF_OPT_TST(options, RENAME_EMPTY) && - is_empty_blob_sha1(p->two->sha1)) + is_empty_blob_sha1(p->two->oid.hash)) continue; else if (add_rename_dst(p->two) < 0) { warning("skipping rename detection, detected" @@ -476,7 +478,7 @@ void diffcore_rename(struct diff_options *options) } } else if (!DIFF_OPT_TST(options, RENAME_EMPTY) && - is_empty_blob_sha1(p->one->sha1)) + is_empty_blob_sha1(p->one->oid.hash)) continue; else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) { /* diff --git a/diffcore.h b/diffcore.h index 33ea2de348..c11b8465fc 100644 --- a/diffcore.h +++ b/diffcore.h @@ -25,7 +25,7 @@ struct userdiff_driver; struct diff_filespec { - unsigned char sha1[20]; + struct object_id oid; char *path; void *data; void *cnt_data; @@ -33,7 +33,7 @@ struct diff_filespec { int count; /* Reference count */ int rename_used; /* Count of rename users */ unsigned short mode; /* file mode */ - unsigned sha1_valid : 1; /* if true, use sha1 and trust mode; + unsigned oid_valid : 1; /* if true, use oid and trust mode; * if false, use the name and read from * the filesystem. */ diff --git a/dir-iterator.c b/dir-iterator.c new file mode 100644 index 0000000000..34182a9a1c --- /dev/null +++ b/dir-iterator.c @@ -0,0 +1,202 @@ +#include "cache.h" +#include "dir.h" +#include "iterator.h" +#include "dir-iterator.h" + +struct dir_iterator_level { + int initialized; + + DIR *dir; + + /* + * The length of the directory part of path at this level + * (including a trailing '/'): + */ + size_t prefix_len; + + /* + * The last action that has been taken with the current entry + * (needed for directories, which have to be included in the + * iteration and also iterated into): + */ + enum { + DIR_STATE_ITER, + DIR_STATE_RECURSE + } dir_state; +}; + +/* + * The full data structure used to manage the internal directory + * iteration state. It includes members that are not part of the + * public interface. + */ +struct dir_iterator_int { + struct dir_iterator base; + + /* + * The number of levels currently on the stack. This is always + * at least 1, because when it becomes zero the iteration is + * ended and this struct is freed. + */ + size_t levels_nr; + + /* The number of levels that have been allocated on the stack */ + size_t levels_alloc; + + /* + * A stack of levels. levels[0] is the uppermost directory + * that will be included in this iteration. + */ + struct dir_iterator_level *levels; +}; + +int dir_iterator_advance(struct dir_iterator *dir_iterator) +{ + struct dir_iterator_int *iter = + (struct dir_iterator_int *)dir_iterator; + + while (1) { + struct dir_iterator_level *level = + &iter->levels[iter->levels_nr - 1]; + struct dirent *de; + + if (!level->initialized) { + /* + * Note: dir_iterator_begin() ensures that + * path is not the empty string. + */ + if (!is_dir_sep(iter->base.path.buf[iter->base.path.len - 1])) + strbuf_addch(&iter->base.path, '/'); + level->prefix_len = iter->base.path.len; + + level->dir = opendir(iter->base.path.buf); + if (!level->dir && errno != ENOENT) { + warning("error opening directory %s: %s", + iter->base.path.buf, strerror(errno)); + /* Popping the level is handled below */ + } + + level->initialized = 1; + } else if (S_ISDIR(iter->base.st.st_mode)) { + if (level->dir_state == DIR_STATE_ITER) { + /* + * The directory was just iterated + * over; now prepare to iterate into + * it. + */ + level->dir_state = DIR_STATE_RECURSE; + ALLOC_GROW(iter->levels, iter->levels_nr + 1, + iter->levels_alloc); + level = &iter->levels[iter->levels_nr++]; + level->initialized = 0; + continue; + } else { + /* + * The directory has already been + * iterated over and iterated into; + * we're done with it. + */ + } + } + + if (!level->dir) { + /* + * This level is exhausted (or wasn't opened + * successfully); pop up a level. + */ + if (--iter->levels_nr == 0) + return dir_iterator_abort(dir_iterator); + + continue; + } + + /* + * Loop until we find an entry that we can give back + * to the caller: + */ + while (1) { + strbuf_setlen(&iter->base.path, level->prefix_len); + errno = 0; + de = readdir(level->dir); + + if (!de) { + /* This level is exhausted; pop up a level. */ + if (errno) { + warning("error reading directory %s: %s", + iter->base.path.buf, strerror(errno)); + } else if (closedir(level->dir)) + warning("error closing directory %s: %s", + iter->base.path.buf, strerror(errno)); + + level->dir = NULL; + if (--iter->levels_nr == 0) + return dir_iterator_abort(dir_iterator); + break; + } + + if (is_dot_or_dotdot(de->d_name)) + continue; + + strbuf_addstr(&iter->base.path, de->d_name); + if (lstat(iter->base.path.buf, &iter->base.st) < 0) { + if (errno != ENOENT) + warning("error reading path '%s': %s", + iter->base.path.buf, + strerror(errno)); + continue; + } + + /* + * We have to set these each time because + * the path strbuf might have been realloc()ed. + */ + iter->base.relative_path = + iter->base.path.buf + iter->levels[0].prefix_len; + iter->base.basename = + iter->base.path.buf + level->prefix_len; + level->dir_state = DIR_STATE_ITER; + + return ITER_OK; + } + } +} + +int dir_iterator_abort(struct dir_iterator *dir_iterator) +{ + struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator; + + for (; iter->levels_nr; iter->levels_nr--) { + struct dir_iterator_level *level = + &iter->levels[iter->levels_nr - 1]; + + if (level->dir && closedir(level->dir)) { + strbuf_setlen(&iter->base.path, level->prefix_len); + warning("error closing directory %s: %s", + iter->base.path.buf, strerror(errno)); + } + } + + free(iter->levels); + strbuf_release(&iter->base.path); + free(iter); + return ITER_DONE; +} + +struct dir_iterator *dir_iterator_begin(const char *path) +{ + struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter)); + struct dir_iterator *dir_iterator = &iter->base; + + if (!path || !*path) + die("BUG: empty path passed to dir_iterator_begin()"); + + strbuf_init(&iter->base.path, PATH_MAX); + strbuf_addstr(&iter->base.path, path); + + ALLOC_GROW(iter->levels, 10, iter->levels_alloc); + + iter->levels_nr = 1; + iter->levels[0].initialized = 0; + + return dir_iterator; +} diff --git a/dir-iterator.h b/dir-iterator.h new file mode 100644 index 0000000000..27739e6c29 --- /dev/null +++ b/dir-iterator.h @@ -0,0 +1,87 @@ +#ifndef DIR_ITERATOR_H +#define DIR_ITERATOR_H + +/* + * Iterate over a directory tree. + * + * Iterate over a directory tree, recursively, including paths of all + * types and hidden paths. Skip "." and ".." entries and don't follow + * symlinks except for the original path. + * + * Every time dir_iterator_advance() is called, update the members of + * the dir_iterator structure to reflect the next path in the + * iteration. The order that paths are iterated over within a + * directory is undefined, but directory paths are always iterated + * over before the subdirectory contents. + * + * A typical iteration looks like this: + * + * int ok; + * struct iterator *iter = dir_iterator_begin(path); + * + * while ((ok = dir_iterator_advance(iter)) == ITER_OK) { + * if (want_to_stop_iteration()) { + * ok = dir_iterator_abort(iter); + * break; + * } + * + * // Access information about the current path: + * if (S_ISDIR(iter->st.st_mode)) + * printf("%s is a directory\n", iter->relative_path); + * } + * + * if (ok != ITER_DONE) + * handle_error(); + * + * Callers are allowed to modify iter->path while they are working, + * but they must restore it to its original contents before calling + * dir_iterator_advance() again. + */ + +struct dir_iterator { + /* The current path: */ + struct strbuf path; + + /* + * The current path relative to the starting path. This part + * of the path always uses "/" characters to separate path + * components: + */ + const char *relative_path; + + /* The current basename: */ + const char *basename; + + /* The result of calling lstat() on path: */ + struct stat st; +}; + +/* + * Start a directory iteration over path. Return a dir_iterator that + * holds the internal state of the iteration. + * + * The iteration includes all paths under path, not including path + * itself and not including "." or ".." entries. + * + * path is the starting directory. An internal copy will be made. + */ +struct dir_iterator *dir_iterator_begin(const char *path); + +/* + * Advance the iterator to the first or next item and return ITER_OK. + * If the iteration is exhausted, free the dir_iterator and any + * resources associated with it and return ITER_DONE. On error, free + * dir_iterator and associated resources and return ITER_ERROR. It is + * a bug to use iterator or call this function again after it has + * returned ITER_DONE or ITER_ERROR. + */ +int dir_iterator_advance(struct dir_iterator *iterator); + +/* + * End the iteration before it has been exhausted. Free the + * dir_iterator and any associated resources and return ITER_DONE. On + * error, free the dir_iterator and return ITER_ERROR. + */ +int dir_iterator_abort(struct dir_iterator *iterator); + +#endif @@ -2364,7 +2364,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra varint_len = encode_varint(untracked->ident.len, varbuf); strbuf_add(out, varbuf, varint_len); - strbuf_add(out, untracked->ident.buf, untracked->ident.len); + strbuf_addbuf(out, &untracked->ident); strbuf_add(out, ouc, ouc_size(len)); free(ouc); @@ -262,9 +262,32 @@ extern int is_empty_dir(const char *dir); extern void setup_standard_excludes(struct dir_struct *dir); + +/* Constants for remove_dir_recursively: */ + +/* + * If a non-directory is found within path, stop and return an error. + * (In this case some empty directories might already have been + * removed.) + */ #define REMOVE_DIR_EMPTY_ONLY 01 + +/* + * If any Git work trees are found within path, skip them without + * considering it an error. + */ #define REMOVE_DIR_KEEP_NESTED_GIT 02 + +/* Remove the contents of path, but leave path itself. */ #define REMOVE_DIR_KEEP_TOPLEVEL 04 + +/* + * Remove path and its contents, recursively. flags is a combination + * of the above REMOVE_DIR_* constants. Return 0 on success. + * + * This function uses path as temporary scratch space, but restores it + * before returning. + */ extern int remove_dir_recursively(struct strbuf *path, int flag); /* tries to remove the path with empty directories along it, ignores ENOENT */ diff --git a/fast-import.c b/fast-import.c index 59630cee14..bf53ac95da 100644 --- a/fast-import.c +++ b/fast-import.c @@ -164,7 +164,6 @@ Format of STDIN stream: #include "refs.h" #include "csum-file.h" #include "quote.h" -#include "exec_cmd.h" #include "dir.h" #include "run-command.h" @@ -302,7 +301,7 @@ static int failure; static FILE *pack_edges; static unsigned int show_stats = 1; static int global_argc; -static char **global_argv; +static const char **global_argv; /* Memory pools */ static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool); @@ -3445,14 +3444,10 @@ static void parse_argv(void) read_marks(); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { unsigned int i; - git_extract_argv0_path(argv[0]); - - git_setup_gettext(); - if (argc == 2 && !strcmp(argv[1], "-h")) usage(fast_import_usage); @@ -9,6 +9,7 @@ #include "refs.h" #include "utf8.h" #include "sha1-array.h" +#include "decorate.h" #define FSCK_FATAL -1 #define FSCK_INFO -2 @@ -290,35 +291,87 @@ static int report(struct fsck_options *options, struct object *object, va_start(ap, fmt); strbuf_vaddf(&sb, fmt, ap); - result = options->error_func(object, msg_type, sb.buf); + result = options->error_func(options, object, msg_type, sb.buf); strbuf_release(&sb); va_end(ap); return result; } +static char *get_object_name(struct fsck_options *options, struct object *obj) +{ + if (!options->object_names) + return NULL; + return lookup_decoration(options->object_names, obj); +} + +static void put_object_name(struct fsck_options *options, struct object *obj, + const char *fmt, ...) +{ + va_list ap; + struct strbuf buf = STRBUF_INIT; + char *existing; + + if (!options->object_names) + return; + existing = lookup_decoration(options->object_names, obj); + if (existing) + return; + va_start(ap, fmt); + strbuf_vaddf(&buf, fmt, ap); + add_decoration(options->object_names, obj, strbuf_detach(&buf, NULL)); + va_end(ap); +} + +static const char *describe_object(struct fsck_options *o, struct object *obj) +{ + static struct strbuf buf = STRBUF_INIT; + char *name; + + strbuf_reset(&buf); + strbuf_addstr(&buf, oid_to_hex(&obj->oid)); + if (o->object_names && (name = lookup_decoration(o->object_names, obj))) + strbuf_addf(&buf, " (%s)", name); + + return buf.buf; +} + static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options) { struct tree_desc desc; struct name_entry entry; int res = 0; + const char *name; if (parse_tree(tree)) return -1; + name = get_object_name(options, &tree->object); init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { + struct object *obj; int result; if (S_ISGITLINK(entry.mode)) continue; - if (S_ISDIR(entry.mode)) - result = options->walk(&lookup_tree(entry.oid->hash)->object, OBJ_TREE, data, options); - else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) - result = options->walk(&lookup_blob(entry.oid->hash)->object, OBJ_BLOB, data, options); + + if (S_ISDIR(entry.mode)) { + obj = &lookup_tree(entry.oid->hash)->object; + if (name) + put_object_name(options, obj, "%s%s/", name, + entry.path); + result = options->walk(obj, OBJ_TREE, data, options); + } + else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) { + obj = &lookup_blob(entry.oid->hash)->object; + if (name) + put_object_name(options, obj, "%s%s", name, + entry.path); + result = options->walk(obj, OBJ_BLOB, data, options); + } else { result = error("in tree %s: entry %s has bad mode %.6o", - oid_to_hex(&tree->object.oid), entry.path, entry.mode); + describe_object(options, &tree->object), entry.path, entry.mode); } if (result < 0) return result; @@ -330,20 +383,55 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_options *options) { + int counter = 0, generation = 0, name_prefix_len = 0; struct commit_list *parents; int res; int result; + const char *name; if (parse_commit(commit)) return -1; + name = get_object_name(options, &commit->object); + if (name) + put_object_name(options, &commit->tree->object, "%s:", name); + result = options->walk((struct object *)commit->tree, OBJ_TREE, data, options); if (result < 0) return result; res = result; parents = commit->parents; + if (name && parents) { + int len = strlen(name), power; + + if (len && name[len - 1] == '^') { + generation = 1; + name_prefix_len = len - 1; + } + else { /* parse ~<generation> suffix */ + for (generation = 0, power = 1; + len && isdigit(name[len - 1]); + power *= 10) + generation += power * (name[--len] - '0'); + if (power > 1 && len && name[len - 1] == '~') + name_prefix_len = len - 1; + } + } + while (parents) { + if (name) { + struct object *obj = &parents->item->object; + + if (++counter > 1) + put_object_name(options, obj, "%s^%d", + name, counter); + else if (generation > 0) + put_object_name(options, obj, "%.*s~%d", + name_prefix_len, name, generation + 1); + else + put_object_name(options, obj, "%s^", name); + } result = options->walk((struct object *)parents->item, OBJ_COMMIT, data, options); if (result < 0) return result; @@ -356,8 +444,12 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options) { + char *name = get_object_name(options, &tag->object); + if (parse_tag(tag)) return -1; + if (name) + put_object_name(options, tag->tagged, "%s", name); return options->walk(tag->tagged, OBJ_ANY, data, options); } @@ -375,7 +467,7 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) case OBJ_TAG: return fsck_walk_tag((struct tag *)obj, data, options); default: - error("Unknown object type for %s", oid_to_hex(&obj->oid)); + error("Unknown object type for %s", describe_object(options, obj)); return -1; } } @@ -818,12 +910,13 @@ int fsck_object(struct object *obj, void *data, unsigned long size, obj->type); } -int fsck_error_function(struct object *obj, int msg_type, const char *message) +int fsck_error_function(struct fsck_options *o, + struct object *obj, int msg_type, const char *message) { if (msg_type == FSCK_WARN) { - warning("object %s: %s", oid_to_hex(&obj->oid), message); + warning("object %s: %s", describe_object(o, obj), message); return 0; } - error("object %s: %s", oid_to_hex(&obj->oid), message); + error("object %s: %s", describe_object(o, obj), message); return 1; } @@ -23,9 +23,11 @@ int is_valid_msg_type(const char *msg_id, const char *msg_type); typedef int (*fsck_walk_func)(struct object *obj, int type, void *data, struct fsck_options *options); /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */ -typedef int (*fsck_error)(struct object *obj, int type, const char *message); +typedef int (*fsck_error)(struct fsck_options *o, + struct object *obj, int type, const char *message); -int fsck_error_function(struct object *obj, int type, const char *message); +int fsck_error_function(struct fsck_options *o, + struct object *obj, int type, const char *message); struct fsck_options { fsck_walk_func walk; @@ -33,6 +35,7 @@ struct fsck_options { unsigned strict:1; int *msg_type; struct sha1_array *skiplist; + struct decoration *object_names; }; #define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL } @@ -18,6 +18,8 @@ # endif #endif +static const char *charset; + /* * Guess the user's preferred languages from the value in LANGUAGE environment * variable and LC_MESSAGES locale category if NO_GETTEXT is not defined. @@ -65,7 +67,6 @@ static int test_vsnprintf(const char *fmt, ...) return ret; } -static const char *charset; static void init_gettext_charset(const char *domain) { /* @@ -172,8 +173,27 @@ int gettext_width(const char *s) { static int is_utf8 = -1; if (is_utf8 == -1) - is_utf8 = !strcmp(charset, "UTF-8"); + is_utf8 = is_utf8_locale(); return is_utf8 ? utf8_strwidth(s) : strlen(s); } #endif + +int is_utf8_locale(void) +{ +#ifdef NO_GETTEXT + if (!charset) { + const char *env = getenv("LC_ALL"); + if (!env || !*env) + env = getenv("LC_CTYPE"); + if (!env || !*env) + env = getenv("LANG"); + if (!env) + env = ""; + if (strchr(env, '.')) + env = strchr(env, '.') + 1; + charset = xstrdup(env); + } +#endif + return is_encoding_utf8(charset); +} @@ -90,5 +90,6 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n) #endif const char *get_preferred_languages(void); +extern int is_utf8_locale(void); #endif diff --git a/git-compat-util.h b/git-compat-util.h index c99cddc54b..590bfddf73 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -1062,3 +1062,5 @@ struct tm *git_gmtime_r(const time_t *, struct tm *); #endif #endif + +extern int cmd_main(int, const char **); @@ -2274,7 +2274,7 @@ class P4Sync(Command, P4UserMap): self.useClientSpec_from_options = False self.clientSpecDirs = None self.tempBranches = [] - self.tempBranchLocation = "git-p4-tmp" + self.tempBranchLocation = "refs/git-p4-tmp" self.largeFileSystem = None if gitConfig('git-p4.largeFileSystem'): diff --git a/git-submodule.sh b/git-submodule.sh index 4ec7546f1e..2b23ce6e25 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -379,8 +379,6 @@ cmd_init() # # Unregister submodules from .git/config and remove their work tree # -# $@ = requested paths (use '.' to deinit all submodules) -# cmd_deinit() { # parse $args after "submodule ... deinit". @@ -609,48 +609,15 @@ static int run_argv(int *argcp, const char ***argv) return done_alias; } -/* - * Many parts of Git have subprograms communicate via pipe, expect the - * upstream of a pipe to die with SIGPIPE when the downstream of a - * pipe does not need to read all that is written. Some third-party - * programs that ignore or block SIGPIPE for their own reason forget - * to restore SIGPIPE handling to the default before spawning Git and - * break this carefully orchestrated machinery. - * - * Restore the way SIGPIPE is handled to default, which is what we - * expect. - */ -static void restore_sigpipe_to_default(void) -{ - sigset_t unblock; - - sigemptyset(&unblock); - sigaddset(&unblock, SIGPIPE); - sigprocmask(SIG_UNBLOCK, &unblock, NULL); - signal(SIGPIPE, SIG_DFL); -} - -int main(int argc, char **av) +int cmd_main(int argc, const char **argv) { - const char **argv = (const char **) av; const char *cmd; int done_help = 0; - cmd = git_extract_argv0_path(argv[0]); + cmd = argv[0]; if (!cmd) cmd = "git-help"; - /* - * Always open file descriptors 0/1/2 to avoid clobbering files - * in die(). It also avoids messing up when the pipes are dup'ed - * onto stdin/stdout/stderr in the child processes we spawn. - */ - sanitize_stdfds(); - - restore_sigpipe_to_default(); - - git_setup_gettext(); - trace_command_performance(argv); /* @@ -17,8 +17,8 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb); /* - * Print a strbuf to stdout. If the graph is non-NULL, all lines but the - * first will be prefixed with the graph output. + * Print a strbuf. If the graph is non-NULL, all lines but the first will be + * prefixed with the graph output. * * If the strbuf ends with a newline, the output will end after this * newline. A new graph line will not be printed after the final newline. @@ -1200,9 +1200,10 @@ void graph_show_commit(struct git_graph *graph) while (!shown_commit_line && !graph_is_commit_finished(graph)) { shown_commit_line = graph_next_line(graph, &msgbuf); - fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); + fwrite(msgbuf.buf, sizeof(char), msgbuf.len, + graph->revs->diffopt.file); if (!shown_commit_line) - putchar('\n'); + putc('\n', graph->revs->diffopt.file); strbuf_setlen(&msgbuf, 0); } @@ -1217,7 +1218,7 @@ void graph_show_oneline(struct git_graph *graph) return; graph_next_line(graph, &msgbuf); - fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); + fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file); strbuf_release(&msgbuf); } @@ -1229,7 +1230,7 @@ void graph_show_padding(struct git_graph *graph) return; graph_padding_line(graph, &msgbuf); - fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); + fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file); strbuf_release(&msgbuf); } @@ -1246,12 +1247,13 @@ int graph_show_remainder(struct git_graph *graph) for (;;) { graph_next_line(graph, &msgbuf); - fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); + fwrite(msgbuf.buf, sizeof(char), msgbuf.len, + graph->revs->diffopt.file); strbuf_setlen(&msgbuf, 0); shown = 1; if (!graph_is_commit_finished(graph)) - putchar('\n'); + putc('\n', graph->revs->diffopt.file); else break; } @@ -1266,7 +1268,8 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb) char *p; if (!graph) { - fwrite(sb->buf, sizeof(char), sb->len, stdout); + fwrite(sb->buf, sizeof(char), sb->len, + graph->revs->diffopt.file); return; } @@ -1284,7 +1287,7 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb) } else { len = (sb->buf + sb->len) - p; } - fwrite(p, sizeof(char), len, stdout); + fwrite(p, sizeof(char), len, graph->revs->diffopt.file); if (next_p && *next_p != '\0') graph_show_oneline(graph); p = next_p; @@ -1304,7 +1307,8 @@ void graph_show_commit_msg(struct git_graph *graph, * CMIT_FMT_USERFORMAT are already missing a terminating * newline. All of the other formats should have it. */ - fwrite(sb->buf, sizeof(char), sb->len, stdout); + fwrite(sb->buf, sizeof(char), sb->len, + graph->revs->diffopt.file); return; } @@ -1325,7 +1329,7 @@ void graph_show_commit_msg(struct git_graph *graph, * new line. */ if (!newline_terminated) - putchar('\n'); + putc('\n', graph->revs->diffopt.file); graph_show_remainder(graph); @@ -1333,6 +1337,6 @@ void graph_show_commit_msg(struct git_graph *graph, * If sb ends with a newline, our output should too. */ if (newline_terminated) - putchar('\n'); + putc('\n', graph->revs->diffopt.file); } } @@ -4,6 +4,8 @@ #include "xdiff-interface.h" #include "diff.h" #include "diffcore.h" +#include "commit.h" +#include "quote.h" static int grep_source_load(struct grep_source *gs); static int grep_source_is_binary(struct grep_source *gs); @@ -322,11 +324,16 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) int erroffset; int options = PCRE_MULTILINE; - if (opt->ignore_case) + if (opt->ignore_case) { + if (has_non_ascii(p->pattern)) + p->pcre_tables = pcre_maketables(); options |= PCRE_CASELESS; + } + if (is_utf8_locale() && has_non_ascii(p->pattern)) + options |= PCRE_UTF8; p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset, - NULL); + p->pcre_tables); if (!p->pcre_regexp) compile_regexp_failed(p, error); @@ -360,6 +367,7 @@ static void free_pcre_regexp(struct grep_pat *p) { pcre_free(p->pcre_regexp); pcre_free(p->pcre_extra_info); + pcre_free((void *)p->pcre_tables); } #else /* !USE_LIBPCRE */ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) @@ -396,26 +404,68 @@ static int is_fixed(const char *s, size_t len) return 1; } +static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + struct strbuf sb = STRBUF_INIT; + int err; + int regflags; + + basic_regex_quote_buf(&sb, p->pattern); + regflags = opt->regflags & ~REG_EXTENDED; + if (opt->ignore_case) + regflags |= REG_ICASE; + err = regcomp(&p->regexp, sb.buf, regflags); + if (opt->debug) + fprintf(stderr, "fixed %s\n", sb.buf); + strbuf_release(&sb); + if (err) { + char errbuf[1024]; + regerror(err, &p->regexp, errbuf, sizeof(errbuf)); + regfree(&p->regexp); + compile_regexp_failed(p, errbuf); + } +} + static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { + int icase, ascii_only; int err; p->word_regexp = opt->word_regexp; p->ignore_case = opt->ignore_case; + icase = opt->regflags & REG_ICASE || p->ignore_case; + ascii_only = !has_non_ascii(p->pattern); + /* + * Even when -F (fixed) asks us to do a non-regexp search, we + * may not be able to correctly case-fold when -i + * (ignore-case) is asked (in which case, we'll synthesize a + * regexp to match the pattern that matches regexp special + * characters literally, while ignoring case differences). On + * the other hand, even without -F, if the pattern does not + * have any regexp special characters and there is no need for + * case-folding search, we can internally turn it into a + * simple string match using kws. p->fixed tells us if we + * want to use kws. + */ if (opt->fixed || is_fixed(p->pattern, p->patternlen)) - p->fixed = 1; + p->fixed = !icase || ascii_only; else p->fixed = 0; if (p->fixed) { - if (opt->regflags & REG_ICASE || p->ignore_case) - p->kws = kwsalloc(tolower_trans_tbl); - else - p->kws = kwsalloc(NULL); + p->kws = kwsalloc(icase ? tolower_trans_tbl : NULL); kwsincr(p->kws, p->pattern, p->patternlen); kwsprep(p->kws); return; + } else if (opt->fixed) { + /* + * We come here when the pattern has the non-ascii + * characters we cannot case-fold, and asked to + * ignore-case. + */ + compile_fixed_regexp(p, opt); + return; } if (opt->pcre) { @@ -48,6 +48,7 @@ struct grep_pat { regex_t regexp; pcre *pcre_regexp; pcre_extra *pcre_extra_info; + const unsigned char *pcre_tables; kwset_t kws; unsigned fixed:1; unsigned ignore_case:1; @@ -77,6 +77,11 @@ char *sha1_to_hex_r(char *buffer, const unsigned char *sha1) return buffer; } +char *oid_to_hex_r(char *buffer, const struct object_id *oid) +{ + return sha1_to_hex_r(buffer, oid->hash); +} + char *sha1_to_hex(const unsigned char *sha1) { static int bufno; diff --git a/http-backend.c b/http-backend.c index 214881459d..0d59499a51 100644 --- a/http-backend.c +++ b/http-backend.c @@ -632,7 +632,7 @@ static struct service_cmd { {"POST", "/git-receive-pack$", service_rpc} }; -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { char *method = getenv("REQUEST_METHOD"); char *dir; @@ -640,9 +640,6 @@ int main(int argc, char **argv) char *cmd_arg = NULL; int i; - git_setup_gettext(); - - git_extract_argv0_path(argv[0]); set_die_routine(die_webcgi); set_die_is_recursing_routine(die_webcgi_recursing); diff --git a/http-fetch.c b/http-fetch.c index ba3ea10670..3b556d6619 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -6,7 +6,7 @@ static const char http_fetch_usage[] = "git http-fetch " "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"; -int main(int argc, const char **argv) +int cmd_main(int argc, const char **argv) { struct walker *walker; int commits_on_stdin = 0; @@ -22,10 +22,6 @@ int main(int argc, const char **argv) int get_verbosely = 0; int get_recover = 0; - git_setup_gettext(); - - git_extract_argv0_path(argv[0]); - while (arg < argc && argv[arg][0] == '-') { if (argv[arg][1] == 't') { get_tree = 1; diff --git a/http-push.c b/http-push.c index a092f0288b..dacada9094 100644 --- a/http-push.c +++ b/http-push.c @@ -1692,12 +1692,12 @@ static void run_request_queue(void) #endif } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct transfer_request *request; struct transfer_request *next_request; int nr_refspec = 0; - char **refspec = NULL; + const char **refspec = NULL; struct remote_lock *ref_lock = NULL; struct remote_lock *info_ref_lock = NULL; struct rev_info revs; @@ -1709,15 +1709,11 @@ int main(int argc, char **argv) int new_refs; struct ref *ref, *local_refs; - git_setup_gettext(); - - git_extract_argv0_path(argv[0]); - repo = xcalloc(1, sizeof(*repo)); argv++; for (i = 1; i < argc; i++, argv++) { - char *arg = *argv; + const char *arg = *argv; if (*arg == '-') { if (!strcmp(arg, "--all")) { diff --git a/imap-send.c b/imap-send.c index 50377c5b88..db0fafee99 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1495,16 +1495,12 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server, } #endif -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct strbuf all_msgs = STRBUF_INIT; int total; int nongit_ok; - git_extract_argv0_path(argv[0]); - - git_setup_gettext(); - setup_git_directory_gently(&nongit_ok); git_imap_config(); diff --git a/iterator.h b/iterator.h new file mode 100644 index 0000000000..0f6900e43a --- /dev/null +++ b/iterator.h @@ -0,0 +1,81 @@ +#ifndef ITERATOR_H +#define ITERATOR_H + +/* + * Generic constants related to iterators. + */ + +/* + * The attempt to advance the iterator was successful; the iterator + * reflects the new current entry. + */ +#define ITER_OK 0 + +/* + * The iterator is exhausted and has been freed. + */ +#define ITER_DONE -1 + +/* + * The iterator experienced an error. The iteration has been aborted + * and the iterator has been freed. + */ +#define ITER_ERROR -2 + +/* + * Return values for selector functions for merge iterators. The + * numerical values of these constants are important and must be + * compatible with ITER_DONE and ITER_ERROR. + */ +enum iterator_selection { + /* End the iteration without an error: */ + ITER_SELECT_DONE = ITER_DONE, + + /* Report an error and abort the iteration: */ + ITER_SELECT_ERROR = ITER_ERROR, + + /* + * The next group of constants are masks that are useful + * mainly internally. + */ + + /* The LSB selects whether iter0/iter1 is the "current" iterator: */ + ITER_CURRENT_SELECTION_MASK = 0x01, + + /* iter0 is the "current" iterator this round: */ + ITER_CURRENT_SELECTION_0 = 0x00, + + /* iter1 is the "current" iterator this round: */ + ITER_CURRENT_SELECTION_1 = 0x01, + + /* Yield the value from the current iterator? */ + ITER_YIELD_CURRENT = 0x02, + + /* Discard the value from the secondary iterator? */ + ITER_SKIP_SECONDARY = 0x04, + + /* + * The constants that a selector function should usually + * return. + */ + + /* Yield the value from iter0: */ + ITER_SELECT_0 = ITER_CURRENT_SELECTION_0 | ITER_YIELD_CURRENT, + + /* Yield the value from iter0 and discard the one from iter1: */ + ITER_SELECT_0_SKIP_1 = ITER_SELECT_0 | ITER_SKIP_SECONDARY, + + /* Discard the value from iter0 without yielding anything this round: */ + ITER_SKIP_0 = ITER_CURRENT_SELECTION_1 | ITER_SKIP_SECONDARY, + + /* Yield the value from iter1: */ + ITER_SELECT_1 = ITER_CURRENT_SELECTION_1 | ITER_YIELD_CURRENT, + + /* Yield the value from iter1 and discard the one from iter0: */ + ITER_SELECT_1_SKIP_0 = ITER_SELECT_1 | ITER_SKIP_SECONDARY, + + /* Discard the value from iter1 without yielding anything this round: */ + ITER_SKIP_1 = ITER_CURRENT_SELECTION_0 | ITER_SKIP_SECONDARY +}; + +#endif /* ITERATOR_H */ diff --git a/line-log.c b/line-log.c index 1fbbe4f0af..916e724870 100644 --- a/line-log.c +++ b/line-log.c @@ -519,7 +519,7 @@ static void fill_line_ends(struct diff_filespec *spec, long *lines, char *data = NULL; if (diff_populate_filespec(spec, 0)) - die("Cannot read blob %s", sha1_to_hex(spec->sha1)); + die("Cannot read blob %s", oid_to_hex(&spec->oid)); ALLOC_ARRAY(ends, size); ends[cur++] = 0; @@ -840,7 +840,7 @@ static char *get_nth_line(long line, unsigned long *ends, void *data) static void print_line(const char *prefix, char first, long line, unsigned long *ends, void *data, - const char *color, const char *reset) + const char *color, const char *reset, FILE *file) { char *begin = get_nth_line(line, ends, data); char *end = get_nth_line(line+1, ends, data); @@ -851,14 +851,14 @@ static void print_line(const char *prefix, char first, had_nl = 1; } - fputs(prefix, stdout); - fputs(color, stdout); - putchar(first); - fwrite(begin, 1, end-begin, stdout); - fputs(reset, stdout); - putchar('\n'); + fputs(prefix, file); + fputs(color, file); + putc(first, file); + fwrite(begin, 1, end-begin, file); + fputs(reset, file); + putc('\n', file); if (!had_nl) - fputs("\\ No newline at end of file\n", stdout); + fputs("\\ No newline at end of file\n", file); } static char *output_prefix(struct diff_options *opt) @@ -893,16 +893,16 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang if (!pair || !diff) return; - if (pair->one->sha1_valid) + if (pair->one->oid_valid) fill_line_ends(pair->one, &p_lines, &p_ends); fill_line_ends(pair->two, &t_lines, &t_ends); - printf("%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset); - printf("%s%s--- %s%s%s\n", prefix, c_meta, - pair->one->sha1_valid ? "a/" : "", - pair->one->sha1_valid ? pair->one->path : "/dev/null", + fprintf(opt->file, "%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset); + fprintf(opt->file, "%s%s--- %s%s%s\n", prefix, c_meta, + pair->one->oid_valid ? "a/" : "", + pair->one->oid_valid ? pair->one->path : "/dev/null", c_reset); - printf("%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset); + fprintf(opt->file, "%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset); for (i = 0; i < range->ranges.nr; i++) { long p_start, p_end; long t_start = range->ranges.ranges[i].start; @@ -944,7 +944,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang } /* Now output a diff hunk for this range */ - printf("%s%s@@ -%ld,%ld +%ld,%ld @@%s\n", + fprintf(opt->file, "%s%s@@ -%ld,%ld +%ld,%ld @@%s\n", prefix, c_frag, p_start+1, p_end-p_start, t_start+1, t_end-t_start, c_reset); @@ -952,18 +952,18 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang int k; for (; t_cur < diff->target.ranges[j].start; t_cur++) print_line(prefix, ' ', t_cur, t_ends, pair->two->data, - c_context, c_reset); + c_context, c_reset, opt->file); for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++) print_line(prefix, '-', k, p_ends, pair->one->data, - c_old, c_reset); + c_old, c_reset, opt->file); for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++) print_line(prefix, '+', t_cur, t_ends, pair->two->data, - c_new, c_reset); + c_new, c_reset, opt->file); j++; } for (; t_cur < t_end; t_cur++) print_line(prefix, ' ', t_cur, t_ends, pair->two->data, - c_context, c_reset); + c_context, c_reset, opt->file); } free(p_ends); @@ -976,7 +976,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang */ static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range) { - puts(output_prefix(&rev->diffopt)); + fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt)); while (range) { dump_diff_hacky_one(rev, range); range = range->next; @@ -1010,12 +1010,12 @@ static int process_diff_filepair(struct rev_info *rev, if (rg->ranges.nr == 0) return 0; - assert(pair->two->sha1_valid); + assert(pair->two->oid_valid); diff_populate_filespec(pair->two, 0); file_target.ptr = pair->two->data; file_target.size = pair->two->size; - if (pair->one->sha1_valid) { + if (pair->one->oid_valid) { diff_populate_filespec(pair->one, 0); file_parent.ptr = pair->one->data; file_parent.size = pair->one->size; diff --git a/log-tree.c b/log-tree.c index 9f678abc92..d0062e6788 100644 --- a/log-tree.c +++ b/log-tree.c @@ -159,12 +159,12 @@ void load_ref_decorations(int flags) } } -static void show_parents(struct commit *commit, int abbrev) +static void show_parents(struct commit *commit, int abbrev, FILE *file) { struct commit_list *p; for (p = commit->parents; p ; p = p->next) { struct commit *parent = p->item; - printf(" %s", find_unique_abbrev(parent->object.oid.hash, abbrev)); + fprintf(file, " %s", find_unique_abbrev(parent->object.oid.hash, abbrev)); } } @@ -172,7 +172,7 @@ static void show_children(struct rev_info *opt, struct commit *commit, int abbre { struct commit_list *p = lookup_decoration(&opt->children, &commit->object); for ( ; p; p = p->next) { - printf(" %s", find_unique_abbrev(p->item->object.oid.hash, abbrev)); + fprintf(opt->diffopt.file, " %s", find_unique_abbrev(p->item->object.oid.hash, abbrev)); } } @@ -286,11 +286,11 @@ void show_decorations(struct rev_info *opt, struct commit *commit) struct strbuf sb = STRBUF_INIT; if (opt->show_source && commit->util) - printf("\t%s", (char *) commit->util); + fprintf(opt->diffopt.file, "\t%s", (char *) commit->util); if (!opt->show_decorations) return; format_decorations(&sb, commit, opt->diffopt.use_color); - fputs(sb.buf, stdout); + fputs(sb.buf, opt->diffopt.file); strbuf_release(&sb); } @@ -364,18 +364,18 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, subject = "Subject: "; } - printf("From %s Mon Sep 17 00:00:00 2001\n", name); + fprintf(opt->diffopt.file, "From %s Mon Sep 17 00:00:00 2001\n", name); graph_show_oneline(opt->graph); if (opt->message_id) { - printf("Message-Id: <%s>\n", opt->message_id); + fprintf(opt->diffopt.file, "Message-Id: <%s>\n", opt->message_id); graph_show_oneline(opt->graph); } if (opt->ref_message_ids && opt->ref_message_ids->nr > 0) { int i, n; n = opt->ref_message_ids->nr; - printf("In-Reply-To: <%s>\n", opt->ref_message_ids->items[n-1].string); + fprintf(opt->diffopt.file, "In-Reply-To: <%s>\n", opt->ref_message_ids->items[n-1].string); for (i = 0; i < n; i++) - printf("%s<%s>\n", (i > 0 ? "\t" : "References: "), + fprintf(opt->diffopt.file, "%s<%s>\n", (i > 0 ? "\t" : "References: "), opt->ref_message_ids->items[i].string); graph_show_oneline(opt->graph); } @@ -432,7 +432,7 @@ static void show_sig_lines(struct rev_info *opt, int status, const char *bol) reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET); while (*bol) { eol = strchrnul(bol, '\n'); - printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset, + fprintf(opt->diffopt.file, "%s%.*s%s%s", color, (int)(eol - bol), bol, reset, *eol ? "\n" : ""); graph_show_oneline(opt->graph); bol = (*eol) ? (eol + 1) : eol; @@ -553,17 +553,17 @@ void show_log(struct rev_info *opt) if (!opt->graph) put_revision_mark(opt, commit); - fputs(find_unique_abbrev(commit->object.oid.hash, abbrev_commit), stdout); + fputs(find_unique_abbrev(commit->object.oid.hash, abbrev_commit), opt->diffopt.file); if (opt->print_parents) - show_parents(commit, abbrev_commit); + show_parents(commit, abbrev_commit, opt->diffopt.file); if (opt->children.name) show_children(opt, commit, abbrev_commit); show_decorations(opt, commit); if (opt->graph && !graph_is_commit_finished(opt->graph)) { - putchar('\n'); + putc('\n', opt->diffopt.file); graph_show_remainder(opt->graph); } - putchar(opt->diffopt.line_termination); + putc(opt->diffopt.line_termination, opt->diffopt.file); return; } @@ -589,7 +589,7 @@ void show_log(struct rev_info *opt) if (opt->diffopt.line_termination == '\n' && !opt->missing_newline) graph_show_padding(opt->graph); - putchar(opt->diffopt.line_termination); + putc(opt->diffopt.line_termination, opt->diffopt.file); } opt->shown_one = 1; @@ -607,28 +607,28 @@ void show_log(struct rev_info *opt) log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, &ctx.need_8bit_cte); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { - fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); + fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), opt->diffopt.file); if (opt->commit_format != CMIT_FMT_ONELINE) - fputs("commit ", stdout); + fputs("commit ", opt->diffopt.file); if (!opt->graph) put_revision_mark(opt, commit); fputs(find_unique_abbrev(commit->object.oid.hash, abbrev_commit), - stdout); + opt->diffopt.file); if (opt->print_parents) - show_parents(commit, abbrev_commit); + show_parents(commit, abbrev_commit, opt->diffopt.file); if (opt->children.name) show_children(opt, commit, abbrev_commit); if (parent) - printf(" (from %s)", + fprintf(opt->diffopt.file, " (from %s)", find_unique_abbrev(parent->object.oid.hash, abbrev_commit)); - fputs(diff_get_color_opt(&opt->diffopt, DIFF_RESET), stdout); + fputs(diff_get_color_opt(&opt->diffopt, DIFF_RESET), opt->diffopt.file); show_decorations(opt, commit); if (opt->commit_format == CMIT_FMT_ONELINE) { - putchar(' '); + putc(' ', opt->diffopt.file); } else { - putchar('\n'); + putc('\n', opt->diffopt.file); graph_show_oneline(opt->graph); } if (opt->reflog_info) { @@ -704,7 +704,7 @@ void show_log(struct rev_info *opt) } if (opt->show_log_size) { - printf("log size %i\n", (int)msgbuf.len); + fprintf(opt->diffopt.file, "log size %i\n", (int)msgbuf.len); graph_show_oneline(opt->graph); } @@ -720,11 +720,11 @@ void show_log(struct rev_info *opt) if (opt->graph) graph_show_commit_msg(opt->graph, &msgbuf); else - fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout); + fwrite(msgbuf.buf, sizeof(char), msgbuf.len, opt->diffopt.file); if (opt->use_terminator && !commit_format_is_empty(opt->commit_format)) { if (!opt->missing_newline) graph_show_padding(opt->graph); - putchar(opt->diffopt.line_termination); + putc(opt->diffopt.line_termination, opt->diffopt.file); } strbuf_release(&msgbuf); @@ -761,7 +761,7 @@ int log_tree_diff_flush(struct rev_info *opt) struct strbuf *msg = NULL; msg = opt->diffopt.output_prefix(&opt->diffopt, opt->diffopt.output_prefix_data); - fwrite(msg->buf, msg->len, 1, stdout); + fwrite(msg->buf, msg->len, 1, opt->diffopt.file); } /* @@ -776,8 +776,8 @@ int log_tree_diff_flush(struct rev_info *opt) */ if (!opt->shown_dashes && (pch & opt->diffopt.output_format) == pch) - printf("---"); - putchar('\n'); + fprintf(opt->diffopt.file, "---"); + putc('\n', opt->diffopt.file); } } diff_flush(&opt->diffopt); @@ -864,17 +864,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log int log_tree_commit(struct rev_info *opt, struct commit *commit) { struct log_info log; - int shown; + int shown, close_file = opt->diffopt.close_file; log.commit = commit; log.parent = NULL; opt->loginfo = &log; + opt->diffopt.close_file = 0; if (opt->line_level_traverse) return line_log_print(opt, commit); if (opt->track_linear && !opt->linear && !opt->reverse_output_stage) - printf("\n%s\n", opt->break_bar); + fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); shown = log_tree_diff(opt, commit, &log); if (!shown && opt->loginfo && opt->always_show_header) { log.parent = NULL; @@ -882,8 +883,10 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) shown = 1; } if (opt->track_linear && !opt->linear && opt->reverse_output_stage) - printf("\n%s\n", opt->break_bar); + fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); opt->loginfo = NULL; - maybe_flush_or_die(stdout, "stdout"); + maybe_flush_or_die(opt->diffopt.file, "stdout"); + if (close_file) + fclose(opt->diffopt.file); return shown; } diff --git a/merge-recursive.c b/merge-recursive.c index 65cb5d6c1f..a4a1195f61 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -56,11 +56,11 @@ static struct commit *make_virtual_commit(struct tree *tree, const char *comment * Since we use get_tree_entry(), which does not put the read object into * the object pool, we cannot rely on a == b. */ -static int sha_eq(const unsigned char *a, const unsigned char *b) +static int oid_eq(const struct object_id *a, const struct object_id *b) { if (!a && !b) return 2; - return a && b && hashcmp(a, b) == 0; + return a && b && oidcmp(a, b) == 0; } enum rename_type { @@ -90,7 +90,7 @@ struct rename_conflict_info { struct stage_data { struct { unsigned mode; - unsigned char sha[20]; + struct object_id oid; } stages[4]; struct rename_conflict_info *rename_conflict_info; unsigned processed:1; @@ -134,11 +134,11 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type, int ostage2 = ostage1 ^ 1; ci->ren1_other.path = pair1->one->path; - hashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha); + oidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid); ci->ren1_other.mode = src_entry1->stages[ostage1].mode; ci->ren2_other.path = pair2->one->path; - hashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha); + oidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid); ci->ren2_other.mode = src_entry2->stages[ostage2].mode; } } @@ -198,16 +198,25 @@ static void output_commit_title(struct merge_options *o, struct commit *commit) } } -static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, +static int add_cacheinfo(unsigned int mode, const struct object_id *oid, const char *path, int stage, int refresh, int options) { struct cache_entry *ce; - ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, - (refresh ? (CE_MATCH_REFRESH | - CE_MATCH_IGNORE_MISSING) : 0 )); + int ret; + + ce = make_cache_entry(mode, oid ? oid->hash : null_sha1, path, stage, 0); if (!ce) return error(_("addinfo_cache failed for path '%s'"), path); - return add_cache_entry(ce, options); + + ret = add_cache_entry(ce, options); + if (refresh) { + struct cache_entry *nce; + + nce = refresh_cache_entry(ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING); + if (nce != ce) + ret = add_cache_entry(nce, options); + } + return ret; } static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) @@ -314,11 +323,11 @@ static struct stage_data *insert_stage_data(const char *path, struct string_list_item *item; struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); get_tree_entry(o->object.oid.hash, path, - e->stages[1].sha, &e->stages[1].mode); + e->stages[1].oid.hash, &e->stages[1].mode); get_tree_entry(a->object.oid.hash, path, - e->stages[2].sha, &e->stages[2].mode); + e->stages[2].oid.hash, &e->stages[2].mode); get_tree_entry(b->object.oid.hash, path, - e->stages[3].sha, &e->stages[3].mode); + e->stages[3].oid.hash, &e->stages[3].mode); item = string_list_insert(entries, path); item->util = e; return e; @@ -349,7 +358,7 @@ static struct string_list *get_unmerged(void) } e = item->util; e->stages[ce_stage(ce)].mode = ce->ce_mode; - hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); + hashcpy(e->stages[ce_stage(ce)].oid.hash, ce->sha1); } return unmerged; @@ -552,13 +561,13 @@ static int update_stages(const char *path, const struct diff_filespec *o, if (remove_file_from_cache(path)) return -1; if (o) - if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) + if (add_cacheinfo(o->mode, &o->oid, path, 1, 0, options)) return -1; if (a) - if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) + if (add_cacheinfo(a->mode, &a->oid, path, 2, 0, options)) return -1; if (b) - if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) + if (add_cacheinfo(b->mode, &b->oid, path, 3, 0, options)) return -1; return 0; } @@ -572,9 +581,9 @@ static void update_entry(struct stage_data *entry, entry->stages[1].mode = o->mode; entry->stages[2].mode = a->mode; entry->stages[3].mode = b->mode; - hashcpy(entry->stages[1].sha, o->sha1); - hashcpy(entry->stages[2].sha, a->sha1); - hashcpy(entry->stages[3].sha, b->sha1); + oidcpy(&entry->stages[1].oid, &o->oid); + oidcpy(&entry->stages[2].oid, &a->oid); + oidcpy(&entry->stages[3].oid, &b->oid); } static int remove_file(struct merge_options *o, int clean, @@ -736,7 +745,7 @@ static int make_room_for_path(struct merge_options *o, const char *path) } static void update_file_flags(struct merge_options *o, - const unsigned char *sha, + const struct object_id *oid, unsigned mode, const char *path, int update_cache, @@ -760,11 +769,11 @@ static void update_file_flags(struct merge_options *o, goto update_index; } - buf = read_sha1_file(sha, &type, &size); + buf = read_sha1_file(oid->hash, &type, &size); if (!buf) - die(_("cannot read object %s '%s'"), sha1_to_hex(sha), path); + die(_("cannot read object %s '%s'"), oid_to_hex(oid), path); if (type != OBJ_BLOB) - die(_("blob expected for %s '%s'"), sha1_to_hex(sha), path); + die(_("blob expected for %s '%s'"), oid_to_hex(oid), path); if (S_ISREG(mode)) { struct strbuf strbuf = STRBUF_INIT; if (convert_to_working_tree(path, buf, size, &strbuf)) { @@ -799,27 +808,27 @@ static void update_file_flags(struct merge_options *o, free(lnk); } else die(_("do not know what to do with %06o %s '%s'"), - mode, sha1_to_hex(sha), path); + mode, oid_to_hex(oid), path); free(buf); } update_index: if (update_cache) - add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); + add_cacheinfo(mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); } static void update_file(struct merge_options *o, int clean, - const unsigned char *sha, + const struct object_id *oid, unsigned mode, const char *path) { - update_file_flags(o, sha, mode, path, o->call_depth || clean, !o->call_depth); + update_file_flags(o, oid, mode, path, o->call_depth || clean, !o->call_depth); } /* Low level file merging, update and removal */ struct merge_file_info { - unsigned char sha[20]; + struct object_id oid; unsigned mode; unsigned clean:1, merge:1; @@ -871,9 +880,9 @@ static int merge_3way(struct merge_options *o, name2 = mkpathdup("%s", branch2); } - read_mmblob(&orig, one->sha1); - read_mmblob(&src1, a->sha1); - read_mmblob(&src2, b->sha1); + read_mmblob(&orig, one->oid.hash); + read_mmblob(&src1, a->oid.hash); + read_mmblob(&src2, b->oid.hash); merge_status = ll_merge(result_buf, a->path, &orig, base_name, &src1, name1, &src2, name2, &ll_opts); @@ -902,13 +911,13 @@ static struct merge_file_info merge_file_1(struct merge_options *o, result.clean = 0; if (S_ISREG(a->mode)) { result.mode = a->mode; - hashcpy(result.sha, a->sha1); + oidcpy(&result.oid, &a->oid); } else { result.mode = b->mode; - hashcpy(result.sha, b->sha1); + oidcpy(&result.oid, &b->oid); } } else { - if (!sha_eq(a->sha1, one->sha1) && !sha_eq(b->sha1, one->sha1)) + if (!oid_eq(&a->oid, &one->oid) && !oid_eq(&b->oid, &one->oid)) result.merge = 1; /* @@ -924,10 +933,10 @@ static struct merge_file_info merge_file_1(struct merge_options *o, } } - if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, one->sha1)) - hashcpy(result.sha, b->sha1); - else if (sha_eq(b->sha1, one->sha1)) - hashcpy(result.sha, a->sha1); + if (oid_eq(&a->oid, &b->oid) || oid_eq(&a->oid, &one->oid)) + oidcpy(&result.oid, &b->oid); + else if (oid_eq(&b->oid, &one->oid)) + oidcpy(&result.oid, &a->oid); else if (S_ISREG(a->mode)) { mmbuffer_t result_buf; int merge_status; @@ -939,21 +948,23 @@ static struct merge_file_info merge_file_1(struct merge_options *o, die(_("Failed to execute internal merge")); if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, result.sha)) + blob_type, result.oid.hash)) die(_("Unable to add %s to database"), a->path); free(result_buf.ptr); result.clean = (merge_status == 0); } else if (S_ISGITLINK(a->mode)) { - result.clean = merge_submodule(result.sha, - one->path, one->sha1, - a->sha1, b->sha1, + result.clean = merge_submodule(result.oid.hash, + one->path, + one->oid.hash, + a->oid.hash, + b->oid.hash, !o->call_depth); } else if (S_ISLNK(a->mode)) { - hashcpy(result.sha, a->sha1); + oidcpy(&result.oid, &a->oid); - if (!sha_eq(a->sha1, b->sha1)) + if (!oid_eq(&a->oid, &b->oid)) result.clean = 0; } else { die(_("unsupported object type in the tree")); @@ -991,34 +1002,34 @@ merge_file_special_markers(struct merge_options *o, static struct merge_file_info merge_file_one(struct merge_options *o, const char *path, - const unsigned char *o_sha, int o_mode, - const unsigned char *a_sha, int a_mode, - const unsigned char *b_sha, int b_mode, + const struct object_id *o_oid, int o_mode, + const struct object_id *a_oid, int a_mode, + const struct object_id *b_oid, int b_mode, const char *branch1, const char *branch2) { struct diff_filespec one, a, b; one.path = a.path = b.path = (char *)path; - hashcpy(one.sha1, o_sha); + oidcpy(&one.oid, o_oid); one.mode = o_mode; - hashcpy(a.sha1, a_sha); + oidcpy(&a.oid, a_oid); a.mode = a_mode; - hashcpy(b.sha1, b_sha); + oidcpy(&b.oid, b_oid); b.mode = b_mode; return merge_file_1(o, &one, &a, &b, branch1, branch2); } static void handle_change_delete(struct merge_options *o, const char *path, - const unsigned char *o_sha, int o_mode, - const unsigned char *a_sha, int a_mode, - const unsigned char *b_sha, int b_mode, + const struct object_id *o_oid, int o_mode, + const struct object_id *a_oid, int a_mode, + const struct object_id *b_oid, int b_mode, const char *change, const char *change_past) { char *renamed = NULL; if (dir_in_way(path, !o->call_depth)) { - renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2); + renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2); } if (o->call_depth) { @@ -1028,20 +1039,20 @@ static void handle_change_delete(struct merge_options *o, * them, simply reuse the base version for virtual merge base. */ remove_file_from_cache(path); - update_file(o, 0, o_sha, o_mode, renamed ? renamed : path); - } else if (!a_sha) { + update_file(o, 0, o_oid, o_mode, renamed ? renamed : path); + } else if (!a_oid) { if (!renamed) { output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " "and %s in %s. Version %s of %s left in tree."), change, path, o->branch1, change_past, o->branch2, o->branch2, path); - update_file(o, 0, b_sha, b_mode, path); + update_file(o, 0, b_oid, b_mode, path); } else { output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " "and %s in %s. Version %s of %s left in tree at %s."), change, path, o->branch1, change_past, o->branch2, o->branch2, path, renamed); - update_file(o, 0, b_sha, b_mode, renamed); + update_file(o, 0, b_oid, b_mode, renamed); } } else { if (!renamed) { @@ -1054,7 +1065,7 @@ static void handle_change_delete(struct merge_options *o, "and %s in %s. Version %s of %s left in tree at %s."), change, path, o->branch2, change_past, o->branch1, o->branch1, path, renamed); - update_file(o, 0, a_sha, a_mode, renamed); + update_file(o, 0, a_oid, a_mode, renamed); } /* * No need to call update_file() on path when !renamed, since @@ -1073,24 +1084,24 @@ static void conflict_rename_delete(struct merge_options *o, { const struct diff_filespec *orig = pair->one; const struct diff_filespec *dest = pair->two; - const unsigned char *a_sha = NULL; - const unsigned char *b_sha = NULL; + const struct object_id *a_oid = NULL; + const struct object_id *b_oid = NULL; int a_mode = 0; int b_mode = 0; if (rename_branch == o->branch1) { - a_sha = dest->sha1; + a_oid = &dest->oid; a_mode = dest->mode; } else { - b_sha = dest->sha1; + b_oid = &dest->oid; b_mode = dest->mode; } handle_change_delete(o, o->call_depth ? orig->path : dest->path, - orig->sha1, orig->mode, - a_sha, a_mode, - b_sha, b_mode, + &orig->oid, orig->mode, + a_oid, a_mode, + b_oid, b_mode, _("rename"), _("renamed")); if (o->call_depth) { @@ -1107,11 +1118,11 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target, struct stage_data *entry, int stage) { - unsigned char *sha = entry->stages[stage].sha; + struct object_id *oid = &entry->stages[stage].oid; unsigned mode = entry->stages[stage].mode; - if (mode == 0 || is_null_sha1(sha)) + if (mode == 0 || is_null_oid(oid)) return NULL; - hashcpy(target->sha1, sha); + oidcpy(&target->oid, oid); target->mode = mode; return target; } @@ -1140,7 +1151,7 @@ static void handle_file(struct merge_options *o, add = filespec_from_entry(&other, dst_entry, stage ^ 1); if (add) { char *add_name = unique_path(o, rename->path, other_branch); - update_file(o, 0, add->sha1, add->mode, add_name); + update_file(o, 0, &add->oid, add->mode, add_name); remove_file(o, 0, rename->path, 0); dst_name = unique_path(o, rename->path, cur_branch); @@ -1151,7 +1162,7 @@ static void handle_file(struct merge_options *o, rename->path, other_branch, dst_name); } } - update_file(o, 0, rename->sha1, rename->mode, dst_name); + update_file(o, 0, &rename->oid, rename->mode, dst_name); if (stage == 2) update_stages(rename->path, NULL, rename, add); else @@ -1180,9 +1191,9 @@ static void conflict_rename_rename_1to2(struct merge_options *o, struct diff_filespec other; struct diff_filespec *add; mfi = merge_file_one(o, one->path, - one->sha1, one->mode, - a->sha1, a->mode, - b->sha1, b->mode, + &one->oid, one->mode, + &a->oid, a->mode, + &b->oid, b->mode, ci->branch1, ci->branch2); /* * FIXME: For rename/add-source conflicts (if we could detect @@ -1190,7 +1201,7 @@ static void conflict_rename_rename_1to2(struct merge_options *o, * pathname and then either rename the add-source file to that * unique path, or use that unique path instead of src here. */ - update_file(o, 0, mfi.sha, mfi.mode, one->path); + update_file(o, 0, &mfi.oid, mfi.mode, one->path); /* * Above, we put the merged content at the merge-base's @@ -1202,12 +1213,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o, */ add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1); if (add) - update_file(o, 0, add->sha1, add->mode, a->path); + update_file(o, 0, &add->oid, add->mode, a->path); else remove_file_from_cache(a->path); add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1); if (add) - update_file(o, 0, add->sha1, add->mode, b->path); + update_file(o, 0, &add->oid, add->mode, b->path); else remove_file_from_cache(b->path); } else { @@ -1253,16 +1264,16 @@ static void conflict_rename_rename_2to1(struct merge_options *o, * again later for the non-recursive merge. */ remove_file(o, 0, path, 0); - update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path); - update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path); + update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path); + update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, b->path); } else { char *new_path1 = unique_path(o, path, ci->branch1); char *new_path2 = unique_path(o, path, ci->branch2); output(o, 1, _("Renaming %s to %s and %s to %s instead"), a->path, new_path1, b->path, new_path2); remove_file(o, 0, path, 0); - update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1); - update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2); + update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1); + update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, new_path2); free(new_path2); free(new_path1); } @@ -1421,13 +1432,15 @@ static int process_renames(struct merge_options *o, remove_file(o, 1, ren1_src, renamed_stage == 2 || !was_tracked(ren1_src)); - hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha); + oidcpy(&src_other.oid, + &ren1->src_entry->stages[other_stage].oid); src_other.mode = ren1->src_entry->stages[other_stage].mode; - hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha); + oidcpy(&dst_other.oid, + &ren1->dst_entry->stages[other_stage].oid); dst_other.mode = ren1->dst_entry->stages[other_stage].mode; try_merge = 0; - if (sha_eq(src_other.sha1, null_sha1)) { + if (oid_eq(&src_other.oid, &null_oid)) { setup_rename_conflict_info(RENAME_DELETE, ren1->pair, NULL, @@ -1439,7 +1452,7 @@ static int process_renames(struct merge_options *o, NULL, NULL); } else if ((dst_other.mode == ren1->pair->two->mode) && - sha_eq(dst_other.sha1, ren1->pair->two->sha1)) { + oid_eq(&dst_other.oid, &ren1->pair->two->oid)) { /* * Added file on the other side identical to * the file being renamed: clean merge. @@ -1449,12 +1462,12 @@ static int process_renames(struct merge_options *o, * update_file(). */ update_file_flags(o, - ren1->pair->two->sha1, + &ren1->pair->two->oid, ren1->pair->two->mode, ren1_dst, 1, /* update_cache */ 0 /* update_wd */); - } else if (!sha_eq(dst_other.sha1, null_sha1)) { + } else if (!oid_eq(&dst_other.oid, &null_oid)) { clean_merge = 0; try_merge = 1; output(o, 1, _("CONFLICT (rename/add): Rename %s->%s in %s. " @@ -1463,17 +1476,21 @@ static int process_renames(struct merge_options *o, ren1_dst, branch2); if (o->call_depth) { struct merge_file_info mfi; - mfi = merge_file_one(o, ren1_dst, null_sha1, 0, - ren1->pair->two->sha1, ren1->pair->two->mode, - dst_other.sha1, dst_other.mode, + mfi = merge_file_one(o, ren1_dst, &null_oid, 0, + &ren1->pair->two->oid, + ren1->pair->two->mode, + &dst_other.oid, + dst_other.mode, branch1, branch2); output(o, 1, _("Adding merged %s"), ren1_dst); - update_file(o, 0, mfi.sha, mfi.mode, ren1_dst); + update_file(o, 0, &mfi.oid, + mfi.mode, ren1_dst); try_merge = 0; } else { char *new_path = unique_path(o, ren1_dst, branch2); output(o, 1, _("Adding as %s instead"), new_path); - update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); + update_file(o, 0, &dst_other.oid, + dst_other.mode, new_path); free(new_path); } } else @@ -1511,30 +1528,30 @@ static int process_renames(struct merge_options *o, return clean_merge; } -static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) +static struct object_id *stage_oid(const struct object_id *oid, unsigned mode) { - return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; + return (is_null_oid(oid) || mode == 0) ? NULL: (struct object_id *)oid; } -static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst) +static int read_oid_strbuf(const struct object_id *oid, struct strbuf *dst) { void *buf; enum object_type type; unsigned long size; - buf = read_sha1_file(sha1, &type, &size); + buf = read_sha1_file(oid->hash, &type, &size); if (!buf) - return error(_("cannot read object %s"), sha1_to_hex(sha1)); + return error(_("cannot read object %s"), oid_to_hex(oid)); if (type != OBJ_BLOB) { free(buf); - return error(_("object %s is not a blob"), sha1_to_hex(sha1)); + return error(_("object %s is not a blob"), oid_to_hex(oid)); } strbuf_attach(dst, buf, size, size + 1); return 0; } -static int blob_unchanged(const unsigned char *o_sha, +static int blob_unchanged(const struct object_id *o_oid, unsigned o_mode, - const unsigned char *a_sha, + const struct object_id *a_oid, unsigned a_mode, int renormalize, const char *path) { @@ -1544,13 +1561,13 @@ static int blob_unchanged(const unsigned char *o_sha, if (a_mode != o_mode) return 0; - if (sha_eq(o_sha, a_sha)) + if (oid_eq(o_oid, a_oid)) return 1; if (!renormalize) return 0; - assert(o_sha && a_sha); - if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a)) + assert(o_oid && a_oid); + if (read_oid_strbuf(o_oid, &o) || read_oid_strbuf(a_oid, &a)) goto error_return; /* * Note: binary | is used so that both renormalizations are @@ -1569,23 +1586,23 @@ error_return: static void handle_modify_delete(struct merge_options *o, const char *path, - unsigned char *o_sha, int o_mode, - unsigned char *a_sha, int a_mode, - unsigned char *b_sha, int b_mode) + struct object_id *o_oid, int o_mode, + struct object_id *a_oid, int a_mode, + struct object_id *b_oid, int b_mode) { handle_change_delete(o, path, - o_sha, o_mode, - a_sha, a_mode, - b_sha, b_mode, + o_oid, o_mode, + a_oid, a_mode, + b_oid, b_mode, _("modify"), _("modified")); } static int merge_content(struct merge_options *o, const char *path, - unsigned char *o_sha, int o_mode, - unsigned char *a_sha, int a_mode, - unsigned char *b_sha, int b_mode, + struct object_id *o_oid, int o_mode, + struct object_id *a_oid, int a_mode, + struct object_id *b_oid, int b_mode, struct rename_conflict_info *rename_conflict_info) { const char *reason = _("content"); @@ -1594,16 +1611,16 @@ static int merge_content(struct merge_options *o, struct diff_filespec one, a, b; unsigned df_conflict_remains = 0; - if (!o_sha) { + if (!o_oid) { reason = _("add/add"); - o_sha = (unsigned char *)null_sha1; + o_oid = (struct object_id *)&null_oid; } one.path = a.path = b.path = (char *)path; - hashcpy(one.sha1, o_sha); + oidcpy(&one.oid, o_oid); one.mode = o_mode; - hashcpy(a.sha1, a_sha); + oidcpy(&a.oid, a_oid); a.mode = a_mode; - hashcpy(b.sha1, b_sha); + oidcpy(&b.oid, b_oid); b.mode = b_mode; if (rename_conflict_info) { @@ -1627,7 +1644,7 @@ static int merge_content(struct merge_options *o, o->branch2, path2); if (mfi.clean && !df_conflict_remains && - sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) { + oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) { int path_renamed_outside_HEAD; output(o, 3, _("Skipped %s (merged same as existing)"), path); /* @@ -1638,7 +1655,7 @@ static int merge_content(struct merge_options *o, */ path_renamed_outside_HEAD = !path2 || !strcmp(path, path2); if (!path_renamed_outside_HEAD) { - add_cacheinfo(mfi.mode, mfi.sha, path, + add_cacheinfo(mfi.mode, &mfi.oid, path, 0, (!o->call_depth), 0); return mfi.clean; } @@ -1664,7 +1681,7 @@ static int merge_content(struct merge_options *o, else { int file_from_stage2 = was_tracked(path); struct diff_filespec merged; - hashcpy(merged.sha1, mfi.sha); + oidcpy(&merged.oid, &mfi.oid); merged.mode = mfi.mode; update_stages(path, NULL, @@ -1675,11 +1692,11 @@ static int merge_content(struct merge_options *o, } new_path = unique_path(o, path, rename_conflict_info->branch1); output(o, 1, _("Adding as %s instead"), new_path); - update_file(o, 0, mfi.sha, mfi.mode, new_path); + update_file(o, 0, &mfi.oid, mfi.mode, new_path); free(new_path); mfi.clean = 0; } else { - update_file(o, mfi.clean, mfi.sha, mfi.mode, path); + update_file(o, mfi.clean, &mfi.oid, mfi.mode, path); } return mfi.clean; @@ -1694,9 +1711,9 @@ static int process_entry(struct merge_options *o, unsigned o_mode = entry->stages[1].mode; unsigned a_mode = entry->stages[2].mode; unsigned b_mode = entry->stages[3].mode; - unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); - unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); - unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); + struct object_id *o_oid = stage_oid(&entry->stages[1].oid, o_mode); + struct object_id *a_oid = stage_oid(&entry->stages[2].oid, a_mode); + struct object_id *b_oid = stage_oid(&entry->stages[3].oid, b_mode); entry->processed = 1; if (entry->rename_conflict_info) { @@ -1705,7 +1722,7 @@ static int process_entry(struct merge_options *o, case RENAME_NORMAL: case RENAME_ONE_FILE_TO_ONE: clean_merge = merge_content(o, path, - o_sha, o_mode, a_sha, a_mode, b_sha, b_mode, + o_oid, o_mode, a_oid, a_mode, b_oid, b_mode, conflict_info); break; case RENAME_DELETE: @@ -1726,45 +1743,45 @@ static int process_entry(struct merge_options *o, entry->processed = 0; break; } - } else if (o_sha && (!a_sha || !b_sha)) { + } else if (o_oid && (!a_oid || !b_oid)) { /* Case A: Deleted in one */ - if ((!a_sha && !b_sha) || - (!b_sha && blob_unchanged(o_sha, o_mode, a_sha, a_mode, normalize, path)) || - (!a_sha && blob_unchanged(o_sha, o_mode, b_sha, b_mode, normalize, path))) { + if ((!a_oid && !b_oid) || + (!b_oid && blob_unchanged(o_oid, o_mode, a_oid, a_mode, normalize, path)) || + (!a_oid && blob_unchanged(o_oid, o_mode, b_oid, b_mode, normalize, path))) { /* Deleted in both or deleted in one and * unchanged in the other */ - if (a_sha) + if (a_oid) output(o, 2, _("Removing %s"), path); /* do not touch working file if it did not exist */ - remove_file(o, 1, path, !a_sha); + remove_file(o, 1, path, !a_oid); } else { /* Modify/delete; deleted side may have put a directory in the way */ clean_merge = 0; - handle_modify_delete(o, path, o_sha, o_mode, - a_sha, a_mode, b_sha, b_mode); + handle_modify_delete(o, path, o_oid, o_mode, + a_oid, a_mode, b_oid, b_mode); } - } else if ((!o_sha && a_sha && !b_sha) || - (!o_sha && !a_sha && b_sha)) { + } else if ((!o_oid && a_oid && !b_oid) || + (!o_oid && !a_oid && b_oid)) { /* Case B: Added in one. */ /* [nothing|directory] -> ([nothing|directory], file) */ const char *add_branch; const char *other_branch; unsigned mode; - const unsigned char *sha; + const struct object_id *oid; const char *conf; - if (a_sha) { + if (a_oid) { add_branch = o->branch1; other_branch = o->branch2; mode = a_mode; - sha = a_sha; + oid = a_oid; conf = _("file/directory"); } else { add_branch = o->branch2; other_branch = o->branch1; mode = b_mode; - sha = b_sha; + oid = b_oid; conf = _("directory/file"); } if (dir_in_way(path, !o->call_depth)) { @@ -1773,22 +1790,22 @@ static int process_entry(struct merge_options *o, output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s"), conf, path, other_branch, path, new_path); - update_file(o, 0, sha, mode, new_path); + update_file(o, 0, oid, mode, new_path); if (o->call_depth) remove_file_from_cache(path); free(new_path); } else { output(o, 2, _("Adding %s"), path); /* do not overwrite file if already present */ - update_file_flags(o, sha, mode, path, 1, !a_sha); + update_file_flags(o, oid, mode, path, 1, !a_oid); } - } else if (a_sha && b_sha) { + } else if (a_oid && b_oid) { /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ clean_merge = merge_content(o, path, - o_sha, o_mode, a_sha, a_mode, b_sha, b_mode, + o_oid, o_mode, a_oid, a_mode, b_oid, b_mode, NULL); - } else if (!o_sha && !a_sha && !b_sha) { + } else if (!o_oid && !a_oid && !b_oid) { /* * this entry was deleted altogether. a_mode == 0 means * we had that path and want to actively remove it. @@ -1813,7 +1830,7 @@ int merge_trees(struct merge_options *o, common = shift_tree_object(head, common, o->subtree_shift); } - if (sha_eq(common->object.oid.hash, merge->object.oid.hash)) { + if (oid_eq(&common->object.oid, &merge->object.oid)) { output(o, 0, _("Already up-to-date!")); *result = head; return 1; @@ -1974,11 +1991,11 @@ int merge_recursive(struct merge_options *o, return clean; } -static struct commit *get_ref(const unsigned char *sha1, const char *name) +static struct commit *get_ref(const struct object_id *oid, const char *name) { struct object *object; - object = deref_tag(parse_object(sha1), name, strlen(name)); + object = deref_tag(parse_object(oid->hash), name, strlen(name)); if (!object) return NULL; if (object->type == OBJ_TREE) @@ -1991,10 +2008,10 @@ static struct commit *get_ref(const unsigned char *sha1, const char *name) } int merge_recursive_generic(struct merge_options *o, - const unsigned char *head, - const unsigned char *merge, + const struct object_id *head, + const struct object_id *merge, int num_base_list, - const unsigned char **base_list, + const struct object_id **base_list, struct commit **result) { int clean; @@ -2007,9 +2024,9 @@ int merge_recursive_generic(struct merge_options *o, int i; for (i = 0; i < num_base_list; ++i) { struct commit *base; - if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i])))) + if (!(base = get_ref(base_list[i], oid_to_hex(base_list[i])))) return error(_("Could not parse object '%s'"), - sha1_to_hex(base_list[i])); + oid_to_hex(base_list[i])); commit_list_insert(base, &ca); } } diff --git a/merge-recursive.h b/merge-recursive.h index 52f0201f68..d415724aea 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -49,10 +49,10 @@ int merge_trees(struct merge_options *o, * virtual commits and call merge_recursive() proper. */ int merge_recursive_generic(struct merge_options *o, - const unsigned char *head, - const unsigned char *merge, + const struct object_id *head, + const struct object_id *merge, int num_ca, - const unsigned char **ca, + const struct object_id **ca, struct commit **result); void init_merge_options(struct merge_options *o); diff --git a/notes-merge.c b/notes-merge.c index b7814c9b64..97fc42f64b 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -41,14 +41,14 @@ static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1) switch (p->status) { case DIFF_STATUS_MODIFIED: assert(p->one->mode == p->two->mode); - assert(!is_null_sha1(p->one->sha1)); - assert(!is_null_sha1(p->two->sha1)); + assert(!is_null_oid(&p->one->oid)); + assert(!is_null_oid(&p->two->oid)); break; case DIFF_STATUS_ADDED: - assert(is_null_sha1(p->one->sha1)); + assert(is_null_oid(&p->one->oid)); break; case DIFF_STATUS_DELETED: - assert(is_null_sha1(p->two->sha1)); + assert(is_null_oid(&p->two->oid)); break; default: return -1; @@ -142,27 +142,27 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, if (verify_notes_filepair(p, obj)) { trace_printf("\t\tCannot merge entry '%s' (%c): " "%.7s -> %.7s. Skipping!\n", p->one->path, - p->status, sha1_to_hex(p->one->sha1), - sha1_to_hex(p->two->sha1)); + p->status, oid_to_hex(&p->one->oid), + oid_to_hex(&p->two->oid)); continue; } mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied); if (occupied) { /* We've found an addition/deletion pair */ assert(!hashcmp(mp->obj, obj)); - if (is_null_sha1(p->one->sha1)) { /* addition */ + if (is_null_oid(&p->one->oid)) { /* addition */ assert(is_null_sha1(mp->remote)); - hashcpy(mp->remote, p->two->sha1); - } else if (is_null_sha1(p->two->sha1)) { /* deletion */ + hashcpy(mp->remote, p->two->oid.hash); + } else if (is_null_oid(&p->two->oid)) { /* deletion */ assert(is_null_sha1(mp->base)); - hashcpy(mp->base, p->one->sha1); + hashcpy(mp->base, p->one->oid.hash); } else assert(!"Invalid existing change recorded"); } else { hashcpy(mp->obj, obj); - hashcpy(mp->base, p->one->sha1); + hashcpy(mp->base, p->one->oid.hash); hashcpy(mp->local, uninitialized); - hashcpy(mp->remote, p->two->sha1); + hashcpy(mp->remote, p->two->oid.hash); len++; } trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n", @@ -203,21 +203,21 @@ static void diff_tree_local(struct notes_merge_options *o, if (verify_notes_filepair(p, obj)) { trace_printf("\t\tCannot merge entry '%s' (%c): " "%.7s -> %.7s. Skipping!\n", p->one->path, - p->status, sha1_to_hex(p->one->sha1), - sha1_to_hex(p->two->sha1)); + p->status, oid_to_hex(&p->one->oid), + oid_to_hex(&p->two->oid)); continue; } mp = find_notes_merge_pair_pos(changes, len, obj, 0, &match); if (!match) { trace_printf("\t\tIgnoring local-only change for %s: " "%.7s -> %.7s\n", sha1_to_hex(obj), - sha1_to_hex(p->one->sha1), - sha1_to_hex(p->two->sha1)); + oid_to_hex(&p->one->oid), + oid_to_hex(&p->two->oid)); continue; } assert(!hashcmp(mp->obj, obj)); - if (is_null_sha1(p->two->sha1)) { /* deletion */ + if (is_null_oid(&p->two->oid)) { /* deletion */ /* * Either this is a true deletion (1), or it is part * of an A/D pair (2), or D/A pair (3): @@ -229,7 +229,7 @@ static void diff_tree_local(struct notes_merge_options *o, */ if (!hashcmp(mp->local, uninitialized)) hashclr(mp->local); - } else if (is_null_sha1(p->one->sha1)) { /* addition */ + } else if (is_null_oid(&p->one->oid)) { /* addition */ /* * Either this is a true addition (1), or it is part * of an A/D pair (2), or D/A pair (3): @@ -240,16 +240,16 @@ static void diff_tree_local(struct notes_merge_options *o, */ assert(is_null_sha1(mp->local) || !hashcmp(mp->local, uninitialized)); - hashcpy(mp->local, p->two->sha1); + hashcpy(mp->local, p->two->oid.hash); } else { /* modification */ /* * This is a true modification. p->one->sha1 shall * match mp->base, and mp->local shall be uninitialized. * Set mp->local to p->two->sha1. */ - assert(!hashcmp(p->one->sha1, mp->base)); + assert(!hashcmp(p->one->oid.hash, mp->base)); assert(!hashcmp(mp->local, uninitialized)); - hashcpy(mp->local, p->two->sha1); + hashcpy(mp->local, p->two->oid.hash); } trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n", sha1_to_hex(mp->obj), sha1_to_hex(mp->base), @@ -298,12 +298,8 @@ static void write_buf_to_worktree(const unsigned char *obj, char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); if (safe_create_leading_directories_const(path)) die_errno("unable to create directory for '%s'", path); - if (file_exists(path)) - die("found existing file at '%s'", path); - fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0666); - if (fd < 0) - die_errno("failed to open '%s'", path); + fd = xopen(path, O_WRONLY | O_EXCL | O_CREAT, 0666); while (size > 0) { long ret = write_in_full(fd, buf, size); diff --git a/pack-check.c b/pack-check.c index 1da89a41ce..d123846ea2 100644 --- a/pack-check.c +++ b/pack-check.c @@ -105,6 +105,8 @@ static int verify_packfile(struct packed_git *p, void *data; enum object_type type; unsigned long size; + off_t curpos; + int data_valid; if (p->index_version > 1) { off_t offset = entries[i].offset; @@ -116,8 +118,25 @@ static int verify_packfile(struct packed_git *p, sha1_to_hex(entries[i].sha1), p->pack_name, (uintmax_t)offset); } - data = unpack_entry(p, entries[i].offset, &type, &size); - if (!data) + + curpos = entries[i].offset; + type = unpack_object_header(p, w_curs, &curpos, &size); + unuse_pack(w_curs); + + if (type == OBJ_BLOB && big_file_threshold <= size) { + /* + * Let check_sha1_signature() check it with + * the streaming interface; no point slurping + * the data in-core only to discard. + */ + data = NULL; + data_valid = 0; + } else { + data = unpack_entry(p, entries[i].offset, &type, &size); + data_valid = 1; + } + + if (data_valid && !data) err = error("cannot unpack %s from %s at offset %"PRIuMAX"", sha1_to_hex(entries[i].sha1), p->pack_name, (uintmax_t)entries[i].offset); @@ -74,6 +74,7 @@ struct pack_idx_entry { struct progress; +/* Note, the data argument could be NULL if object type is blob */ typedef int (*verify_fn)(const unsigned char*, enum object_type, unsigned long, void*, int*); extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, const unsigned char *sha1); @@ -483,7 +483,7 @@ static void do_submodule_path(struct strbuf *buf, const char *path, strbuf_addstr(buf, git_dir); } strbuf_addch(buf, '/'); - strbuf_addstr(&git_submodule_dir, buf->buf); + strbuf_addbuf(&git_submodule_dir, buf); strbuf_vaddf(buf, fmt, args); @@ -453,3 +453,40 @@ void tcl_quote_buf(struct strbuf *sb, const char *src) } strbuf_addch(sb, '"'); } + +void basic_regex_quote_buf(struct strbuf *sb, const char *src) +{ + char c; + + if (*src == '^') { + /* only beginning '^' is special and needs quoting */ + strbuf_addch(sb, '\\'); + strbuf_addch(sb, *src++); + } + if (*src == '*') + /* beginning '*' is not special, no quoting */ + strbuf_addch(sb, *src++); + + while ((c = *src++)) { + switch (c) { + case '[': + case '.': + case '\\': + case '*': + strbuf_addch(sb, '\\'); + strbuf_addch(sb, c); + break; + + case '$': + /* only the end '$' is special and needs quoting */ + if (*src == '\0') + strbuf_addch(sb, '\\'); + strbuf_addch(sb, c); + break; + + default: + strbuf_addch(sb, c); + break; + } + } +} @@ -70,5 +70,6 @@ extern char *quote_path_relative(const char *in, const char *prefix, extern void perl_quote_buf(struct strbuf *sb, const char *src); extern void python_quote_buf(struct strbuf *sb, const char *src); extern void tcl_quote_buf(struct strbuf *sb, const char *src); +extern void basic_regex_quote_buf(struct strbuf *sb, const char *src); #endif diff --git a/read-cache.c b/read-cache.c index db27766055..491e52d120 100644 --- a/read-cache.c +++ b/read-cache.c @@ -19,9 +19,6 @@ #include "split-index.h" #include "utf8.h" -static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, - unsigned int options); - /* Mask for the name length in ce_flags in the on-disk index */ #define CE_NAMEMASK (0x0fff) @@ -1257,7 +1254,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, return has_errors; } -static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, +struct cache_entry *refresh_cache_entry(struct cache_entry *ce, unsigned int options) { return refresh_cache_ent(&the_index, ce, options, NULL, NULL); @@ -120,25 +120,33 @@ int check_refname_format(const char *refname, int flags) int refname_is_safe(const char *refname) { - if (starts_with(refname, "refs/")) { + const char *rest; + + if (skip_prefix(refname, "refs/", &rest)) { char *buf; int result; + size_t restlen = strlen(rest); + + /* rest must not be empty, or start or end with "/" */ + if (!restlen || *rest == '/' || rest[restlen - 1] == '/') + return 0; - buf = xmallocz(strlen(refname)); /* * Does the refname try to escape refs/? * For example: refs/foo/../bar is safe but refs/foo/../../bar * is not. */ - result = !normalize_path_copy(buf, refname + strlen("refs/")); + buf = xmallocz(restlen); + result = !normalize_path_copy(buf, rest) && !strcmp(buf, rest); free(buf); return result; } - while (*refname) { + + do { if (!isupper(*refname) && *refname != '_') return 0; refname++; - } + } while (*refname); return 1; } @@ -496,7 +504,7 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, filename = git_path("%s", pseudoref); fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); if (fd < 0) { - strbuf_addf(err, "Could not open '%s' for writing: %s", + strbuf_addf(err, "could not open '%s' for writing: %s", filename, strerror(errno)); return -1; } @@ -507,14 +515,14 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, if (read_ref(pseudoref, actual_old_sha1)) die("could not read ref '%s'", pseudoref); if (hashcmp(actual_old_sha1, old_sha1)) { - strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref); + strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref); rollback_lock_file(&lock); goto done; } } if (write_in_full(fd, buf.buf, buf.len) != buf.len) { - strbuf_addf(err, "Could not write to '%s'", filename); + strbuf_addf(err, "could not write to '%s'", filename); rollback_lock_file(&lock); goto done; } @@ -758,13 +766,33 @@ void ref_transaction_free(struct ref_transaction *transaction) free(transaction); } -static struct ref_update *add_update(struct ref_transaction *transaction, - const char *refname) +struct ref_update *ref_transaction_add_update( + struct ref_transaction *transaction, + const char *refname, unsigned int flags, + const unsigned char *new_sha1, + const unsigned char *old_sha1, + const char *msg) { struct ref_update *update; + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: update called for transaction that is not open"); + + if ((flags & REF_ISPRUNING) && !(flags & REF_NODEREF)) + die("BUG: REF_ISPRUNING set without REF_NODEREF"); + FLEX_ALLOC_STR(update, refname, refname); ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc); transaction->updates[transaction->nr++] = update; + + update->flags = flags; + + if (flags & REF_HAVE_NEW) + hashcpy(update->new_sha1, new_sha1); + if (flags & REF_HAVE_OLD) + hashcpy(update->old_sha1, old_sha1); + if (msg) + update->msg = xstrdup(msg); return update; } @@ -775,32 +803,20 @@ int ref_transaction_update(struct ref_transaction *transaction, unsigned int flags, const char *msg, struct strbuf *err) { - struct ref_update *update; - assert(err); - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: update called for transaction that is not open"); - - if (new_sha1 && !is_null_sha1(new_sha1) && - check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - strbuf_addf(err, "refusing to update ref with bad name %s", + if ((new_sha1 && !is_null_sha1(new_sha1)) ? + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) : + !refname_is_safe(refname)) { + strbuf_addf(err, "refusing to update ref with bad name '%s'", refname); return -1; } - update = add_update(transaction, refname); - if (new_sha1) { - hashcpy(update->new_sha1, new_sha1); - flags |= REF_HAVE_NEW; - } - if (old_sha1) { - hashcpy(update->old_sha1, old_sha1); - flags |= REF_HAVE_OLD; - } - update->flags = flags; - if (msg) - update->msg = xstrdup(msg); + flags |= (new_sha1 ? REF_HAVE_NEW : 0) | (old_sha1 ? REF_HAVE_OLD : 0); + + ref_transaction_add_update(transaction, refname, flags, + new_sha1, old_sha1, msg); return 0; } @@ -1104,6 +1120,26 @@ int head_ref(each_ref_fn fn, void *cb_data) return head_ref_submodule(NULL, fn, cb_data); } +/* + * Call fn for each reference in the specified submodule for which the + * refname begins with prefix. If trim is non-zero, then trim that + * many characters off the beginning of each refname before passing + * the refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to + * include broken references in the iteration. If fn ever returns a + * non-zero value, stop the iteration and return that value; + * otherwise, return 0. + */ +static int do_for_each_ref(const char *submodule, const char *prefix, + each_ref_fn fn, int trim, int flags, void *cb_data) +{ + struct ref_iterator *iter; + + iter = files_ref_iterator_begin(submodule, prefix, flags); + iter = prefix_ref_iterator_begin(iter, prefix, trim); + + return do_for_each_ref_iterator(iter, fn, cb_data); +} + int for_each_ref(each_ref_fn fn, void *cb_data) { return do_for_each_ref(NULL, "", fn, 0, 0, cb_data); @@ -52,19 +52,19 @@ #define RESOLVE_REF_NO_RECURSE 0x02 #define RESOLVE_REF_ALLOW_BAD_NAME 0x04 -extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags, - unsigned char *sha1, int *flags); +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); -extern char *resolve_refdup(const char *refname, int resolve_flags, - unsigned char *sha1, int *flags); +char *resolve_refdup(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); -extern int read_ref_full(const char *refname, int resolve_flags, - unsigned char *sha1, int *flags); -extern int read_ref(const char *refname, unsigned char *sha1); +int read_ref_full(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); +int read_ref(const char *refname, unsigned char *sha1); -extern int ref_exists(const char *refname); +int ref_exists(const char *refname); -extern int is_branch(const char *refname); +int is_branch(const char *refname); /* * If refname is a non-symbolic reference that refers to a tag object, @@ -74,24 +74,25 @@ extern int is_branch(const char *refname); * Symbolic references are considered unpeelable, even if they * ultimately resolve to a peelable tag. */ -extern int peel_ref(const char *refname, unsigned char *sha1); +int peel_ref(const char *refname, unsigned char *sha1); /** * Resolve refname in the nested "gitlink" repository that is located * at path. If the resolution is successful, return 0 and set sha1 to * the name of the object; otherwise, return a non-zero value. */ -extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); +int resolve_gitlink_ref(const char *path, const char *refname, + unsigned char *sha1); /* * Return true iff abbrev_name is a possible abbreviation for * full_name according to the rules defined by ref_rev_parse_rules in * refs.c. */ -extern int refname_match(const char *abbrev_name, const char *full_name); +int refname_match(const char *abbrev_name, const char *full_name); -extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); -extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); +int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); +int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); /* * A ref_transaction represents a collection of ref updates @@ -140,7 +141,9 @@ extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); struct ref_transaction; /* - * Bit values set in the flags argument passed to each_ref_fn(): + * Bit values set in the flags argument passed to each_ref_fn() and + * stored in ref_iterator::flags. Other bits are for internal use + * only: */ /* Reference is a symbolic reference. */ @@ -182,38 +185,45 @@ typedef int each_ref_fn(const char *refname, * modifies the reference also returns a nonzero value to immediately * stop the iteration. */ -extern int head_ref(each_ref_fn fn, void *cb_data); -extern int for_each_ref(each_ref_fn fn, void *cb_data); -extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); -extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken); -extern int for_each_tag_ref(each_ref_fn fn, void *cb_data); -extern int for_each_branch_ref(each_ref_fn fn, void *cb_data); -extern int for_each_remote_ref(each_ref_fn fn, void *cb_data); -extern int for_each_replace_ref(each_ref_fn fn, void *cb_data); -extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data); -extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data); - -extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); -extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); -extern int for_each_ref_in_submodule(const char *submodule, const char *prefix, +int head_ref(each_ref_fn fn, void *cb_data); +int for_each_ref(each_ref_fn fn, void *cb_data); +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, + unsigned int broken); +int for_each_tag_ref(each_ref_fn fn, void *cb_data); +int for_each_branch_ref(each_ref_fn fn, void *cb_data); +int for_each_remote_ref(each_ref_fn fn, void *cb_data); +int for_each_replace_ref(each_ref_fn fn, void *cb_data); +int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data); +int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, + const char *prefix, void *cb_data); + +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +int for_each_ref_submodule(const char *submodule, + each_ref_fn fn, void *cb_data); +int for_each_ref_in_submodule(const char *submodule, const char *prefix, each_ref_fn fn, void *cb_data); -extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); -extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); -extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +int for_each_tag_ref_submodule(const char *submodule, + each_ref_fn fn, void *cb_data); +int for_each_branch_ref_submodule(const char *submodule, + each_ref_fn fn, void *cb_data); +int for_each_remote_ref_submodule(const char *submodule, + each_ref_fn fn, void *cb_data); -extern int head_ref_namespaced(each_ref_fn fn, void *cb_data); -extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data); +int head_ref_namespaced(each_ref_fn fn, void *cb_data); +int for_each_namespaced_ref(each_ref_fn fn, void *cb_data); /* can be used to learn about broken ref and symref */ -extern int for_each_rawref(each_ref_fn fn, void *cb_data); +int for_each_rawref(each_ref_fn fn, void *cb_data); static inline const char *has_glob_specials(const char *pattern) { return strpbrk(pattern, "?*["); } -extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname); -extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames); +void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname); +void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, + const struct string_list *refnames); /* * Flags for controlling behaviour of pack_refs() @@ -245,13 +255,13 @@ int pack_refs(unsigned int flags); int safe_create_reflog(const char *refname, int force_create, struct strbuf *err); /** Reads log for the value of ref during at_time. **/ -extern int read_ref_at(const char *refname, unsigned int flags, - unsigned long at_time, int cnt, - unsigned char *sha1, char **msg, - unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt); +int read_ref_at(const char *refname, unsigned int flags, + unsigned long at_time, int cnt, + unsigned char *sha1, char **msg, + unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt); /** Check if a particular reflog exists */ -extern int reflog_exists(const char *refname); +int reflog_exists(const char *refname); /* * Delete the specified reference. If old_sha1 is non-NULL, then @@ -260,21 +270,26 @@ extern int reflog_exists(const char *refname); * exists, regardless of its old value. It is an error for old_sha1 to * be NULL_SHA1. flags is passed through to ref_transaction_delete(). */ -extern int delete_ref(const char *refname, const unsigned char *old_sha1, - unsigned int flags); +int delete_ref(const char *refname, const unsigned char *old_sha1, + unsigned int flags); /* * Delete the specified references. If there are any problems, emit * errors but attempt to keep going (i.e., the deletes are not done in - * an all-or-nothing transaction). + * an all-or-nothing transaction). flags is passed through to + * ref_transaction_delete(). */ -extern int delete_refs(struct string_list *refnames); +int delete_refs(struct string_list *refnames, unsigned int flags); /** Delete a reflog */ -extern int delete_reflog(const char *refname); +int delete_reflog(const char *refname); /* iterate over reflog entries */ -typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); +typedef int each_reflog_ent_fn( + unsigned char *old_sha1, unsigned char *new_sha1, + const char *committer, unsigned long timestamp, + int tz, const char *msg, void *cb_data); + int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data); int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data); @@ -282,7 +297,7 @@ int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void * Calls the specified function for each reflog file until it returns nonzero, * and returns the value */ -extern int for_each_reflog(each_ref_fn, void *); +int for_each_reflog(each_ref_fn fn, void *cb_data); #define REFNAME_ALLOW_ONELEVEL 1 #define REFNAME_REFSPEC_PATTERN 2 @@ -295,16 +310,16 @@ extern int for_each_reflog(each_ref_fn, void *); * allow a single "*" wildcard character in the refspec. No leading or * repeated slashes are accepted. */ -extern int check_refname_format(const char *refname, int flags); +int check_refname_format(const char *refname, int flags); -extern const char *prettify_refname(const char *refname); +const char *prettify_refname(const char *refname); -extern char *shorten_unambiguous_ref(const char *refname, int strict); +char *shorten_unambiguous_ref(const char *refname, int strict); /** rename ref, return 0 on success **/ -extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); +int rename_ref(const char *oldref, const char *newref, const char *logmsg); -extern int create_symref(const char *refname, const char *target, const char *logmsg); +int create_symref(const char *refname, const char *target, const char *logmsg); /* * Update HEAD of the specified gitdir. @@ -313,7 +328,7 @@ extern int create_symref(const char *refname, const char *target, const char *lo * $GIT_DIR points to. * Return 0 if successful, non-zero otherwise. * */ -extern int set_worktree_head_symref(const char *gitdir, const char *target); +int set_worktree_head_symref(const char *gitdir, const char *target); enum action_on_err { UPDATE_REFS_MSG_ON_ERR, @@ -463,7 +478,7 @@ int update_ref(const char *msg, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, unsigned int flags, enum action_on_err onerr); -extern int parse_hide_refs_config(const char *var, const char *value, const char *); +int parse_hide_refs_config(const char *var, const char *value, const char *); /* * Check whether a ref is hidden. If no namespace is set, both the first and @@ -473,7 +488,7 @@ extern int parse_hide_refs_config(const char *var, const char *value, const char * the ref is outside that namespace, the first parameter is NULL. The second * parameter always points to the full ref name. */ -extern int ref_is_hidden(const char *, const char *); +int ref_is_hidden(const char *, const char *); enum ref_type { REF_TYPE_PER_WORKTREE, @@ -522,11 +537,11 @@ typedef void reflog_expiry_cleanup_fn(void *cb_data); * enum expire_reflog_flags. The three function pointers are described * above. On success, return zero. */ -extern int reflog_expire(const char *refname, const unsigned char *sha1, - unsigned int flags, - reflog_expiry_prepare_fn prepare_fn, - reflog_expiry_should_prune_fn should_prune_fn, - reflog_expiry_cleanup_fn cleanup_fn, - void *policy_cb_data); +int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data); #endif /* REFS_H */ diff --git a/refs/files-backend.c b/refs/files-backend.c index 1f38076411..12290d2496 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1,13 +1,14 @@ #include "../cache.h" #include "../refs.h" #include "refs-internal.h" +#include "../iterator.h" +#include "../dir-iterator.h" #include "../lockfile.h" #include "../object.h" #include "../dir.h" struct ref_lock { char *ref_name; - char *orig_ref_name; struct lock_file *lk; struct object_id old_oid; }; @@ -514,68 +515,36 @@ static void sort_ref_dir(struct ref_dir *dir) } /* - * Return true iff the reference described by entry can be resolved to - * an object in the database. Emit a warning if the referred-to - * object does not exist. + * Return true if refname, which has the specified oid and flags, can + * be resolved to an object in the database. If the referred-to object + * does not exist, emit a warning and return false. */ -static int ref_resolves_to_object(struct ref_entry *entry) +static int ref_resolves_to_object(const char *refname, + const struct object_id *oid, + unsigned int flags) { - if (entry->flag & REF_ISBROKEN) + if (flags & REF_ISBROKEN) return 0; - if (!has_sha1_file(entry->u.value.oid.hash)) { - error("%s does not point to a valid object!", entry->name); + if (!has_sha1_file(oid->hash)) { + error("%s does not point to a valid object!", refname); return 0; } return 1; } /* - * current_ref is a performance hack: when iterating over references - * using the for_each_ref*() functions, current_ref is set to the - * current reference's entry before calling the callback function. If - * the callback function calls peel_ref(), then peel_ref() first - * checks whether the reference to be peeled is the current reference - * (it usually is) and if so, returns that reference's peeled version - * if it is available. This avoids a refname lookup in a common case. - */ -static struct ref_entry *current_ref; - -typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data); - -struct ref_entry_cb { - const char *base; - int trim; - int flags; - each_ref_fn *fn; - void *cb_data; -}; - -/* - * Handle one reference in a do_for_each_ref*()-style iteration, - * calling an each_ref_fn for each entry. + * Return true if the reference described by entry can be resolved to + * an object in the database; otherwise, emit a warning and return + * false. */ -static int do_one_ref(struct ref_entry *entry, void *cb_data) +static int entry_resolves_to_object(struct ref_entry *entry) { - struct ref_entry_cb *data = cb_data; - struct ref_entry *old_current_ref; - int retval; - - if (!starts_with(entry->name, data->base)) - return 0; - - if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) && - !ref_resolves_to_object(entry)) - return 0; - - /* Store the old value, in case this is a recursive call: */ - old_current_ref = current_ref; - current_ref = entry; - retval = data->fn(entry->name + data->trim, &entry->u.value.oid, - entry->flag, data->cb_data); - current_ref = old_current_ref; - return retval; + return ref_resolves_to_object(entry->name, + &entry->u.value.oid, entry->flag); } +typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data); + /* * Call fn for each reference in dir that has index in the range * offset <= index < dir->nr. Recurse into subdirectories that are in @@ -605,78 +574,6 @@ static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset, } /* - * Call fn for each reference in the union of dir1 and dir2, in order - * by refname. Recurse into subdirectories. If a value entry appears - * in both dir1 and dir2, then only process the version that is in - * dir2. The input dirs must already be sorted, but subdirs will be - * sorted as needed. fn is called for all references, including - * broken ones. - */ -static int do_for_each_entry_in_dirs(struct ref_dir *dir1, - struct ref_dir *dir2, - each_ref_entry_fn fn, void *cb_data) -{ - int retval; - int i1 = 0, i2 = 0; - - assert(dir1->sorted == dir1->nr); - assert(dir2->sorted == dir2->nr); - while (1) { - struct ref_entry *e1, *e2; - int cmp; - if (i1 == dir1->nr) { - return do_for_each_entry_in_dir(dir2, i2, fn, cb_data); - } - if (i2 == dir2->nr) { - return do_for_each_entry_in_dir(dir1, i1, fn, cb_data); - } - e1 = dir1->entries[i1]; - e2 = dir2->entries[i2]; - cmp = strcmp(e1->name, e2->name); - if (cmp == 0) { - if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) { - /* Both are directories; descend them in parallel. */ - struct ref_dir *subdir1 = get_ref_dir(e1); - struct ref_dir *subdir2 = get_ref_dir(e2); - sort_ref_dir(subdir1); - sort_ref_dir(subdir2); - retval = do_for_each_entry_in_dirs( - subdir1, subdir2, fn, cb_data); - i1++; - i2++; - } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) { - /* Both are references; ignore the one from dir1. */ - retval = fn(e2, cb_data); - i1++; - i2++; - } else { - die("conflict between reference and directory: %s", - e1->name); - } - } else { - struct ref_entry *e; - if (cmp < 0) { - e = e1; - i1++; - } else { - e = e2; - i2++; - } - if (e->flag & REF_DIR) { - struct ref_dir *subdir = get_ref_dir(e); - sort_ref_dir(subdir); - retval = do_for_each_entry_in_dir( - subdir, 0, fn, cb_data); - } else { - retval = fn(e, cb_data); - } - } - if (retval) - return retval; - } -} - -/* * Load all of the refs from the dir into our in-memory cache. The hard work * of loading loose refs is done by get_ref_dir(), so we just need to recurse * through all of the sub-directories. We do not even need to care about @@ -692,6 +589,153 @@ static void prime_ref_dir(struct ref_dir *dir) } } +/* + * A level in the reference hierarchy that is currently being iterated + * through. + */ +struct cache_ref_iterator_level { + /* + * The ref_dir being iterated over at this level. The ref_dir + * is sorted before being stored here. + */ + struct ref_dir *dir; + + /* + * The index of the current entry within dir (which might + * itself be a directory). If index == -1, then the iteration + * hasn't yet begun. If index == dir->nr, then the iteration + * through this level is over. + */ + int index; +}; + +/* + * Represent an iteration through a ref_dir in the memory cache. The + * iteration recurses through subdirectories. + */ +struct cache_ref_iterator { + struct ref_iterator base; + + /* + * The number of levels currently on the stack. This is always + * at least 1, because when it becomes zero the iteration is + * ended and this struct is freed. + */ + size_t levels_nr; + + /* The number of levels that have been allocated on the stack */ + size_t levels_alloc; + + /* + * A stack of levels. levels[0] is the uppermost level that is + * being iterated over in this iteration. (This is not + * necessary the top level in the references hierarchy. If we + * are iterating through a subtree, then levels[0] will hold + * the ref_dir for that subtree, and subsequent levels will go + * on from there.) + */ + struct cache_ref_iterator_level *levels; +}; + +static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct cache_ref_iterator *iter = + (struct cache_ref_iterator *)ref_iterator; + + while (1) { + struct cache_ref_iterator_level *level = + &iter->levels[iter->levels_nr - 1]; + struct ref_dir *dir = level->dir; + struct ref_entry *entry; + + if (level->index == -1) + sort_ref_dir(dir); + + if (++level->index == level->dir->nr) { + /* This level is exhausted; pop up a level */ + if (--iter->levels_nr == 0) + return ref_iterator_abort(ref_iterator); + + continue; + } + + entry = dir->entries[level->index]; + + if (entry->flag & REF_DIR) { + /* push down a level */ + ALLOC_GROW(iter->levels, iter->levels_nr + 1, + iter->levels_alloc); + + level = &iter->levels[iter->levels_nr++]; + level->dir = get_ref_dir(entry); + level->index = -1; + } else { + iter->base.refname = entry->name; + iter->base.oid = &entry->u.value.oid; + iter->base.flags = entry->flag; + return ITER_OK; + } + } +} + +static enum peel_status peel_entry(struct ref_entry *entry, int repeel); + +static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct cache_ref_iterator *iter = + (struct cache_ref_iterator *)ref_iterator; + struct cache_ref_iterator_level *level; + struct ref_entry *entry; + + level = &iter->levels[iter->levels_nr - 1]; + + if (level->index == -1) + die("BUG: peel called before advance for cache iterator"); + + entry = level->dir->entries[level->index]; + + if (peel_entry(entry, 0)) + return -1; + hashcpy(peeled->hash, entry->u.value.peeled.hash); + return 0; +} + +static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct cache_ref_iterator *iter = + (struct cache_ref_iterator *)ref_iterator; + + free(iter->levels); + base_ref_iterator_free(ref_iterator); + return ITER_DONE; +} + +static struct ref_iterator_vtable cache_ref_iterator_vtable = { + cache_ref_iterator_advance, + cache_ref_iterator_peel, + cache_ref_iterator_abort +}; + +static struct ref_iterator *cache_ref_iterator_begin(struct ref_dir *dir) +{ + struct cache_ref_iterator *iter; + struct ref_iterator *ref_iterator; + struct cache_ref_iterator_level *level; + + iter = xcalloc(1, sizeof(*iter)); + ref_iterator = &iter->base; + base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable); + ALLOC_GROW(iter->levels, 10, iter->levels_alloc); + + iter->levels_nr = 1; + level = &iter->levels[0]; + level->index = -1; + level->dir = dir; + + return ref_iterator; +} + struct nonmatching_ref_data { const struct string_list *skip; const char *conflicting_refname; @@ -955,15 +999,26 @@ static struct ref_cache *lookup_ref_cache(const char *submodule) /* * Return a pointer to a ref_cache for the specified submodule. For - * the main repository, use submodule==NULL. The returned structure - * will be allocated and initialized but not necessarily populated; it - * should not be freed. + * the main repository, use submodule==NULL; such a call cannot fail. + * For a submodule, the submodule must exist and be a nonbare + * repository, otherwise return NULL. + * + * The returned structure will be allocated and initialized but not + * necessarily populated; it should not be freed. */ static struct ref_cache *get_ref_cache(const char *submodule) { struct ref_cache *refs = lookup_ref_cache(submodule); - if (!refs) - refs = create_ref_cache(submodule); + + if (!refs) { + struct strbuf submodule_sb = STRBUF_INIT; + + strbuf_addstr(&submodule_sb, submodule); + if (is_nonbare_repository_dir(&submodule_sb)) + refs = create_ref_cache(submodule); + strbuf_release(&submodule_sb); + } + return refs; } @@ -1342,13 +1397,10 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh return -1; strbuf_add(&submodule, path, len); - refs = lookup_ref_cache(submodule.buf); + refs = get_ref_cache(submodule.buf); if (!refs) { - if (!is_nonbare_repository_dir(&submodule)) { - strbuf_release(&submodule); - return -1; - } - refs = create_ref_cache(submodule.buf); + strbuf_release(&submodule); + return -1; } strbuf_release(&submodule); @@ -1388,38 +1440,8 @@ static int resolve_missing_loose_ref(const char *refname, return -1; } -/* - * Read a raw ref from the filesystem or packed refs file. - * - * If the ref is a sha1, fill in sha1 and return 0. - * - * If the ref is symbolic, fill in *symref with the referrent - * (e.g. "refs/heads/master") and return 0. The caller is responsible - * for validating the referrent. Set REF_ISSYMREF in flags. - * - * If the ref doesn't exist, set errno to ENOENT and return -1. - * - * If the ref exists but is neither a symbolic ref nor a sha1, it is - * broken. Set REF_ISBROKEN in flags, set errno to EINVAL, and return - * -1. - * - * If there is another error reading the ref, set errno appropriately and - * return -1. - * - * Backend-specific flags might be set in flags as well, regardless of - * outcome. - * - * sb_path is workspace: the caller should allocate and free it. - * - * It is OK for refname to point into symref. In this case: - * - if the function succeeds with REF_ISSYMREF, symref will be - * overwritten and the memory pointed to by refname might be changed - * or even freed. - * - in all other cases, symref will be untouched, and therefore - * refname will still be valid and unchanged. - */ int read_raw_ref(const char *refname, unsigned char *sha1, - struct strbuf *symref, unsigned int *flags) + struct strbuf *referent, unsigned int *type) { struct strbuf sb_contents = STRBUF_INIT; struct strbuf sb_path = STRBUF_INIT; @@ -1430,6 +1452,7 @@ int read_raw_ref(const char *refname, unsigned char *sha1, int ret = -1; int save_errno; + *type = 0; strbuf_reset(&sb_path); strbuf_git_path(&sb_path, "%s", refname); path = sb_path.buf; @@ -1448,7 +1471,7 @@ stat_ref: if (lstat(path, &st) < 0) { if (errno != ENOENT) goto out; - if (resolve_missing_loose_ref(refname, sha1, flags)) { + if (resolve_missing_loose_ref(refname, sha1, type)) { errno = ENOENT; goto out; } @@ -1468,8 +1491,8 @@ stat_ref: } if (starts_with(sb_contents.buf, "refs/") && !check_refname_format(sb_contents.buf, 0)) { - strbuf_swap(&sb_contents, symref); - *flags |= REF_ISSYMREF; + strbuf_swap(&sb_contents, referent); + *type |= REF_ISSYMREF; ret = 0; goto out; } @@ -1477,7 +1500,16 @@ stat_ref: /* Is it a directory? */ if (S_ISDIR(st.st_mode)) { - errno = EISDIR; + /* + * Even though there is a directory where the loose + * ref is supposed to be, there could still be a + * packed ref: + */ + if (resolve_missing_loose_ref(refname, sha1, type)) { + errno = EISDIR; + goto out; + } + ret = 0; goto out; } @@ -1508,9 +1540,9 @@ stat_ref: while (isspace(*buf)) buf++; - strbuf_reset(symref); - strbuf_addstr(symref, buf); - *flags |= REF_ISSYMREF; + strbuf_reset(referent); + strbuf_addstr(referent, buf); + *type |= REF_ISSYMREF; ret = 0; goto out; } @@ -1521,7 +1553,7 @@ stat_ref: */ if (get_sha1_hex(buf, sha1) || (buf[40] != '\0' && !isspace(buf[40]))) { - *flags |= REF_ISBROKEN; + *type |= REF_ISBROKEN; errno = EINVAL; goto out; } @@ -1536,6 +1568,241 @@ out: return ret; } +static void unlock_ref(struct ref_lock *lock) +{ + /* Do not free lock->lk -- atexit() still looks at them */ + if (lock->lk) + rollback_lock_file(lock->lk); + free(lock->ref_name); + free(lock); +} + +/* + * Lock refname, without following symrefs, and set *lock_p to point + * at a newly-allocated lock object. Fill in lock->old_oid, referent, + * and type similarly to read_raw_ref(). + * + * The caller must verify that refname is a "safe" reference name (in + * the sense of refname_is_safe()) before calling this function. + * + * If the reference doesn't already exist, verify that refname doesn't + * have a D/F conflict with any existing references. extras and skip + * are passed to verify_refname_available_dir() for this check. + * + * If mustexist is not set and the reference is not found or is + * broken, lock the reference anyway but clear sha1. + * + * Return 0 on success. On failure, write an error message to err and + * return TRANSACTION_NAME_CONFLICT or TRANSACTION_GENERIC_ERROR. + * + * Implementation note: This function is basically + * + * lock reference + * read_raw_ref() + * + * but it includes a lot more code to + * - Deal with possible races with other processes + * - Avoid calling verify_refname_available_dir() when it can be + * avoided, namely if we were successfully able to read the ref + * - Generate informative error messages in the case of failure + */ +static int lock_raw_ref(const char *refname, int mustexist, + const struct string_list *extras, + const struct string_list *skip, + struct ref_lock **lock_p, + struct strbuf *referent, + unsigned int *type, + struct strbuf *err) +{ + struct ref_lock *lock; + struct strbuf ref_file = STRBUF_INIT; + int attempts_remaining = 3; + int ret = TRANSACTION_GENERIC_ERROR; + + assert(err); + *type = 0; + + /* First lock the file so it can't change out from under us. */ + + *lock_p = lock = xcalloc(1, sizeof(*lock)); + + lock->ref_name = xstrdup(refname); + strbuf_git_path(&ref_file, "%s", refname); + +retry: + switch (safe_create_leading_directories(ref_file.buf)) { + case SCLD_OK: + break; /* success */ + case SCLD_EXISTS: + /* + * Suppose refname is "refs/foo/bar". We just failed + * to create the containing directory, "refs/foo", + * because there was a non-directory in the way. This + * indicates a D/F conflict, probably because of + * another reference such as "refs/foo". There is no + * reason to expect this error to be transitory. + */ + if (verify_refname_available(refname, extras, skip, err)) { + if (mustexist) { + /* + * To the user the relevant error is + * that the "mustexist" reference is + * missing: + */ + strbuf_reset(err); + strbuf_addf(err, "unable to resolve reference '%s'", + refname); + } else { + /* + * The error message set by + * verify_refname_available_dir() is OK. + */ + ret = TRANSACTION_NAME_CONFLICT; + } + } else { + /* + * The file that is in the way isn't a loose + * reference. Report it as a low-level + * failure. + */ + strbuf_addf(err, "unable to create lock file %s.lock; " + "non-directory in the way", + ref_file.buf); + } + goto error_return; + case SCLD_VANISHED: + /* Maybe another process was tidying up. Try again. */ + if (--attempts_remaining > 0) + goto retry; + /* fall through */ + default: + strbuf_addf(err, "unable to create directory for %s", + ref_file.buf); + goto error_return; + } + + if (!lock->lk) + lock->lk = xcalloc(1, sizeof(struct lock_file)); + + if (hold_lock_file_for_update(lock->lk, ref_file.buf, LOCK_NO_DEREF) < 0) { + if (errno == ENOENT && --attempts_remaining > 0) { + /* + * Maybe somebody just deleted one of the + * directories leading to ref_file. Try + * again: + */ + goto retry; + } else { + unable_to_lock_message(ref_file.buf, errno, err); + goto error_return; + } + } + + /* + * Now we hold the lock and can read the reference without + * fear that its value will change. + */ + + if (read_raw_ref(refname, lock->old_oid.hash, referent, type)) { + if (errno == ENOENT) { + if (mustexist) { + /* Garden variety missing reference. */ + strbuf_addf(err, "unable to resolve reference '%s'", + refname); + goto error_return; + } else { + /* + * Reference is missing, but that's OK. We + * know that there is not a conflict with + * another loose reference because + * (supposing that we are trying to lock + * reference "refs/foo/bar"): + * + * - We were successfully able to create + * the lockfile refs/foo/bar.lock, so we + * know there cannot be a loose reference + * named "refs/foo". + * + * - We got ENOENT and not EISDIR, so we + * know that there cannot be a loose + * reference named "refs/foo/bar/baz". + */ + } + } else if (errno == EISDIR) { + /* + * There is a directory in the way. It might have + * contained references that have been deleted. If + * we don't require that the reference already + * exists, try to remove the directory so that it + * doesn't cause trouble when we want to rename the + * lockfile into place later. + */ + if (mustexist) { + /* Garden variety missing reference. */ + strbuf_addf(err, "unable to resolve reference '%s'", + refname); + goto error_return; + } else if (remove_dir_recursively(&ref_file, + REMOVE_DIR_EMPTY_ONLY)) { + if (verify_refname_available_dir( + refname, extras, skip, + get_loose_refs(&ref_cache), + err)) { + /* + * The error message set by + * verify_refname_available() is OK. + */ + ret = TRANSACTION_NAME_CONFLICT; + goto error_return; + } else { + /* + * We can't delete the directory, + * but we also don't know of any + * references that it should + * contain. + */ + strbuf_addf(err, "there is a non-empty directory '%s' " + "blocking reference '%s'", + ref_file.buf, refname); + goto error_return; + } + } + } else if (errno == EINVAL && (*type & REF_ISBROKEN)) { + strbuf_addf(err, "unable to resolve reference '%s': " + "reference broken", refname); + goto error_return; + } else { + strbuf_addf(err, "unable to resolve reference '%s': %s", + refname, strerror(errno)); + goto error_return; + } + + /* + * If the ref did not exist and we are creating it, + * make sure there is no existing packed ref whose + * name begins with our refname, nor a packed ref + * whose name is a proper prefix of our refname. + */ + if (verify_refname_available_dir( + refname, extras, skip, + get_packed_refs(&ref_cache), + err)) { + goto error_return; + } + } + + ret = 0; + goto out; + +error_return: + unlock_ref(lock); + *lock_p = NULL; + +out: + strbuf_release(&ref_file); + return ret; +} + /* * Peel the entry (if possible) and return its new peel_status. If * repeel is true, re-peel the entry even if there is an old peeled @@ -1576,11 +1843,12 @@ int peel_ref(const char *refname, unsigned char *sha1) int flag; unsigned char base[20]; - if (current_ref && (current_ref->name == refname - || !strcmp(current_ref->name, refname))) { - if (peel_entry(current_ref, 0)) + if (current_ref_iter && current_ref_iter->refname == refname) { + struct object_id peeled; + + if (ref_iterator_peel(current_ref_iter, &peeled)) return -1; - hashcpy(sha1, current_ref->u.value.peeled.hash); + hashcpy(sha1, peeled.hash); return 0; } @@ -1608,100 +1876,137 @@ int peel_ref(const char *refname, unsigned char *sha1) return peel_object(base, sha1); } -/* - * Call fn for each reference in the specified ref_cache, omitting - * references not in the containing_dir of base. fn is called for all - * references, including broken ones. If fn ever returns a non-zero - * value, stop the iteration and return that value; otherwise, return - * 0. - */ -static int do_for_each_entry(struct ref_cache *refs, const char *base, - each_ref_entry_fn fn, void *cb_data) -{ +struct files_ref_iterator { + struct ref_iterator base; + struct packed_ref_cache *packed_ref_cache; - struct ref_dir *loose_dir; - struct ref_dir *packed_dir; - int retval = 0; + struct ref_iterator *iter0; + unsigned int flags; +}; - /* - * We must make sure that all loose refs are read before accessing the - * packed-refs file; this avoids a race condition in which loose refs - * are migrated to the packed-refs file by a simultaneous process, but - * our in-memory view is from before the migration. get_packed_ref_cache() - * takes care of making sure our view is up to date with what is on - * disk. - */ - loose_dir = get_loose_refs(refs); - if (base && *base) { - loose_dir = find_containing_dir(loose_dir, base, 0); - } - if (loose_dir) - prime_ref_dir(loose_dir); +static int files_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct files_ref_iterator *iter = + (struct files_ref_iterator *)ref_iterator; + int ok; - packed_ref_cache = get_packed_ref_cache(refs); - acquire_packed_ref_cache(packed_ref_cache); - packed_dir = get_packed_ref_dir(packed_ref_cache); - if (base && *base) { - packed_dir = find_containing_dir(packed_dir, base, 0); - } - - if (packed_dir && loose_dir) { - sort_ref_dir(packed_dir); - sort_ref_dir(loose_dir); - retval = do_for_each_entry_in_dirs( - packed_dir, loose_dir, fn, cb_data); - } else if (packed_dir) { - sort_ref_dir(packed_dir); - retval = do_for_each_entry_in_dir( - packed_dir, 0, fn, cb_data); - } else if (loose_dir) { - sort_ref_dir(loose_dir); - retval = do_for_each_entry_in_dir( - loose_dir, 0, fn, cb_data); + while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { + if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && + !ref_resolves_to_object(iter->iter0->refname, + iter->iter0->oid, + iter->iter0->flags)) + continue; + + iter->base.refname = iter->iter0->refname; + iter->base.oid = iter->iter0->oid; + iter->base.flags = iter->iter0->flags; + return ITER_OK; } - release_packed_ref_cache(packed_ref_cache); - return retval; + iter->iter0 = NULL; + if (ref_iterator_abort(ref_iterator) != ITER_DONE) + ok = ITER_ERROR; + + return ok; } -/* - * Call fn for each reference in the specified ref_cache for which the - * refname begins with base. If trim is non-zero, then trim that many - * characters off the beginning of each refname before passing the - * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include - * broken references in the iteration. If fn ever returns a non-zero - * value, stop the iteration and return that value; otherwise, return - * 0. - */ -int do_for_each_ref(const char *submodule, const char *base, - each_ref_fn fn, int trim, int flags, void *cb_data) +static int files_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) { - struct ref_entry_cb data; - struct ref_cache *refs; + struct files_ref_iterator *iter = + (struct files_ref_iterator *)ref_iterator; + + return ref_iterator_peel(iter->iter0, peeled); +} + +static int files_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct files_ref_iterator *iter = + (struct files_ref_iterator *)ref_iterator; + int ok = ITER_DONE; + + if (iter->iter0) + ok = ref_iterator_abort(iter->iter0); - refs = get_ref_cache(submodule); - data.base = base; - data.trim = trim; - data.flags = flags; - data.fn = fn; - data.cb_data = cb_data; + release_packed_ref_cache(iter->packed_ref_cache); + base_ref_iterator_free(ref_iterator); + return ok; +} + +static struct ref_iterator_vtable files_ref_iterator_vtable = { + files_ref_iterator_advance, + files_ref_iterator_peel, + files_ref_iterator_abort +}; + +struct ref_iterator *files_ref_iterator_begin( + const char *submodule, + const char *prefix, unsigned int flags) +{ + struct ref_cache *refs = get_ref_cache(submodule); + struct ref_dir *loose_dir, *packed_dir; + struct ref_iterator *loose_iter, *packed_iter; + struct files_ref_iterator *iter; + struct ref_iterator *ref_iterator; + + if (!refs) + return empty_ref_iterator_begin(); if (ref_paranoia < 0) ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0); if (ref_paranoia) - data.flags |= DO_FOR_EACH_INCLUDE_BROKEN; + flags |= DO_FOR_EACH_INCLUDE_BROKEN; - return do_for_each_entry(refs, base, do_one_ref, &data); -} + iter = xcalloc(1, sizeof(*iter)); + ref_iterator = &iter->base; + base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable); -static void unlock_ref(struct ref_lock *lock) -{ - /* Do not free lock->lk -- atexit() still looks at them */ - if (lock->lk) - rollback_lock_file(lock->lk); - free(lock->ref_name); - free(lock->orig_ref_name); - free(lock); + /* + * We must make sure that all loose refs are read before + * accessing the packed-refs file; this avoids a race + * condition if loose refs are migrated to the packed-refs + * file by a simultaneous process, but our in-memory view is + * from before the migration. We ensure this as follows: + * First, we call prime_ref_dir(), which pre-reads the loose + * references for the subtree into the cache. (If they've + * already been read, that's OK; we only need to guarantee + * that they're read before the packed refs, not *how much* + * before.) After that, we call get_packed_ref_cache(), which + * internally checks whether the packed-ref cache is up to + * date with what is on disk, and re-reads it if not. + */ + + loose_dir = get_loose_refs(refs); + + if (prefix && *prefix) + loose_dir = find_containing_dir(loose_dir, prefix, 0); + + if (loose_dir) { + prime_ref_dir(loose_dir); + loose_iter = cache_ref_iterator_begin(loose_dir); + } else { + /* There's nothing to iterate over. */ + loose_iter = empty_ref_iterator_begin(); + } + + iter->packed_ref_cache = get_packed_ref_cache(refs); + acquire_packed_ref_cache(iter->packed_ref_cache); + packed_dir = get_packed_ref_dir(iter->packed_ref_cache); + + if (prefix && *prefix) + packed_dir = find_containing_dir(packed_dir, prefix, 0); + + if (packed_dir) { + packed_iter = cache_ref_iterator_begin(packed_dir); + } else { + /* There's nothing to iterate over. */ + packed_iter = empty_ref_iterator_begin(); + } + + iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter); + iter->flags = flags; + + return ref_iterator; } /* @@ -1721,18 +2026,18 @@ static int verify_lock(struct ref_lock *lock, lock->old_oid.hash, NULL)) { if (old_sha1) { int save_errno = errno; - strbuf_addf(err, "can't verify ref %s", lock->ref_name); + strbuf_addf(err, "can't verify ref '%s'", lock->ref_name); errno = save_errno; return -1; } else { - hashclr(lock->old_oid.hash); + oidclr(&lock->old_oid); return 0; } } if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) { - strbuf_addf(err, "ref %s is at %s but expected %s", + strbuf_addf(err, "ref '%s' is at %s but expected %s", lock->ref_name, - sha1_to_hex(lock->old_oid.hash), + oid_to_hex(&lock->old_oid), sha1_to_hex(old_sha1)); errno = EBUSY; return -1; @@ -1758,19 +2063,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, const struct string_list *extras, const struct string_list *skip, - unsigned int flags, int *type_p, + unsigned int flags, int *type, struct strbuf *err) { struct strbuf ref_file = STRBUF_INIT; - struct strbuf orig_ref_file = STRBUF_INIT; - const char *orig_refname = refname; struct ref_lock *lock; int last_errno = 0; - int type; - int lflags = 0; + int lflags = LOCK_NO_DEREF; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); - int resolve_flags = 0; + int resolve_flags = RESOLVE_REF_NO_RECURSE; int attempts_remaining = 3; + int resolved; assert(err); @@ -1780,48 +2083,39 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, resolve_flags |= RESOLVE_REF_READING; if (flags & REF_DELETING) resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; - if (flags & REF_NODEREF) { - resolve_flags |= RESOLVE_REF_NO_RECURSE; - lflags |= LOCK_NO_DEREF; - } - refname = resolve_ref_unsafe(refname, resolve_flags, - lock->old_oid.hash, &type); - if (!refname && errno == EISDIR) { + strbuf_git_path(&ref_file, "%s", refname); + resolved = !!resolve_ref_unsafe(refname, resolve_flags, + lock->old_oid.hash, type); + if (!resolved && errno == EISDIR) { /* * we are trying to lock foo but we used to * have foo/bar which now does not exist; * it is normal for the empty directory 'foo' * to remain. */ - strbuf_git_path(&orig_ref_file, "%s", orig_refname); - if (remove_empty_directories(&orig_ref_file)) { + if (remove_empty_directories(&ref_file)) { last_errno = errno; - if (!verify_refname_available_dir(orig_refname, extras, skip, + if (!verify_refname_available_dir(refname, extras, skip, get_loose_refs(&ref_cache), err)) strbuf_addf(err, "there are still refs under '%s'", - orig_refname); + refname); goto error_return; } - refname = resolve_ref_unsafe(orig_refname, resolve_flags, - lock->old_oid.hash, &type); + resolved = !!resolve_ref_unsafe(refname, resolve_flags, + lock->old_oid.hash, type); } - if (type_p) - *type_p = type; - if (!refname) { + if (!resolved) { last_errno = errno; if (last_errno != ENOTDIR || - !verify_refname_available_dir(orig_refname, extras, skip, + !verify_refname_available_dir(refname, extras, skip, get_loose_refs(&ref_cache), err)) - strbuf_addf(err, "unable to resolve reference %s: %s", - orig_refname, strerror(last_errno)); + strbuf_addf(err, "unable to resolve reference '%s': %s", + refname, strerror(last_errno)); goto error_return; } - if (flags & REF_NODEREF) - refname = orig_refname; - /* * If the ref did not exist and we are creating it, make sure * there is no existing packed ref whose name begins with our @@ -1838,8 +2132,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(refname); - lock->orig_ref_name = xstrdup(orig_refname); - strbuf_git_path(&ref_file, "%s", refname); retry: switch (safe_create_leading_directories_const(ref_file.buf)) { @@ -1851,7 +2143,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, /* fall through */ default: last_errno = errno; - strbuf_addf(err, "unable to create directory for %s", + strbuf_addf(err, "unable to create directory for '%s'", ref_file.buf); goto error_return; } @@ -1882,7 +2174,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, out: strbuf_release(&ref_file); - strbuf_release(&orig_ref_file); errno = last_errno; return lock; } @@ -2036,7 +2327,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data) return 0; /* Do not pack symbolic or broken refs: */ - if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry)) + if ((entry->flag & REF_ISSYMREF) || !entry_resolves_to_object(entry)) return 0; /* Add a packed ref cache entry equivalent to the loose entry. */ @@ -2110,7 +2401,7 @@ static void prune_ref(struct ref_to_prune *r) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, r->name, r->sha1, - REF_ISPRUNING, NULL, &err) || + REF_ISPRUNING | REF_NODEREF, NULL, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); @@ -2222,7 +2513,7 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) return 0; } -int delete_refs(struct string_list *refnames) +int delete_refs(struct string_list *refnames, unsigned int flags) { struct strbuf err = STRBUF_INIT; int i, result = 0; @@ -2251,7 +2542,7 @@ int delete_refs(struct string_list *refnames) for (i = 0; i < refnames->nr; i++) { const char *refname = refnames->items[i].string; - if (delete_ref(refname, NULL, 0)) + if (delete_ref(refname, NULL, flags)) result |= error(_("could not remove reference %s"), refname); } @@ -2322,8 +2613,8 @@ out: } int verify_refname_available(const char *newname, - struct string_list *extras, - struct string_list *skip, + const struct string_list *extras, + const struct string_list *skip, struct strbuf *err) { struct ref_dir *packed_refs = get_packed_refs(&ref_cache); @@ -2342,7 +2633,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1, struct strbuf *err); static int commit_ref_update(struct ref_lock *lock, const unsigned char *sha1, const char *logmsg, - int flags, struct strbuf *err); + struct strbuf *err); int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) { @@ -2351,20 +2642,18 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms struct ref_lock *lock; struct stat loginfo; int log = !lstat(git_path("logs/%s", oldrefname), &loginfo); - const char *symref = NULL; struct strbuf err = STRBUF_INIT; if (log && S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldrefname); - symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING, - orig_sha1, &flag); + if (!resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + orig_sha1, &flag)) + return error("refname %s not found", oldrefname); + if (flag & REF_ISSYMREF) return error("refname %s is a symbolic ref, renaming it is not supported", oldrefname); - if (!symref) - return error("refname %s not found", oldrefname); - if (!rename_ref_available(oldrefname, newrefname)) return 1; @@ -2377,8 +2666,16 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms goto rollback; } - if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && - delete_ref(newrefname, sha1, REF_NODEREF)) { + /* + * Since we are doing a shallow lookup, sha1 is not the + * correct value to pass to delete_ref as old_sha1. But that + * doesn't matter, because an old_sha1 check wouldn't add to + * the safety anyway; we want to delete the reference whatever + * its current value. + */ + if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + sha1, NULL) && + delete_ref(newrefname, NULL, REF_NODEREF)) { if (errno==EISDIR) { struct strbuf path = STRBUF_INIT; int result; @@ -2402,7 +2699,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms logmoved = log; - lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err); + lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, REF_NODEREF, + NULL, &err); if (!lock) { error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf); strbuf_release(&err); @@ -2411,7 +2709,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms hashcpy(lock->old_oid.hash, orig_sha1); if (write_ref_to_lockfile(lock, orig_sha1, &err) || - commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) { + commit_ref_update(lock, orig_sha1, logmsg, &err)) { error("unable to write current sha1 into %s: %s", newrefname, err.buf); strbuf_release(&err); goto rollback; @@ -2420,7 +2718,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms return 0; rollback: - lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err); + lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, REF_NODEREF, + NULL, &err); if (!lock) { error("unable to lock %s for rollback: %s", oldrefname, err.buf); strbuf_release(&err); @@ -2430,7 +2729,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms flag = log_all_ref_updates; log_all_ref_updates = 0; if (write_ref_to_lockfile(lock, orig_sha1, &err) || - commit_ref_update(lock, orig_sha1, NULL, 0, &err)) { + commit_ref_update(lock, orig_sha1, NULL, &err)) { error("unable to write current sha1 into %s: %s", oldrefname, err.buf); strbuf_release(&err); } @@ -2457,6 +2756,30 @@ static int close_ref(struct ref_lock *lock) static int commit_ref(struct ref_lock *lock) { + char *path = get_locked_file_path(lock->lk); + struct stat st; + + if (!lstat(path, &st) && S_ISDIR(st.st_mode)) { + /* + * There is a directory at the path we want to rename + * the lockfile to. Hopefully it is empty; try to + * delete it. + */ + size_t len = strlen(path); + struct strbuf sb_path = STRBUF_INIT; + + strbuf_attach(&sb_path, path, len, len); + + /* + * If this fails, commit_lock_file() will also fail + * and will report the problem. + */ + remove_empty_directories(&sb_path); + strbuf_release(&sb_path); + } else { + free(path); + } + if (commit_lock_file(lock->lk)) return -1; return 0; @@ -2475,7 +2798,7 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str strbuf_git_path(logfile, "logs/%s", refname); if (force_create || should_autocreate_reflog(refname)) { if (safe_create_leading_directories(logfile->buf) < 0) { - strbuf_addf(err, "unable to create directory for %s: " + strbuf_addf(err, "unable to create directory for '%s': " "%s", logfile->buf, strerror(errno)); return -1; } @@ -2489,7 +2812,7 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str if (errno == EISDIR) { if (remove_empty_directories(logfile)) { - strbuf_addf(err, "There are still logs under " + strbuf_addf(err, "there are still logs under " "'%s'", logfile->buf); return -1; } @@ -2497,7 +2820,7 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str } if (logfd < 0) { - strbuf_addf(err, "unable to append to %s: %s", + strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, strerror(errno)); return -1; } @@ -2566,13 +2889,13 @@ static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, result = log_ref_write_fd(logfd, old_sha1, new_sha1, git_committer_info(0), msg); if (result) { - strbuf_addf(err, "unable to append to %s: %s", logfile->buf, + strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, strerror(errno)); close(logfd); return -1; } if (close(logfd)) { - strbuf_addf(err, "unable to append to %s: %s", logfile->buf, + strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, strerror(errno)); return -1; } @@ -2613,14 +2936,14 @@ static int write_ref_to_lockfile(struct ref_lock *lock, o = parse_object(sha1); if (!o) { strbuf_addf(err, - "Trying to write ref %s with nonexistent object %s", + "trying to write ref '%s' with nonexistent object %s", lock->ref_name, sha1_to_hex(sha1)); unlock_ref(lock); return -1; } if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { strbuf_addf(err, - "Trying to write non-commit object %s to branch %s", + "trying to write non-commit object %s to branch '%s'", sha1_to_hex(sha1), lock->ref_name); unlock_ref(lock); return -1; @@ -2630,7 +2953,7 @@ static int write_ref_to_lockfile(struct ref_lock *lock, write_in_full(fd, &term, 1) != 1 || close_ref(lock) < 0) { strbuf_addf(err, - "Couldn't write %s", get_lock_file_path(lock->lk)); + "couldn't write '%s'", get_lock_file_path(lock->lk)); unlock_ref(lock); return -1; } @@ -2644,20 +2967,19 @@ static int write_ref_to_lockfile(struct ref_lock *lock, */ static int commit_ref_update(struct ref_lock *lock, const unsigned char *sha1, const char *logmsg, - int flags, struct strbuf *err) + struct strbuf *err) { clear_loose_ref_cache(&ref_cache); - if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 || - (strcmp(lock->ref_name, lock->orig_ref_name) && - log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) { + if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) { char *old_msg = strbuf_detach(err, NULL); - strbuf_addf(err, "Cannot update the ref '%s': %s", + strbuf_addf(err, "cannot update the ref '%s': %s", lock->ref_name, old_msg); free(old_msg); unlock_ref(lock); return -1; } - if (strcmp(lock->orig_ref_name, "HEAD") != 0) { + + if (strcmp(lock->ref_name, "HEAD") != 0) { /* * Special hack: If a branch is updated directly and HEAD * points to it (may happen on the remote side of a push @@ -2673,6 +2995,7 @@ static int commit_ref_update(struct ref_lock *lock, unsigned char head_sha1[20]; int head_flag; const char *head_ref; + head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && @@ -2685,8 +3008,9 @@ static int commit_ref_update(struct ref_lock *lock, } } } + if (commit_ref(lock)) { - error("Couldn't set %s", lock->ref_name); + strbuf_addf(err, "couldn't set '%s'", lock->ref_name); unlock_ref(lock); return -1; } @@ -2790,7 +3114,6 @@ int set_worktree_head_symref(const char *gitdir, const char *target) lock = xcalloc(1, sizeof(struct ref_lock)); lock->lk = &head_lock; lock->ref_name = xstrdup(head_rel); - lock->orig_ref_name = xstrdup(head_rel); ret = create_symref_locked(lock, head_rel, target, NULL); @@ -2969,60 +3292,88 @@ int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_dat 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 - * space, but its contents will be restored before return. - */ -static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data) -{ - DIR *d = opendir(git_path("logs/%s", name->buf)); - int retval = 0; - struct dirent *de; - int oldlen = name->len; - if (!d) - return name->len ? errno : 0; +struct files_reflog_iterator { + struct ref_iterator base; - while ((de = readdir(d)) != NULL) { - struct stat st; + struct dir_iterator *dir_iterator; + struct object_id oid; +}; - if (de->d_name[0] == '.') +static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct files_reflog_iterator *iter = + (struct files_reflog_iterator *)ref_iterator; + struct dir_iterator *diter = iter->dir_iterator; + int ok; + + while ((ok = dir_iterator_advance(diter)) == ITER_OK) { + int flags; + + if (!S_ISREG(diter->st.st_mode)) continue; - if (ends_with(de->d_name, ".lock")) + if (diter->basename[0] == '.') + continue; + if (ends_with(diter->basename, ".lock")) continue; - strbuf_addstr(name, de->d_name); - if (stat(git_path("logs/%s", name->buf), &st) < 0) { - ; /* silently ignore */ - } else { - if (S_ISDIR(st.st_mode)) { - strbuf_addch(name, '/'); - retval = do_for_each_reflog(name, fn, cb_data); - } else { - struct object_id oid; - if (read_ref_full(name->buf, 0, oid.hash, NULL)) - retval = error("bad ref for %s", name->buf); - else - retval = fn(name->buf, &oid, 0, cb_data); - } - if (retval) - break; + if (read_ref_full(diter->relative_path, 0, + iter->oid.hash, &flags)) { + error("bad ref for %s", diter->path.buf); + continue; } - strbuf_setlen(name, oldlen); + + iter->base.refname = diter->relative_path; + iter->base.oid = &iter->oid; + iter->base.flags = flags; + return ITER_OK; } - closedir(d); - return retval; + + iter->dir_iterator = NULL; + if (ref_iterator_abort(ref_iterator) == ITER_ERROR) + ok = ITER_ERROR; + return ok; +} + +static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + die("BUG: ref_iterator_peel() called for reflog_iterator"); +} + +static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct files_reflog_iterator *iter = + (struct files_reflog_iterator *)ref_iterator; + int ok = ITER_DONE; + + if (iter->dir_iterator) + ok = dir_iterator_abort(iter->dir_iterator); + + base_ref_iterator_free(ref_iterator); + return ok; +} + +static struct ref_iterator_vtable files_reflog_iterator_vtable = { + files_reflog_iterator_advance, + files_reflog_iterator_peel, + files_reflog_iterator_abort +}; + +struct ref_iterator *files_reflog_iterator_begin(void) +{ + struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter)); + struct ref_iterator *ref_iterator = &iter->base; + + base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable); + iter->dir_iterator = dir_iterator_begin(git_path("logs")); + return ref_iterator; } int for_each_reflog(each_ref_fn fn, void *cb_data) { - int retval; - struct strbuf name; - strbuf_init(&name, PATH_MAX); - retval = do_for_each_reflog(&name, fn, cb_data); - strbuf_release(&name); - return retval; + return do_for_each_ref_iterator(files_reflog_iterator_begin(), + fn, cb_data); } static int ref_update_reject_duplicates(struct string_list *refnames, @@ -3035,36 +3386,352 @@ static int ref_update_reject_duplicates(struct string_list *refnames, for (i = 1; i < n; i++) if (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) { strbuf_addf(err, - "Multiple updates for ref '%s' not allowed.", + "multiple updates for ref '%s' not allowed.", refnames->items[i].string); return 1; } return 0; } +/* + * If update is a direct update of head_ref (the reference pointed to + * by HEAD), then add an extra REF_LOG_ONLY update for HEAD. + */ +static int split_head_update(struct ref_update *update, + struct ref_transaction *transaction, + const char *head_ref, + struct string_list *affected_refnames, + struct strbuf *err) +{ + struct string_list_item *item; + struct ref_update *new_update; + + if ((update->flags & REF_LOG_ONLY) || + (update->flags & REF_ISPRUNING) || + (update->flags & REF_UPDATE_VIA_HEAD)) + return 0; + + if (strcmp(update->refname, head_ref)) + return 0; + + /* + * First make sure that HEAD is not already in the + * transaction. This insertion is O(N) in the transaction + * size, but it happens at most once per transaction. + */ + item = string_list_insert(affected_refnames, "HEAD"); + if (item->util) { + /* An entry already existed */ + strbuf_addf(err, + "multiple updates for 'HEAD' (including one " + "via its referent '%s') are not allowed", + update->refname); + return TRANSACTION_NAME_CONFLICT; + } + + new_update = ref_transaction_add_update( + transaction, "HEAD", + update->flags | REF_LOG_ONLY | REF_NODEREF, + update->new_sha1, update->old_sha1, + update->msg); + + item->util = new_update; + + return 0; +} + +/* + * update is for a symref that points at referent and doesn't have + * REF_NODEREF set. Split it into two updates: + * - The original update, but with REF_LOG_ONLY and REF_NODEREF set + * - A new, separate update for the referent reference + * Note that the new update will itself be subject to splitting when + * the iteration gets to it. + */ +static int split_symref_update(struct ref_update *update, + const char *referent, + struct ref_transaction *transaction, + struct string_list *affected_refnames, + struct strbuf *err) +{ + struct string_list_item *item; + struct ref_update *new_update; + unsigned int new_flags; + + /* + * First make sure that referent is not already in the + * transaction. This insertion is O(N) in the transaction + * size, but it happens at most once per symref in a + * transaction. + */ + item = string_list_insert(affected_refnames, referent); + if (item->util) { + /* An entry already existed */ + strbuf_addf(err, + "multiple updates for '%s' (including one " + "via symref '%s') are not allowed", + referent, update->refname); + return TRANSACTION_NAME_CONFLICT; + } + + new_flags = update->flags; + if (!strcmp(update->refname, "HEAD")) { + /* + * Record that the new update came via HEAD, so that + * when we process it, split_head_update() doesn't try + * to add another reflog update for HEAD. Note that + * this bit will be propagated if the new_update + * itself needs to be split. + */ + new_flags |= REF_UPDATE_VIA_HEAD; + } + + new_update = ref_transaction_add_update( + transaction, referent, new_flags, + update->new_sha1, update->old_sha1, + update->msg); + + new_update->parent_update = update; + + /* + * Change the symbolic ref update to log only. Also, it + * doesn't need to check its old SHA-1 value, as that will be + * done when new_update is processed. + */ + update->flags |= REF_LOG_ONLY | REF_NODEREF; + update->flags &= ~REF_HAVE_OLD; + + item->util = new_update; + + return 0; +} + +/* + * Return the refname under which update was originally requested. + */ +static const char *original_update_refname(struct ref_update *update) +{ + while (update->parent_update) + update = update->parent_update; + + return update->refname; +} + +/* + * Check whether the REF_HAVE_OLD and old_oid values stored in update + * are consistent with oid, which is the reference's current value. If + * everything is OK, return 0; otherwise, write an error message to + * err and return -1. + */ +static int check_old_oid(struct ref_update *update, struct object_id *oid, + struct strbuf *err) +{ + if (!(update->flags & REF_HAVE_OLD) || + !hashcmp(oid->hash, update->old_sha1)) + return 0; + + if (is_null_sha1(update->old_sha1)) + strbuf_addf(err, "cannot lock ref '%s': " + "reference already exists", + original_update_refname(update)); + else if (is_null_oid(oid)) + strbuf_addf(err, "cannot lock ref '%s': " + "reference is missing but expected %s", + original_update_refname(update), + sha1_to_hex(update->old_sha1)); + else + strbuf_addf(err, "cannot lock ref '%s': " + "is at %s but expected %s", + original_update_refname(update), + oid_to_hex(oid), + sha1_to_hex(update->old_sha1)); + + return -1; +} + +/* + * Prepare for carrying out update: + * - Lock the reference referred to by update. + * - Read the reference under lock. + * - Check that its old SHA-1 value (if specified) is correct, and in + * any case record it in update->lock->old_oid for later use when + * writing the reflog. + * - If it is a symref update without REF_NODEREF, split it up into a + * REF_LOG_ONLY update of the symref and add a separate update for + * the referent to transaction. + * - If it is an update of head_ref, add a corresponding REF_LOG_ONLY + * update of HEAD. + */ +static int lock_ref_for_update(struct ref_update *update, + struct ref_transaction *transaction, + const char *head_ref, + struct string_list *affected_refnames, + struct strbuf *err) +{ + struct strbuf referent = STRBUF_INIT; + int mustexist = (update->flags & REF_HAVE_OLD) && + !is_null_sha1(update->old_sha1); + int ret; + struct ref_lock *lock; + + if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) + update->flags |= REF_DELETING; + + if (head_ref) { + ret = split_head_update(update, transaction, head_ref, + affected_refnames, err); + if (ret) + return ret; + } + + ret = lock_raw_ref(update->refname, mustexist, + affected_refnames, NULL, + &update->lock, &referent, + &update->type, err); + + if (ret) { + char *reason; + + reason = strbuf_detach(err, NULL); + strbuf_addf(err, "cannot lock ref '%s': %s", + original_update_refname(update), reason); + free(reason); + return ret; + } + + lock = update->lock; + + if (update->type & REF_ISSYMREF) { + if (update->flags & REF_NODEREF) { + /* + * We won't be reading the referent as part of + * the transaction, so we have to read it here + * to record and possibly check old_sha1: + */ + if (read_ref_full(referent.buf, 0, + lock->old_oid.hash, NULL)) { + if (update->flags & REF_HAVE_OLD) { + strbuf_addf(err, "cannot lock ref '%s': " + "error reading reference", + original_update_refname(update)); + return -1; + } + } else if (check_old_oid(update, &lock->old_oid, err)) { + return TRANSACTION_GENERIC_ERROR; + } + } else { + /* + * Create a new update for the reference this + * symref is pointing at. Also, we will record + * and verify old_sha1 for this update as part + * of processing the split-off update, so we + * don't have to do it here. + */ + ret = split_symref_update(update, referent.buf, transaction, + affected_refnames, err); + if (ret) + return ret; + } + } else { + struct ref_update *parent_update; + + if (check_old_oid(update, &lock->old_oid, err)) + return TRANSACTION_GENERIC_ERROR; + + /* + * If this update is happening indirectly because of a + * symref update, record the old SHA-1 in the parent + * update: + */ + for (parent_update = update->parent_update; + parent_update; + parent_update = parent_update->parent_update) { + oidcpy(&parent_update->lock->old_oid, &lock->old_oid); + } + } + + if ((update->flags & REF_HAVE_NEW) && + !(update->flags & REF_DELETING) && + !(update->flags & REF_LOG_ONLY)) { + if (!(update->type & REF_ISSYMREF) && + !hashcmp(lock->old_oid.hash, update->new_sha1)) { + /* + * The reference already has the desired + * value, so we don't need to write it. + */ + } else if (write_ref_to_lockfile(lock, update->new_sha1, + err)) { + char *write_err = strbuf_detach(err, NULL); + + /* + * The lock was freed upon failure of + * write_ref_to_lockfile(): + */ + update->lock = NULL; + strbuf_addf(err, + "cannot update ref '%s': %s", + update->refname, write_err); + free(write_err); + return TRANSACTION_GENERIC_ERROR; + } else { + update->flags |= REF_NEEDS_COMMIT; + } + } + if (!(update->flags & REF_NEEDS_COMMIT)) { + /* + * We didn't call write_ref_to_lockfile(), so + * the lockfile is still open. Close it to + * free up the file descriptor: + */ + if (close_ref(lock)) { + strbuf_addf(err, "couldn't close '%s.lock'", + update->refname); + return TRANSACTION_GENERIC_ERROR; + } + } + return 0; +} + int ref_transaction_commit(struct ref_transaction *transaction, struct strbuf *err) { int ret = 0, i; - int n = transaction->nr; - struct ref_update **updates = transaction->updates; struct string_list refs_to_delete = STRING_LIST_INIT_NODUP; struct string_list_item *ref_to_delete; struct string_list affected_refnames = STRING_LIST_INIT_NODUP; + char *head_ref = NULL; + int head_type; + struct object_id head_oid; assert(err); if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: commit called for transaction that is not open"); - if (!n) { + if (!transaction->nr) { transaction->state = REF_TRANSACTION_CLOSED; return 0; } - /* Fail if a refname appears more than once in the transaction: */ - for (i = 0; i < n; i++) - string_list_append(&affected_refnames, updates[i]->refname); + /* + * Fail if a refname appears more than once in the + * transaction. (If we end up splitting up any updates using + * split_symref_update() or split_head_update(), those + * functions will check that the new updates don't have the + * same refname as any existing ones.) + */ + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + struct string_list_item *item = + string_list_append(&affected_refnames, update->refname); + + /* + * We store a pointer to update in item->util, but at + * the moment we never use the value of this field + * except to check whether it is non-NULL. + */ + item->util = update; + } string_list_sort(&affected_refnames); if (ref_update_reject_duplicates(&affected_refnames, err)) { ret = TRANSACTION_GENERIC_ERROR; @@ -3072,105 +3739,84 @@ int ref_transaction_commit(struct ref_transaction *transaction, } /* + * Special hack: If a branch is updated directly and HEAD + * points to it (may happen on the remote side of a push + * for example) then logically the HEAD reflog should be + * updated too. + * + * A generic solution would require reverse symref lookups, + * but finding all symrefs pointing to a given branch would be + * rather costly for this rare event (the direct update of a + * branch) to be worth it. So let's cheat and check with HEAD + * only, which should cover 99% of all usage scenarios (even + * 100% of the default ones). + * + * So if HEAD is a symbolic reference, then record the name of + * the reference that it points to. If we see an update of + * head_ref within the transaction, then split_head_update() + * arranges for the reflog of HEAD to be updated, too. + */ + head_ref = resolve_refdup("HEAD", RESOLVE_REF_NO_RECURSE, + head_oid.hash, &head_type); + + if (head_ref && !(head_type & REF_ISSYMREF)) { + free(head_ref); + head_ref = NULL; + } + + /* * Acquire all locks, verify old values if provided, check * that new values are valid, and write new values to the * lockfiles, ready to be activated. Only keep one lockfile * open at a time to avoid running out of file descriptors. */ - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; - if ((update->flags & REF_HAVE_NEW) && - is_null_sha1(update->new_sha1)) - update->flags |= REF_DELETING; - update->lock = lock_ref_sha1_basic( - update->refname, - ((update->flags & REF_HAVE_OLD) ? - update->old_sha1 : NULL), - &affected_refnames, NULL, - update->flags, - &update->type, - err); - if (!update->lock) { - char *reason; - - ret = (errno == ENOTDIR) - ? TRANSACTION_NAME_CONFLICT - : TRANSACTION_GENERIC_ERROR; - reason = strbuf_detach(err, NULL); - strbuf_addf(err, "cannot lock ref '%s': %s", - update->refname, reason); - free(reason); + ret = lock_ref_for_update(update, transaction, head_ref, + &affected_refnames, err); + if (ret) goto cleanup; - } - if ((update->flags & REF_HAVE_NEW) && - !(update->flags & REF_DELETING)) { - int overwriting_symref = ((update->type & REF_ISSYMREF) && - (update->flags & REF_NODEREF)); - - if (!overwriting_symref && - !hashcmp(update->lock->old_oid.hash, update->new_sha1)) { - /* - * The reference already has the desired - * value, so we don't need to write it. - */ - } else if (write_ref_to_lockfile(update->lock, - update->new_sha1, - err)) { - char *write_err = strbuf_detach(err, NULL); + } - /* - * The lock was freed upon failure of - * write_ref_to_lockfile(): - */ + /* Perform updates first so live commits remain referenced */ + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + struct ref_lock *lock = update->lock; + + if (update->flags & REF_NEEDS_COMMIT || + update->flags & REF_LOG_ONLY) { + if (log_ref_write(lock->ref_name, lock->old_oid.hash, + update->new_sha1, + update->msg, update->flags, err)) { + char *old_msg = strbuf_detach(err, NULL); + + strbuf_addf(err, "cannot update the ref '%s': %s", + lock->ref_name, old_msg); + free(old_msg); + unlock_ref(lock); update->lock = NULL; - strbuf_addf(err, - "cannot update the ref '%s': %s", - update->refname, write_err); - free(write_err); ret = TRANSACTION_GENERIC_ERROR; goto cleanup; - } else { - update->flags |= REF_NEEDS_COMMIT; - } - } - if (!(update->flags & REF_NEEDS_COMMIT)) { - /* - * We didn't have to write anything to the lockfile. - * Close it to free up the file descriptor: - */ - if (close_ref(update->lock)) { - strbuf_addf(err, "Couldn't close %s.lock", - update->refname); - goto cleanup; } } - } - - /* Perform updates first so live commits remain referenced */ - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; - if (update->flags & REF_NEEDS_COMMIT) { - if (commit_ref_update(update->lock, - update->new_sha1, update->msg, - update->flags, err)) { - /* freed by commit_ref_update(): */ + clear_loose_ref_cache(&ref_cache); + if (commit_ref(lock)) { + strbuf_addf(err, "couldn't set '%s'", lock->ref_name); + unlock_ref(lock); update->lock = NULL; ret = TRANSACTION_GENERIC_ERROR; goto cleanup; - } else { - /* freed by commit_ref_update(): */ - update->lock = NULL; } } } - /* Perform deletes now that updates are safely completed */ - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; - if (update->flags & REF_DELETING) { + if (update->flags & REF_DELETING && + !(update->flags & REF_LOG_ONLY)) { if (delete_ref_loose(update->lock, update->type, err)) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3193,11 +3839,13 @@ int ref_transaction_commit(struct ref_transaction *transaction, cleanup: transaction->state = REF_TRANSACTION_CLOSED; - for (i = 0; i < n; i++) - if (updates[i]->lock) - unlock_ref(updates[i]->lock); + for (i = 0; i < transaction->nr; i++) + if (transaction->updates[i]->lock) + unlock_ref(transaction->updates[i]->lock); string_list_clear(&refs_to_delete, 0); + free(head_ref); string_list_clear(&affected_refnames, 0); + return ret; } @@ -3213,8 +3861,6 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, struct strbuf *err) { int ret = 0, i; - int n = transaction->nr; - struct ref_update **updates = transaction->updates; struct string_list affected_refnames = STRING_LIST_INIT_NODUP; assert(err); @@ -3223,8 +3869,9 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, die("BUG: commit called for transaction that is not open"); /* Fail if a refname appears more than once in the transaction: */ - for (i = 0; i < n; i++) - string_list_append(&affected_refnames, updates[i]->refname); + for (i = 0; i < transaction->nr; i++) + string_list_append(&affected_refnames, + transaction->updates[i]->refname); string_list_sort(&affected_refnames); if (ref_update_reject_duplicates(&affected_refnames, err)) { ret = TRANSACTION_GENERIC_ERROR; @@ -3246,8 +3893,8 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, if (for_each_rawref(ref_present, &affected_refnames)) die("BUG: initial ref transaction called with existing refs"); - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; if ((update->flags & REF_HAVE_OLD) && !is_null_sha1(update->old_sha1)) @@ -3267,8 +3914,8 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, goto cleanup; } - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; if ((update->flags & REF_HAVE_NEW) && !is_null_sha1(update->new_sha1)) diff --git a/refs/iterator.c b/refs/iterator.c new file mode 100644 index 0000000000..bce1f192f7 --- /dev/null +++ b/refs/iterator.c @@ -0,0 +1,384 @@ +/* + * Generic reference iterator infrastructure. See refs-internal.h for + * documentation about the design and use of reference iterators. + */ + +#include "cache.h" +#include "refs.h" +#include "refs/refs-internal.h" +#include "iterator.h" + +int ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + return ref_iterator->vtable->advance(ref_iterator); +} + +int ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + return ref_iterator->vtable->peel(ref_iterator, peeled); +} + +int ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + return ref_iterator->vtable->abort(ref_iterator); +} + +void base_ref_iterator_init(struct ref_iterator *iter, + struct ref_iterator_vtable *vtable) +{ + iter->vtable = vtable; + iter->refname = NULL; + iter->oid = NULL; + iter->flags = 0; +} + +void base_ref_iterator_free(struct ref_iterator *iter) +{ + /* Help make use-after-free bugs fail quickly: */ + iter->vtable = NULL; + free(iter); +} + +struct empty_ref_iterator { + struct ref_iterator base; +}; + +static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + return ref_iterator_abort(ref_iterator); +} + +static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + die("BUG: peel called for empty iterator"); +} + +static int empty_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + base_ref_iterator_free(ref_iterator); + return ITER_DONE; +} + +static struct ref_iterator_vtable empty_ref_iterator_vtable = { + empty_ref_iterator_advance, + empty_ref_iterator_peel, + empty_ref_iterator_abort +}; + +struct ref_iterator *empty_ref_iterator_begin(void) +{ + struct empty_ref_iterator *iter = xcalloc(1, sizeof(*iter)); + struct ref_iterator *ref_iterator = &iter->base; + + base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable); + return ref_iterator; +} + +int is_empty_ref_iterator(struct ref_iterator *ref_iterator) +{ + return ref_iterator->vtable == &empty_ref_iterator_vtable; +} + +struct merge_ref_iterator { + struct ref_iterator base; + + struct ref_iterator *iter0, *iter1; + + ref_iterator_select_fn *select; + void *cb_data; + + /* + * A pointer to iter0 or iter1 (whichever is supplying the + * current value), or NULL if advance has not yet been called. + */ + struct ref_iterator **current; +}; + +static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct merge_ref_iterator *iter = + (struct merge_ref_iterator *)ref_iterator; + int ok; + + if (!iter->current) { + /* Initialize: advance both iterators to their first entries */ + if ((ok = ref_iterator_advance(iter->iter0)) != ITER_OK) { + iter->iter0 = NULL; + if (ok == ITER_ERROR) + goto error; + } + if ((ok = ref_iterator_advance(iter->iter1)) != ITER_OK) { + iter->iter1 = NULL; + if (ok == ITER_ERROR) + goto error; + } + } else { + /* + * Advance the current iterator past the just-used + * entry: + */ + if ((ok = ref_iterator_advance(*iter->current)) != ITER_OK) { + *iter->current = NULL; + if (ok == ITER_ERROR) + goto error; + } + } + + /* Loop until we find an entry that we can yield. */ + while (1) { + struct ref_iterator **secondary; + enum iterator_selection selection = + iter->select(iter->iter0, iter->iter1, iter->cb_data); + + if (selection == ITER_SELECT_DONE) { + return ref_iterator_abort(ref_iterator); + } else if (selection == ITER_SELECT_ERROR) { + ref_iterator_abort(ref_iterator); + return ITER_ERROR; + } + + if ((selection & ITER_CURRENT_SELECTION_MASK) == 0) { + iter->current = &iter->iter0; + secondary = &iter->iter1; + } else { + iter->current = &iter->iter1; + secondary = &iter->iter0; + } + + if (selection & ITER_SKIP_SECONDARY) { + if ((ok = ref_iterator_advance(*secondary)) != ITER_OK) { + *secondary = NULL; + if (ok == ITER_ERROR) + goto error; + } + } + + if (selection & ITER_YIELD_CURRENT) { + iter->base.refname = (*iter->current)->refname; + iter->base.oid = (*iter->current)->oid; + iter->base.flags = (*iter->current)->flags; + return ITER_OK; + } + } + +error: + ref_iterator_abort(ref_iterator); + return ITER_ERROR; +} + +static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct merge_ref_iterator *iter = + (struct merge_ref_iterator *)ref_iterator; + + if (!iter->current) { + die("BUG: peel called before advance for merge iterator"); + } + return ref_iterator_peel(*iter->current, peeled); +} + +static int merge_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct merge_ref_iterator *iter = + (struct merge_ref_iterator *)ref_iterator; + int ok = ITER_DONE; + + if (iter->iter0) { + if (ref_iterator_abort(iter->iter0) != ITER_DONE) + ok = ITER_ERROR; + } + if (iter->iter1) { + if (ref_iterator_abort(iter->iter1) != ITER_DONE) + ok = ITER_ERROR; + } + base_ref_iterator_free(ref_iterator); + return ok; +} + +static struct ref_iterator_vtable merge_ref_iterator_vtable = { + merge_ref_iterator_advance, + merge_ref_iterator_peel, + merge_ref_iterator_abort +}; + +struct ref_iterator *merge_ref_iterator_begin( + struct ref_iterator *iter0, struct ref_iterator *iter1, + ref_iterator_select_fn *select, void *cb_data) +{ + struct merge_ref_iterator *iter = xcalloc(1, sizeof(*iter)); + struct ref_iterator *ref_iterator = &iter->base; + + /* + * We can't do the same kind of is_empty_ref_iterator()-style + * optimization here as overlay_ref_iterator_begin() does, + * because we don't know the semantics of the select function. + * It might, for example, implement "intersect" by passing + * references through only if they exist in both iterators. + */ + + base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable); + iter->iter0 = iter0; + iter->iter1 = iter1; + iter->select = select; + iter->cb_data = cb_data; + iter->current = NULL; + return ref_iterator; +} + +/* + * A ref_iterator_select_fn that overlays the items from front on top + * of those from back (like loose refs over packed refs). See + * overlay_ref_iterator_begin(). + */ +static enum iterator_selection overlay_iterator_select( + struct ref_iterator *front, struct ref_iterator *back, + void *cb_data) +{ + int cmp; + + if (!back) + return front ? ITER_SELECT_0 : ITER_SELECT_DONE; + else if (!front) + return ITER_SELECT_1; + + cmp = strcmp(front->refname, back->refname); + + if (cmp < 0) + return ITER_SELECT_0; + else if (cmp > 0) + return ITER_SELECT_1; + else + return ITER_SELECT_0_SKIP_1; +} + +struct ref_iterator *overlay_ref_iterator_begin( + struct ref_iterator *front, struct ref_iterator *back) +{ + /* + * Optimization: if one of the iterators is empty, return the + * other one rather than incurring the overhead of wrapping + * them. + */ + if (is_empty_ref_iterator(front)) { + ref_iterator_abort(front); + return back; + } else if (is_empty_ref_iterator(back)) { + ref_iterator_abort(back); + return front; + } + + return merge_ref_iterator_begin(front, back, + overlay_iterator_select, NULL); +} + +struct prefix_ref_iterator { + struct ref_iterator base; + + struct ref_iterator *iter0; + char *prefix; + int trim; +}; + +static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct prefix_ref_iterator *iter = + (struct prefix_ref_iterator *)ref_iterator; + int ok; + + while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { + if (!starts_with(iter->iter0->refname, iter->prefix)) + continue; + + iter->base.refname = iter->iter0->refname + iter->trim; + iter->base.oid = iter->iter0->oid; + iter->base.flags = iter->iter0->flags; + return ITER_OK; + } + + iter->iter0 = NULL; + if (ref_iterator_abort(ref_iterator) != ITER_DONE) + return ITER_ERROR; + return ok; +} + +static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct prefix_ref_iterator *iter = + (struct prefix_ref_iterator *)ref_iterator; + + return ref_iterator_peel(iter->iter0, peeled); +} + +static int prefix_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct prefix_ref_iterator *iter = + (struct prefix_ref_iterator *)ref_iterator; + int ok = ITER_DONE; + + if (iter->iter0) + ok = ref_iterator_abort(iter->iter0); + free(iter->prefix); + base_ref_iterator_free(ref_iterator); + return ok; +} + +static struct ref_iterator_vtable prefix_ref_iterator_vtable = { + prefix_ref_iterator_advance, + prefix_ref_iterator_peel, + prefix_ref_iterator_abort +}; + +struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, + const char *prefix, + int trim) +{ + struct prefix_ref_iterator *iter; + struct ref_iterator *ref_iterator; + + if (!*prefix && !trim) + return iter0; /* optimization: no need to wrap iterator */ + + iter = xcalloc(1, sizeof(*iter)); + ref_iterator = &iter->base; + + base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable); + + iter->iter0 = iter0; + iter->prefix = xstrdup(prefix); + iter->trim = trim; + + return ref_iterator; +} + +struct ref_iterator *current_ref_iter = NULL; + +int do_for_each_ref_iterator(struct ref_iterator *iter, + each_ref_fn fn, void *cb_data) +{ + int retval = 0, ok; + struct ref_iterator *old_ref_iter = current_ref_iter; + + current_ref_iter = iter; + while ((ok = ref_iterator_advance(iter)) == ITER_OK) { + retval = fn(iter->refname, iter->oid, iter->flags, cb_data); + if (retval) { + /* + * If ref_iterator_abort() returns ITER_ERROR, + * we ignore that error in deference to the + * callback function's return value. + */ + ref_iterator_abort(iter); + goto out; + } + } + +out: + current_ref_iter = old_ref_iter; + if (ok == ITER_ERROR) + return -1; + return retval; +} diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 3a4f634cb4..efe584701b 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -15,7 +15,7 @@ /* * Used as a flag in ref_update::flags when a loose ref is being - * pruned. + * pruned. This flag must only be used when REF_NODEREF is set. */ #define REF_ISPRUNING 0x04 @@ -43,6 +43,19 @@ */ /* + * Used as a flag in ref_update::flags when we want to log a ref + * update but not actually perform it. This is used when a symbolic + * ref update is split up. + */ +#define REF_LOG_ONLY 0x80 + +/* + * Internal flag, meaning that the containing ref_update was via an + * update to HEAD. + */ +#define REF_UPDATE_VIA_HEAD 0x100 + +/* * Return true iff refname is minimally safe. "Safe" here means that * deleting a loose reference by this name will not do any damage, for * example by causing a file that is not a reference to be deleted. @@ -109,8 +122,8 @@ enum peel_status peel_object(const unsigned char *name, unsigned char *sha1); * extras and skip must be sorted. */ int verify_refname_available(const char *newname, - struct string_list *extras, - struct string_list *skip, + const struct string_list *extras, + const struct string_list *skip, struct strbuf *err); /* @@ -130,27 +143,59 @@ int should_autocreate_reflog(const char *refname); * not exist before update. */ struct ref_update { + /* * If (flags & REF_HAVE_NEW), set the reference to this value: */ unsigned char new_sha1[20]; + /* * If (flags & REF_HAVE_OLD), check that the reference * previously had this value: */ unsigned char old_sha1[20]; + /* * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF, - * REF_DELETING, and REF_ISPRUNING: + * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and + * REF_UPDATE_VIA_HEAD: */ unsigned int flags; + struct ref_lock *lock; - int type; + unsigned int type; char *msg; + + /* + * If this ref_update was split off of a symref update via + * split_symref_update(), then this member points at that + * update. This is used for two purposes: + * 1. When reporting errors, we report the refname under which + * the update was originally requested. + * 2. When we read the old value of this reference, we + * propagate it back to its parent update for recording in + * the latter's reflog. + */ + struct ref_update *parent_update; + const char refname[FLEX_ARRAY]; }; /* + * Add a ref_update with the specified properties to transaction, and + * return a pointer to the new object. This function does not verify + * that refname is well-formed. new_sha1 and old_sha1 are only + * dereferenced if the REF_HAVE_NEW and REF_HAVE_OLD bits, + * respectively, are set in flags. + */ +struct ref_update *ref_transaction_add_update( + struct ref_transaction *transaction, + const char *refname, unsigned int flags, + const unsigned char *new_sha1, + const unsigned char *old_sha1, + const char *msg); + +/* * Transaction states. * OPEN: The transaction is in a valid state and can accept new updates. * An OPEN transaction can be committed. @@ -204,12 +249,270 @@ int rename_ref_available(const char *oldname, const char *newname); #define DO_FOR_EACH_INCLUDE_BROKEN 0x01 /* - * The common backend for the for_each_*ref* functions + * Reference iterators + * + * A reference iterator encapsulates the state of an in-progress + * iteration over references. Create an instance of `struct + * ref_iterator` via one of the functions in this module. + * + * A freshly-created ref_iterator doesn't yet point at a reference. To + * advance the iterator, call ref_iterator_advance(). If successful, + * this sets the iterator's refname, oid, and flags fields to describe + * the next reference and returns ITER_OK. The data pointed at by + * refname and oid belong to the iterator; if you want to retain them + * after calling ref_iterator_advance() again or calling + * ref_iterator_abort(), you must make a copy. When the iteration has + * been exhausted, ref_iterator_advance() releases any resources + * assocated with the iteration, frees the ref_iterator object, and + * returns ITER_DONE. If you want to abort the iteration early, call + * ref_iterator_abort(), which also frees the ref_iterator object and + * any associated resources. If there was an internal error advancing + * to the next entry, ref_iterator_advance() aborts the iteration, + * frees the ref_iterator, and returns ITER_ERROR. + * + * The reference currently being looked at can be peeled by calling + * ref_iterator_peel(). This function is often faster than peel_ref(), + * so it should be preferred when iterating over references. + * + * Putting it all together, a typical iteration looks like this: + * + * int ok; + * struct ref_iterator *iter = ...; + * + * while ((ok = ref_iterator_advance(iter)) == ITER_OK) { + * if (want_to_stop_iteration()) { + * ok = ref_iterator_abort(iter); + * break; + * } + * + * // Access information about the current reference: + * if (!(iter->flags & REF_ISSYMREF)) + * printf("%s is %s\n", iter->refname, oid_to_hex(&iter->oid)); + * + * // If you need to peel the reference: + * ref_iterator_peel(iter, &oid); + * } + * + * if (ok != ITER_DONE) + * handle_error(); + */ +struct ref_iterator { + struct ref_iterator_vtable *vtable; + const char *refname; + const struct object_id *oid; + unsigned int flags; +}; + +/* + * Advance the iterator to the first or next item and return ITER_OK. + * If the iteration is exhausted, free the resources associated with + * the ref_iterator and return ITER_DONE. On errors, free the iterator + * resources and return ITER_ERROR. It is a bug to use ref_iterator or + * call this function again after it has returned ITER_DONE or + * ITER_ERROR. + */ +int ref_iterator_advance(struct ref_iterator *ref_iterator); + +/* + * If possible, peel the reference currently being viewed by the + * iterator. Return 0 on success. + */ +int ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled); + +/* + * End the iteration before it has been exhausted, freeing the + * reference iterator and any associated resources and returning + * ITER_DONE. If the abort itself failed, return ITER_ERROR. + */ +int ref_iterator_abort(struct ref_iterator *ref_iterator); + +/* + * An iterator over nothing (its first ref_iterator_advance() call + * returns ITER_DONE). + */ +struct ref_iterator *empty_ref_iterator_begin(void); + +/* + * Return true iff ref_iterator is an empty_ref_iterator. + */ +int is_empty_ref_iterator(struct ref_iterator *ref_iterator); + +/* + * A callback function used to instruct merge_ref_iterator how to + * interleave the entries from iter0 and iter1. The function should + * return one of the constants defined in enum iterator_selection. It + * must not advance either of the iterators itself. + * + * The function must be prepared to handle the case that iter0 and/or + * iter1 is NULL, which indicates that the corresponding sub-iterator + * has been exhausted. Its return value must be consistent with the + * current states of the iterators; e.g., it must not return + * ITER_SKIP_1 if iter1 has already been exhausted. */ -int do_for_each_ref(const char *submodule, const char *base, - each_ref_fn fn, int trim, int flags, void *cb_data); +typedef enum iterator_selection ref_iterator_select_fn( + struct ref_iterator *iter0, struct ref_iterator *iter1, + void *cb_data); +/* + * Iterate over the entries from iter0 and iter1, with the values + * interleaved as directed by the select function. The iterator takes + * ownership of iter0 and iter1 and frees them when the iteration is + * over. + */ +struct ref_iterator *merge_ref_iterator_begin( + struct ref_iterator *iter0, struct ref_iterator *iter1, + ref_iterator_select_fn *select, void *cb_data); + +/* + * An iterator consisting of the union of the entries from front and + * back. If there are entries common to the two sub-iterators, use the + * one from front. Each iterator must iterate over its entries in + * strcmp() order by refname for this to work. + * + * The new iterator takes ownership of its arguments and frees them + * when the iteration is over. As a convenience to callers, if front + * or back is an empty_ref_iterator, then abort that one immediately + * and return the other iterator directly, without wrapping it. + */ +struct ref_iterator *overlay_ref_iterator_begin( + struct ref_iterator *front, struct ref_iterator *back); + +/* + * Wrap iter0, only letting through the references whose names start + * with prefix. If trim is set, set iter->refname to the name of the + * reference with that many characters trimmed off the front; + * otherwise set it to the full refname. The new iterator takes over + * ownership of iter0 and frees it when iteration is over. It makes + * its own copy of prefix. + * + * As an convenience to callers, if prefix is the empty string and + * trim is zero, this function returns iter0 directly, without + * wrapping it. + */ +struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, + const char *prefix, + int trim); + +/* + * Iterate over the packed and loose references in the specified + * submodule that are within find_containing_dir(prefix). If prefix is + * NULL or the empty string, iterate over all references in the + * submodule. + */ +struct ref_iterator *files_ref_iterator_begin(const char *submodule, + const char *prefix, + unsigned int flags); + +/* + * Iterate over the references in the main ref_store that have a + * reflog. The paths within a directory are iterated over in arbitrary + * order. + */ +struct ref_iterator *files_reflog_iterator_begin(void); + +/* Internal implementation of reference iteration: */ + +/* + * Base class constructor for ref_iterators. Initialize the + * ref_iterator part of iter, setting its vtable pointer as specified. + * This is meant to be called only by the initializers of derived + * classes. + */ +void base_ref_iterator_init(struct ref_iterator *iter, + struct ref_iterator_vtable *vtable); + +/* + * Base class destructor for ref_iterators. Destroy the ref_iterator + * part of iter and shallow-free the object. This is meant to be + * called only by the destructors of derived classes. + */ +void base_ref_iterator_free(struct ref_iterator *iter); + +/* Virtual function declarations for ref_iterators: */ + +typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator); + +typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator, + struct object_id *peeled); + +/* + * Implementations of this function should free any resources specific + * to the derived class, then call base_ref_iterator_free() to clean + * up and free the ref_iterator object. + */ +typedef int ref_iterator_abort_fn(struct ref_iterator *ref_iterator); + +struct ref_iterator_vtable { + ref_iterator_advance_fn *advance; + ref_iterator_peel_fn *peel; + ref_iterator_abort_fn *abort; +}; + +/* + * current_ref_iter is a performance hack: when iterating over + * references using the for_each_ref*() functions, current_ref_iter is + * set to the reference iterator before calling the callback function. + * If the callback function calls peel_ref(), then peel_ref() first + * checks whether the reference to be peeled is the one referred to by + * the iterator (it usually is) and if so, asks the iterator for the + * peeled version of the reference if it is available. This avoids a + * refname lookup in a common case. current_ref_iter is set to NULL + * when the iteration is over. + */ +extern struct ref_iterator *current_ref_iter; + +/* + * The common backend for the for_each_*ref* functions. Call fn for + * each reference in iter. If the iterator itself ever returns + * ITER_ERROR, return -1. If fn ever returns a non-zero value, stop + * the iteration and return that value. Otherwise, return 0. In any + * case, free the iterator when done. This function is basically an + * adapter between the callback style of reference iteration and the + * iterator style. + */ +int do_for_each_ref_iterator(struct ref_iterator *iter, + each_ref_fn fn, void *cb_data); + +/* + * Read the specified reference from the filesystem or packed refs + * file, non-recursively. Set type to describe the reference, and: + * + * - If refname is the name of a normal reference, fill in sha1 + * (leaving referent unchanged). + * + * - If refname is the name of a symbolic reference, write the full + * name of the reference to which it refers (e.g. + * "refs/heads/master") to referent and set the REF_ISSYMREF bit in + * type (leaving sha1 unchanged). The caller is responsible for + * validating that referent is a valid reference name. + * + * WARNING: refname might be used as part of a filename, so it is + * important from a security standpoint that it be safe in the sense + * of refname_is_safe(). Moreover, for symrefs this function sets + * referent to whatever the repository says, which might not be a + * properly-formatted or even safe reference name. NEITHER INPUT NOR + * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION. + * + * Return 0 on success. If the ref doesn't exist, set errno to ENOENT + * and return -1. If the ref exists but is neither a symbolic ref nor + * a sha1, it is broken; set REF_ISBROKEN in type, set errno to + * EINVAL, and return -1. If there is another error reading the ref, + * set errno appropriately and return -1. + * + * Backend-specific flags might be set in type as well, regardless of + * outcome. + * + * It is OK for refname to point into referent. If so: + * + * - if the function succeeds with REF_ISSYMREF, referent will be + * overwritten and the memory formerly pointed to by it might be + * changed or even freed. + * + * - in all other cases, referent will be untouched, and therefore + * refname will still be valid and unchanged. + */ int read_raw_ref(const char *refname, unsigned char *sha1, - struct strbuf *symref, unsigned int *flags); + struct strbuf *referent, unsigned int *type); #endif /* REFS_REFS_INTERNAL_H */ diff --git a/remote-curl.c b/remote-curl.c index 672b382e5a..6b83b7783e 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -984,14 +984,11 @@ static void parse_push(struct strbuf *buf) free(specs); } -int main(int argc, const char **argv) +int cmd_main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT; int nongit; - git_setup_gettext(); - - git_extract_argv0_path(argv[0]); setup_git_directory_gently(&nongit); if (argc < 2) { error("remote-curl: usage: git remote-curl <remote> [<url>]"); diff --git a/remote-testsvn.c b/remote-testsvn.c index f05ff45298..f87bf851ba 100644 --- a/remote-testsvn.c +++ b/remote-testsvn.c @@ -284,7 +284,7 @@ static int do_command(struct strbuf *line) return 0; } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct strbuf buf = STRBUF_INIT, url_sb = STRBUF_INIT, private_ref_sb = STRBUF_INIT, marksfilename_sb = STRBUF_INIT, @@ -292,7 +292,6 @@ int main(int argc, char **argv) static struct remote *remote; const char *url_in; - git_extract_argv0_path(argv[0]); setup_git_directory(); if (argc < 2 || argc > 3) { usage("git-remote-svn <remote-name> [<url>]"); diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c index 2842a22d7f..e06b2c1311 100644 --- a/sh-i18n--envsubst.c +++ b/sh-i18n--envsubst.c @@ -64,7 +64,7 @@ static void note_variables (const char *string); static void subst_from_stdin (void); int -main (int argc, char *argv[]) +cmd_main (int argc, const char *argv[]) { /* Default values for command line options. */ /* unsigned short int show_variables = 0; */ diff --git a/sha1_file.c b/sha1_file.c index d5e11217f5..cb571ac6e8 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2281,7 +2281,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, if (do_check_packed_object_crc && p->index_version > 1) { struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); - unsigned long len = revidx[1].offset - obj_offset; + off_t len = revidx[1].offset - obj_offset; if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { const unsigned char *sha1 = nth_packed_object_sha1(p, revidx->nr); @@ -138,24 +138,13 @@ static struct commands { { NULL }, }; -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { char *prog; const char **user_argv; struct commands *cmd; int count; - git_setup_gettext(); - - git_extract_argv0_path(argv[0]); - - /* - * Always open file descriptors 0/1/2 to avoid clobbering files - * in die(). It also avoids messing up when the pipes are dup'ed - * onto stdin/stdout/stderr in the child processes we spawn. - */ - sanitize_stdfds(); - /* * Special hack to pretend to be a CVS server */ diff --git a/shortlog.h b/shortlog.h index de4f86fb97..5a326c6860 100644 --- a/shortlog.h +++ b/shortlog.h @@ -17,6 +17,7 @@ struct shortlog { char *common_repo_prefix; int email; struct string_list mailmap; + FILE *file; }; void shortlog_init(struct shortlog *log); diff --git a/show-index.c b/show-index.c index acf8d5445a..1ead41e211 100644 --- a/show-index.c +++ b/show-index.c @@ -4,15 +4,13 @@ static const char show_index_usage[] = "git show-index"; -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { int i; unsigned nr; unsigned int version; static unsigned int top_index[256]; - git_setup_gettext(); - if (argc != 1) usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) diff --git a/sideband.c b/sideband.c index c09929dafa..1e4d684d6c 100644 --- a/sideband.c +++ b/sideband.c @@ -13,111 +13,95 @@ * the remote died unexpectedly. A flush() concludes the stream. */ -#define PREFIX "remote:" +#define PREFIX "remote: " #define ANSI_SUFFIX "\033[K" #define DUMB_SUFFIX " " -#define FIX_SIZE 10 /* large enough for any of the above */ - int recv_sideband(const char *me, int in_stream, int out) { - unsigned pf = strlen(PREFIX); - unsigned sf; - char buf[LARGE_PACKET_MAX + 2*FIX_SIZE]; - char *suffix, *term; - int skip_pf = 0; + const char *term, *suffix; + char buf[LARGE_PACKET_MAX + 1]; + struct strbuf outbuf = STRBUF_INIT; + int retval = 0; - memcpy(buf, PREFIX, pf); term = getenv("TERM"); if (isatty(2) && term && strcmp(term, "dumb")) suffix = ANSI_SUFFIX; else suffix = DUMB_SUFFIX; - sf = strlen(suffix); - while (1) { + while (!retval) { + const char *b, *brk; int band, len; - len = packet_read(in_stream, NULL, NULL, buf + pf, LARGE_PACKET_MAX, 0); + len = packet_read(in_stream, NULL, NULL, buf, LARGE_PACKET_MAX, 0); if (len == 0) break; if (len < 1) { - fprintf(stderr, "%s: protocol error: no band designator\n", me); - return SIDEBAND_PROTOCOL_ERROR; + strbuf_addf(&outbuf, + "%s%s: protocol error: no band designator", + outbuf.len ? "\n" : "", me); + retval = SIDEBAND_PROTOCOL_ERROR; + break; } - band = buf[pf] & 0xff; + band = buf[0] & 0xff; + buf[len] = '\0'; len--; switch (band) { case 3: - buf[pf] = ' '; - buf[pf+1+len] = '\0'; - fprintf(stderr, "%s\n", buf); - return SIDEBAND_REMOTE_ERROR; + strbuf_addf(&outbuf, "%s%s%s", outbuf.len ? "\n" : "", + PREFIX, buf + 1); + retval = SIDEBAND_REMOTE_ERROR; + break; case 2: - buf[pf] = ' '; - do { - char *b = buf; - int brk = 0; + b = buf + 1; - /* - * If the last buffer didn't end with a line - * break then we should not print a prefix - * this time around. - */ - if (skip_pf) { - b += pf+1; - } else { - len += pf+1; - brk += pf+1; - } - - /* Look for a line break. */ - for (;;) { - brk++; - if (brk > len) { - brk = 0; - break; - } - if (b[brk-1] == '\n' || - b[brk-1] == '\r') - break; - } + /* + * Append a suffix to each nonempty line to clear the + * end of the screen line. + * + * The output is accumulated in a buffer and + * each line is printed to stderr using + * write(2) to ensure inter-process atomicity. + */ + while ((brk = strpbrk(b, "\n\r"))) { + int linelen = brk - b; - /* - * Let's insert a suffix to clear the end - * of the screen line if a line break was - * found. Also, if we don't skip the - * prefix, then a non-empty string must be - * present too. - */ - if (brk > (skip_pf ? 0 : (pf+1 + 1))) { - char save[FIX_SIZE]; - memcpy(save, b + brk, sf); - b[brk + sf - 1] = b[brk - 1]; - memcpy(b + brk - 1, suffix, sf); - fprintf(stderr, "%.*s", brk + sf, b); - memcpy(b + brk, save, sf); - len -= brk; + if (!outbuf.len) + strbuf_addstr(&outbuf, PREFIX); + if (linelen > 0) { + strbuf_addf(&outbuf, "%.*s%s%c", + linelen, b, suffix, *brk); } else { - int l = brk ? brk : len; - fprintf(stderr, "%.*s", l, b); - len -= l; + strbuf_addch(&outbuf, *brk); } + xwrite(2, outbuf.buf, outbuf.len); + strbuf_reset(&outbuf); - skip_pf = !brk; - memmove(buf + pf+1, b + brk, len); - } while (len); - continue; + b = brk + 1; + } + + if (*b) + strbuf_addf(&outbuf, "%s%s", + outbuf.len ? "" : PREFIX, b); + break; case 1: - write_or_die(out, buf + pf+1, len); - continue; + write_or_die(out, buf + 1, len); + break; default: - fprintf(stderr, "%s: protocol error: bad band #%d\n", - me, band); - return SIDEBAND_PROTOCOL_ERROR; + strbuf_addf(&outbuf, "%s%s: protocol error: bad band #%d", + outbuf.len ? "\n" : "", me, band); + retval = SIDEBAND_PROTOCOL_ERROR; + break; } } - return 0; + + if (outbuf.len) { + strbuf_addch(&outbuf, '\n'); + xwrite(2, outbuf.buf, outbuf.len); + } + strbuf_release(&outbuf); + return retval; } /* @@ -197,6 +197,13 @@ void strbuf_add(struct strbuf *sb, const void *data, size_t len) strbuf_setlen(sb, sb->len + len); } +void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) +{ + strbuf_grow(sb, sb2->len); + memcpy(sb->buf + sb->len, sb2->buf, sb2->len); + strbuf_setlen(sb, sb->len + sb2->len); +} + void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len) { strbuf_grow(sb, len); @@ -263,11 +263,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) /** * Copy the contents of another buffer at the end of the current one. */ -static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) -{ - strbuf_grow(sb, sb2->len); - strbuf_add(sb, sb2->buf, sb2->len); -} +extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); /** * Copy part of the buffer from a given position till a given length to the diff --git a/submodule-config.c b/submodule-config.c index db1847ff68..077db4054f 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -377,7 +377,7 @@ static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, int ret = 0; if (is_null_sha1(commit_sha1)) { - hashcpy(gitmodules_sha1, null_sha1); + hashclr(gitmodules_sha1); return 1; } diff --git a/submodule.c b/submodule.c index abc2ac2a10..1b5cdfb7e7 100644 --- a/submodule.c +++ b/submodule.c @@ -445,7 +445,7 @@ static void collect_submodules_from_diff(struct diff_queue_struct *q, struct diff_filepair *p = q->queue[i]; if (!S_ISGITLINK(p->two->mode)) continue; - if (submodule_needs_pushing(p->two->path, p->two->sha1)) + if (submodule_needs_pushing(p->two->path, p->two->oid.hash)) string_list_insert(needs_pushing, p->two->path); } } @@ -577,7 +577,7 @@ static void submodule_collect_changed_cb(struct diff_queue_struct *q, * being moved around. */ struct string_list_item *path; path = unsorted_string_list_lookup(&changed_submodule_paths, p->two->path); - if (!path && !is_submodule_commit_present(p->two->path, p->two->sha1)) + if (!path && !is_submodule_commit_present(p->two->path, p->two->oid.hash)) string_list_append(&changed_submodule_paths, xstrdup(p->two->path)); } else { /* Submodule is new or was moved here */ diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c index dfe8a83261..e760256406 100644 --- a/t/helper/test-chmtime.c +++ b/t/helper/test-chmtime.c @@ -56,7 +56,7 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) return 1; } -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { static int verbose; diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 509aeef400..3c6d08cd09 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -66,7 +66,7 @@ static int iterate_cb(const char *var, const char *value, void *data) return 0; } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { int i, val; const char *v; diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c index 707a821f03..bb72c47df5 100644 --- a/t/helper/test-ctype.c +++ b/t/helper/test-ctype.c @@ -28,7 +28,7 @@ static int is_in(const char *s, int ch) #define LOWER "abcdefghijklmnopqrstuvwxyz" #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { TEST_CLASS(isdigit, DIGIT); TEST_CLASS(isspace, " \n\r\t"); diff --git a/t/helper/test-date.c b/t/helper/test-date.c index d9ab360909..506054bcd5 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -6,7 +6,7 @@ static const char *usage_msg = "\n" " test-date parse [date]...\n" " test-date approxidate [date]...\n"; -static void show_relative_dates(char **argv, struct timeval *now) +static void show_relative_dates(const char **argv, struct timeval *now) { struct strbuf buf = STRBUF_INIT; @@ -18,13 +18,13 @@ static void show_relative_dates(char **argv, struct timeval *now) strbuf_release(&buf); } -static void show_dates(char **argv, const char *format) +static void show_dates(const char **argv, const char *format) { struct date_mode mode; parse_date_format(format, &mode); for (; *argv; argv++) { - char *arg = *argv; + char *arg; time_t t; int tz; @@ -32,7 +32,7 @@ static void show_dates(char **argv, const char *format) * Do not use our normal timestamp parsing here, as the point * is to test the formatting code in isolation. */ - t = strtol(arg, &arg, 10); + t = strtol(*argv, &arg, 10); while (*arg == ' ') arg++; tz = atoi(arg); @@ -41,7 +41,7 @@ static void show_dates(char **argv, const char *format) } } -static void parse_dates(char **argv, struct timeval *now) +static void parse_dates(const char **argv, struct timeval *now) { struct strbuf result = STRBUF_INIT; @@ -60,7 +60,7 @@ static void parse_dates(char **argv, struct timeval *now) strbuf_release(&result); } -static void parse_approxidate(char **argv, struct timeval *now) +static void parse_approxidate(const char **argv, struct timeval *now) { for (; *argv; argv++) { time_t t; @@ -69,7 +69,7 @@ static void parse_approxidate(char **argv, struct timeval *now) } } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct timeval now; const char *x; diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index 4595cd6433..59937dc1be 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -15,7 +15,7 @@ static const char usage_str[] = "test-delta (-d|-p) <from_file> <data_file> <out_file>"; -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { int fd; struct stat st; diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index bb53c0aa65..44f3290258 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -54,7 +54,7 @@ static int dump_cache_tree(struct cache_tree *it, return errs; } -int main(int ac, char **av) +int cmd_main(int ac, const char **av) { struct index_state istate; struct cache_tree *another = cache_tree(); diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c index 861d28c9b6..d1689248b4 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c @@ -7,7 +7,7 @@ static void show_bit(size_t pos, void *data) printf(" %d", (int)pos); } -int main(int ac, char **av) +int cmd_main(int ac, const char **av) { struct split_index *si; int i; diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index 0a1c285246..50112cc858 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -40,7 +40,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) strbuf_setlen(base, len); } -int main(int ac, char **av) +int cmd_main(int ac, const char **av) { struct untracked_cache *uc; struct strbuf base = STRBUF_INIT; diff --git a/t/helper/test-fake-ssh.c b/t/helper/test-fake-ssh.c index 980de216e1..12beee99ad 100644 --- a/t/helper/test-fake-ssh.c +++ b/t/helper/test-fake-ssh.c @@ -2,7 +2,7 @@ #include "run-command.h" #include "strbuf.h" -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { const char *trash_directory = getenv("TRASH_DIRECTORY"); struct strbuf buf = STRBUF_INIT; diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c index 54824d0754..8d11d22d98 100644 --- a/t/helper/test-genrandom.c +++ b/t/helper/test-genrandom.c @@ -6,7 +6,7 @@ #include "git-compat-util.h" -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { unsigned long count, next = 0; unsigned char *c; diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index cc2891dd97..7aa9440e27 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -138,7 +138,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) * * perfhashmap method rounds -> test hashmap.[ch] performance */ -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { char line[1024]; struct hashmap map; diff --git a/t/helper/test-index-version.c b/t/helper/test-index-version.c index 05d4699c4a..f569f6b7ef 100644 --- a/t/helper/test-index-version.c +++ b/t/helper/test-index-version.c @@ -1,6 +1,6 @@ #include "cache.h" -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct cache_header hdr; int version; diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c index 1e58f0476f..81575fe2ab 100644 --- a/t/helper/test-line-buffer.c +++ b/t/helper/test-line-buffer.c @@ -50,7 +50,7 @@ static void handle_line(const char *line, struct line_buffer *stdin_buf) handle_command(line, arg + 1, stdin_buf); } -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { struct line_buffer stdin_buf = LINE_BUFFER_INIT; struct line_buffer file_buf = LINE_BUFFER_INIT; diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index d446b8eaca..e939502863 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c @@ -1,7 +1,7 @@ #include "cache.h" #include "tree.h" -int main(int ac, char **av) +int cmd_main(int ac, const char **av) { struct object_id hash1, hash2, shifted; struct tree *one, *two; diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c index ea3b959e94..335cf6b626 100644 --- a/t/helper/test-mergesort.c +++ b/t/helper/test-mergesort.c @@ -22,7 +22,7 @@ static int compare_strings(const void *a, const void *b) return strcmp(x->text, y->text); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct line *line, *p = NULL, *lines = NULL; struct strbuf sb = STRBUF_INIT; diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c index c8c54213a3..89d9b2f7be 100644 --- a/t/helper/test-mktemp.c +++ b/t/helper/test-mktemp.c @@ -3,7 +3,7 @@ */ #include "git-compat-util.h" -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { if (argc != 2) usage("Expected 1 parameter defining the temporary file template"); diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 2c63298fab..a01430c24b 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -94,7 +94,7 @@ static void show(struct string_list *expect, int *status, const char *fmt, ...) strbuf_release(&buf); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { const char *prefix = "prefix/"; const char *usage[] = { diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index ba805b374c..1ebe0f750c 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -156,7 +156,7 @@ static struct test_data dirname_data[] = { { NULL, NULL } }; -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { char *buf = xmallocz(strlen(argv[2])); @@ -213,7 +213,7 @@ int main(int argc, char **argv) } if (argc >= 4 && !strcmp(argv[1], "prefix_path")) { - char *prefix = argv[2]; + const char *prefix = argv[2]; int prefix_len = strlen(prefix); int nongit_ok; setup_git_directory_gently(&nongit_ok); diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c index 7be72f0086..ae58fff359 100644 --- a/t/helper/test-prio-queue.c +++ b/t/helper/test-prio-queue.c @@ -16,7 +16,7 @@ static void show(int *v) free(v); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct prio_queue pq = { intcmp }; diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c index b25bcf139b..2a7990efc3 100644 --- a/t/helper/test-read-cache.c +++ b/t/helper/test-read-cache.c @@ -1,6 +1,6 @@ #include "cache.h" -int main (int argc, char **argv) +int cmd_main(int argc, const char **argv) { int i, cnt = 1; if (argc == 2) diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c index 0dc598ecdc..b5ea8a97c5 100644 --- a/t/helper/test-regex.c +++ b/t/helper/test-regex.c @@ -1,6 +1,23 @@ #include "git-compat-util.h" +#include "gettext.h" -int main(int argc, char **argv) +struct reg_flag { + const char *name; + int flag; +}; + +static struct reg_flag reg_flags[] = { + { "EXTENDED", REG_EXTENDED }, + { "NEWLINE", REG_NEWLINE }, + { "ICASE", REG_ICASE }, + { "NOTBOL", REG_NOTBOL }, +#ifdef REG_STARTEND + { "STARTEND", REG_STARTEND }, +#endif + { NULL, 0 } +}; + +static int test_regex_bug(void) { char *pat = "[^={} \t]+"; char *str = "={}\nfred"; @@ -16,5 +33,43 @@ int main(int argc, char **argv) if (m[0].rm_so == 3) /* matches '\n' when it should not */ die("regex bug confirmed: re-build git with NO_REGEX=1"); - exit(0); + return 0; +} + +int cmd_main(int argc, const char **argv) +{ + const char *pat; + const char *str; + int flags = 0; + regex_t r; + regmatch_t m[1]; + + if (argc == 2 && !strcmp(argv[1], "--bug")) + return test_regex_bug(); + else if (argc < 3) + usage("test-regex --bug\n" + "test-regex <pattern> <string> [<options>]"); + + argv++; + pat = *argv++; + str = *argv++; + while (*argv) { + struct reg_flag *rf; + for (rf = reg_flags; rf->name; rf++) + if (!strcmp(*argv, rf->name)) { + flags |= rf->flag; + break; + } + if (!rf->name) + die("do not recognize %s", *argv); + argv++; + } + git_setup_gettext(); + + if (regcomp(&r, pat, flags)) + die("failed regcomp() for pattern '%s'", pat); + if (regexec(&r, str, 1, m, 0)) + return 1; + + return 0; } diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c index 3d0313354b..b8e6fe1d00 100644 --- a/t/helper/test-revision-walking.c +++ b/t/helper/test-revision-walking.c @@ -45,7 +45,7 @@ static int run_revision_walk(void) return got_revision; } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { if (argc < 2) return 1; diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index 30a64a98dc..c71ea4f759 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -49,7 +49,7 @@ static int task_finished(int result, return 1; } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct child_process proc = CHILD_PROCESS_INIT; int jobs; diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 6efee31a48..5b2fd09908 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -5,7 +5,7 @@ static struct lock_file index_lock; -int main(int ac, char **av) +int cmd_main(int ac, const char **av) { hold_locked_index(&index_lock, 1); if (read_cache() < 0) diff --git a/t/helper/test-sha1-array.c b/t/helper/test-sha1-array.c index 60ea1d5f14..09f7790971 100644 --- a/t/helper/test-sha1-array.c +++ b/t/helper/test-sha1-array.c @@ -6,7 +6,7 @@ static void print_sha1(const unsigned char sha1[20], void *data) puts(sha1_to_hex(sha1)); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct sha1_array array = SHA1_ARRAY_INIT; struct strbuf line = STRBUF_INIT; diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c index e57eae10bf..a1c13f54ec 100644 --- a/t/helper/test-sha1.c +++ b/t/helper/test-sha1.c @@ -1,6 +1,6 @@ #include "cache.h" -int main(int ac, char **av) +int cmd_main(int ac, const char **av) { git_SHA_CTX ctx; unsigned char sha1[20]; diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c index e499fce60f..b71edbd442 100644 --- a/t/helper/test-sigchain.c +++ b/t/helper/test-sigchain.c @@ -13,7 +13,7 @@ X(two) X(three) #undef X -int main(int argc, char **argv) { +int cmd_main(int argc, const char **argv) { sigchain_push(SIGTERM, one); sigchain_push(SIGTERM, two); sigchain_push(SIGTERM, three); diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c index 14bdf9d215..4a68967bd1 100644 --- a/t/helper/test-string-list.c +++ b/t/helper/test-string-list.c @@ -41,7 +41,7 @@ static int prefix_cb(struct string_list_item *item, void *cb_data) return starts_with(item->string, prefix); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { if (argc == 5 && !strcmp(argv[1], "split")) { struct string_list list = STRING_LIST_INIT_DUP; diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index dab8c27768..61049b87a0 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -2,7 +2,7 @@ #include "submodule-config.h" #include "submodule.h" -static void die_usage(int argc, char **argv, const char *msg) +static void die_usage(int argc, const char **argv, const char *msg) { fprintf(stderr, "%s\n", msg); fprintf(stderr, "Usage: %s [<commit> <submodulepath>] ...\n", argv[0]); @@ -14,9 +14,9 @@ static int git_test_config(const char *var, const char *value, void *cb) return parse_submodule_config_option(var, value); } -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { - char **arg = argv; + const char **arg = argv; int my_argc = argc; int output_url = 0; int lookup_name = 0; @@ -49,7 +49,7 @@ int main(int argc, char **argv) path_or_name = arg[1]; if (commit[0] == '\0') - hashcpy(commit_sha1, null_sha1); + hashclr(commit_sha1); else if (get_sha1(commit, commit_sha1) < 0) die_usage(argc, argv, "Commit not found."); diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c index 56881a0324..30c5765bfc 100644 --- a/t/helper/test-subprocess.c +++ b/t/helper/test-subprocess.c @@ -1,7 +1,7 @@ #include "cache.h" #include "run-command.h" -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { struct child_process cp = CHILD_PROCESS_INIT; int nogit = 0; diff --git a/t/helper/test-svn-fe.c b/t/helper/test-svn-fe.c index 120ec96b0d..7667c0803f 100644 --- a/t/helper/test-svn-fe.c +++ b/t/helper/test-svn-fe.c @@ -11,7 +11,7 @@ static const char test_svnfe_usage[] = "test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)"; -static int apply_delta(int argc, char *argv[]) +static int apply_delta(int argc, const char **argv) { struct line_buffer preimage = LINE_BUFFER_INIT; struct line_buffer delta = LINE_BUFFER_INIT; @@ -35,7 +35,7 @@ static int apply_delta(int argc, char *argv[]) return 0; } -int main(int argc, char *argv[]) +int cmd_main(int argc, const char **argv) { if (argc == 2) { if (svndump_init(argv[1])) diff --git a/t/helper/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c index 090bf219a7..49b6e836be 100644 --- a/t/helper/test-urlmatch-normalization.c +++ b/t/helper/test-urlmatch-normalization.c @@ -1,7 +1,7 @@ #include "git-compat-util.h" #include "urlmatch.h" -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { const char usage[] = "test-urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; char *url1, *url2; diff --git a/t/helper/test-wildmatch.c b/t/helper/test-wildmatch.c index 578b164fe6..52be876fed 100644 --- a/t/helper/test-wildmatch.c +++ b/t/helper/test-wildmatch.c @@ -1,6 +1,6 @@ #include "cache.h" -int main(int argc, char **argv) +int cmd_main(int argc, const char **argv) { int i; for (i = 2; i < argc; i++) { diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index 340534c064..f9cbd47931 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -82,8 +82,7 @@ stop_git_daemon() { kill "$GIT_DAEMON_PID" wait "$GIT_DAEMON_PID" >&3 2>&4 ret=$? - # expect exit with status 143 = 128+15 for signal TERM=15 - if test $ret -ne 143 + if test_match_signal 15 $? then error "git daemon exited with status: $ret" fi diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 60811a3a7c..1aa5093f36 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -834,7 +834,7 @@ test_expect_success 'git write-tree should be able to write an empty tree' ' ' test_expect_success 'validate object ID of a known tree' ' - test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904 + test "$tree" = $EMPTY_TREE ' # Various types of objects diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh index e7f27ebbc1..46042f1f13 100755 --- a/t/t0005-signals.sh +++ b/t/t0005-signals.sh @@ -11,12 +11,13 @@ EOF test_expect_success 'sigchain works' ' { test-sigchain >actual; ret=$?; } && - case "$ret" in - 143) true ;; # POSIX w/ SIGTERM=15 - 271) true ;; # ksh w/ SIGTERM=15 - 3) true ;; # Windows - *) false ;; - esac && + { + # Signal death by raise() on Windows acts like exit(3), + # regardless of the signal number. So we must allow that + # as well as the normal signal check. + test_match_signal 15 "$ret" || + test "$ret" = 3 + } && test_cmp expect actual ' @@ -41,12 +42,12 @@ test_expect_success 'create blob' ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE' ' OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) && - test "$OUT" -eq 141 + test_match_signal 13 "$OUT" ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' ' OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) && - test "$OUT" -eq 141 + test_match_signal 13 "$OUT" ' test_done diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh index c164b4662a..d0bee08b2e 100755 --- a/t/t0025-crlf-auto.sh +++ b/t/t0025-crlf-auto.sh @@ -114,7 +114,7 @@ test_expect_success 'autocrlf=true does not normalize CRLF files' ' test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff" ' -test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' ' +test_expect_success 'text=auto, autocrlf=true does not normalize CRLF files' ' rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf true && @@ -126,7 +126,7 @@ test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' ' LFonlydiff=$(git diff LFonly) && CRLFonlydiff=$(git diff CRLFonly) && LFwithNULdiff=$(git diff LFwithNUL) && - test -z "$LFonlydiff" -a -n "$CRLFonlydiff" -a -z "$LFwithNULdiff" + test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff" ' test_expect_success 'text=auto, autocrlf=true does not normalize binary files' ' diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index 93725895a4..2860d2d08b 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -175,8 +175,8 @@ attr_ascii () { text,lf) echo "text eol=lf" ;; text,crlf) echo "text eol=crlf" ;; auto,) echo "text=auto" ;; - auto,lf) echo "text eol=lf" ;; - auto,crlf) echo "text eol=crlf" ;; + auto,lf) echo "text=auto eol=lf" ;; + auto,crlf) echo "text=auto eol=crlf" ;; lf,) echo "text eol=lf" ;; crlf,) echo "text eol=crlf" ;; ,) echo "" ;; @@ -397,10 +397,9 @@ commit_chk_wrnNNO "" "" false "" "" "" "" commit_chk_wrnNNO "" "" true LF_CRLF "" "" "" "" commit_chk_wrnNNO "" "" input "" "" "" "" "" -commit_chk_wrnNNO "auto" "" false "$WILC" "$WICL" "$WAMIX" "" "" -commit_chk_wrnNNO "auto" "" true LF_CRLF "" LF_CRLF "" "" -commit_chk_wrnNNO "auto" "" input "" CRLF_LF CRLF_LF "" "" - +commit_chk_wrnNNO "auto" "" false "$WILC" "" "" "" "" +commit_chk_wrnNNO "auto" "" true LF_CRLF "" "" "" "" +commit_chk_wrnNNO "auto" "" input "" "" "" "" "" for crlf in true false input do commit_chk_wrnNNO -text "" $crlf "" "" "" "" "" @@ -408,8 +407,8 @@ do commit_chk_wrnNNO -text crlf $crlf "" "" "" "" "" commit_chk_wrnNNO "" lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF commit_chk_wrnNNO "" crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" - commit_chk_wrnNNO auto lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF - commit_chk_wrnNNO auto crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" + commit_chk_wrnNNO auto lf $crlf "" "" "" "" "" + commit_chk_wrnNNO auto crlf $crlf LF_CRLF "" "" "" "" commit_chk_wrnNNO text lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF commit_chk_wrnNNO text crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" done @@ -454,9 +453,9 @@ do check_in_repo_NNO -text "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul check_in_repo_NNO -text lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul check_in_repo_NNO -text crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul - check_in_repo_NNO auto "" $crlf LF LF LF LF_mix_CR CRLF_nul - check_in_repo_NNO auto lf $crlf LF LF LF LF_mix_CR LF_nul - check_in_repo_NNO auto crlf $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO auto "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul check_in_repo_NNO text "" $crlf LF LF LF LF_mix_CR LF_nul check_in_repo_NNO text lf $crlf LF LF LF LF_mix_CR LF_nul check_in_repo_NNO text crlf $crlf LF LF LF LF_mix_CR LF_nul @@ -509,7 +508,7 @@ do checkout_files text "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul # currently the same as text, eol=XXX checkout_files auto "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files auto "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + checkout_files auto "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul done # core.autocrlf false, different core.eol @@ -517,7 +516,7 @@ do # core.autocrlf true checkout_files "" "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul # text: core.autocrlf = true overrides core.eol - checkout_files auto "$id" "" true "$ceol" CRLF CRLF CRLF LF_mix_CR LF_nul + checkout_files auto "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul checkout_files text "$id" "" true "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul # text: core.autocrlf = input overrides core.eol checkout_files text "$id" "" input "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul @@ -531,8 +530,8 @@ do checkout_files text "$id" "" false "" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL checkout_files text "$id" "" false native $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL # auto: core.autocrlf=false and core.eol unset(or native) uses native eol - checkout_files auto "$id" "" false "" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul - checkout_files auto "$id" "" false native $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul + checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul done # Should be the last test case: remove some files from the worktree diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh index 5ed69a6f56..991ed2a48d 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -31,7 +31,7 @@ test_expect_success 'git_mkstemps_mode does not fail if fd 0 is not open' ' test_expect_success 'check for a bug in the regex routines' ' # if this test fails, re-build git with NO_REGEX=1 - test-regex + test-regex --bug ' test_done diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index e5fa235d3a..c167f606ca 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -15,11 +15,11 @@ test_description='sparse checkout tests . "$TEST_DIRECTORY"/lib-read-tree.sh test_expect_success 'setup' ' - cat >expected <<-\EOF && + cat >expected <<-EOF && 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/addedtoo - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added + 100644 $EMPTY_BLOB 0 sub/added + 100644 $EMPTY_BLOB 0 sub/addedtoo + 100644 $EMPTY_BLOB 0 subsub/added EOF cat >expected.swt <<-\EOF && H init.t diff --git a/t/t1050-large.sh b/t/t1050-large.sh index f9f3d1391f..096dbffecc 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -177,10 +177,9 @@ test_expect_success 'zip achiving, deflate' ' git archive --format=zip HEAD >/dev/null ' -test_expect_success 'fsck' ' - test_must_fail git fsck 2>err && - n=$(grep "error: attempting to allocate .* over limit" err | wc -l) && - test "$n" -gt 1 +test_expect_success 'fsck large blobs' ' + git fsck 2>err && + test_must_be_empty err ' test_done diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh index b7e9b4fc5b..ae66ba5bab 100755 --- a/t/t1100-commit-tree-options.sh +++ b/t/t1100-commit-tree-options.sh @@ -15,7 +15,7 @@ Also make sure that command line parser understands the normal . ./test-lib.sh cat >expected <<EOF -tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 +tree $EMPTY_TREE author Author Name <author@email> 1117148400 +0000 committer Committer Name <committer@email> 1117150200 +0000 diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 75fa6548c4..d4fb977060 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -23,7 +23,7 @@ test_expect_success setup ' m=refs/heads/master n_dir=refs/heads/gu n=$n_dir/fixes -outside=foo +outside=refs/foo test_expect_success \ "create $m" \ @@ -479,7 +479,7 @@ test_expect_success 'stdin fails with duplicate refs' ' create $a $m EOF test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err + grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err ' test_expect_success 'stdin create ref works' ' @@ -880,7 +880,7 @@ test_expect_success 'stdin -z fails option with unknown name' ' test_expect_success 'stdin -z fails with duplicate refs' ' printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err + grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err ' test_expect_success 'stdin -z create ref works' ' @@ -1102,6 +1102,41 @@ test_expect_success 'stdin -z delete refs works with packed and loose refs' ' test_must_fail git rev-parse --verify -q $c ' +test_expect_success 'fails with duplicate HEAD update' ' + git branch target1 $A && + git checkout target1 && + cat >stdin <<-EOF && + update refs/heads/target1 $C + option no-deref + update HEAD $B + EOF + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: multiple updates for '\''HEAD'\'' (including one via its referent .refs/heads/target1.) are not allowed" err && + echo "refs/heads/target1" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && + echo "$A" >expect && + git rev-parse refs/heads/target1 >actual && + test_cmp expect actual +' + +test_expect_success 'fails with duplicate ref update via symref' ' + git branch target2 $A && + git symbolic-ref refs/heads/symref2 refs/heads/target2 && + cat >stdin <<-EOF && + update refs/heads/target2 $C + update refs/heads/symref2 $B + EOF + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: multiple updates for '\''refs/heads/target2'\'' (including one via symref .refs/heads/symref2.) are not allowed" err && + echo "refs/heads/target2" >expect && + git symbolic-ref refs/heads/symref2 >actual && + test_cmp expect actual && + echo "$A" >expect && + git rev-parse refs/heads/target2 >actual && + test_cmp expect actual +' + run_with_limited_open_files () { (ulimit -n 32 && "$@") } diff --git a/t/t1404-update-ref-df-conflicts.sh b/t/t1404-update-ref-df-conflicts.sh deleted file mode 100755 index 66bafb5cf4..0000000000 --- a/t/t1404-update-ref-df-conflicts.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/sh - -test_description='Test git update-ref with D/F conflicts' -. ./test-lib.sh - -test_update_rejected () { - prefix="$1" && - before="$2" && - pack="$3" && - create="$4" && - error="$5" && - printf "create $prefix/%s $C\n" $before | - git update-ref --stdin && - git for-each-ref $prefix >unchanged && - if $pack - then - git pack-refs --all - fi && - printf "create $prefix/%s $C\n" $create >input && - test_must_fail git update-ref --stdin <input 2>output.err && - grep -F "$error" output.err && - git for-each-ref $prefix >actual && - test_cmp unchanged actual -} - -Q="'" - -test_expect_success 'setup' ' - - git commit --allow-empty -m Initial && - C=$(git rev-parse HEAD) - -' - -test_expect_success 'existing loose ref is a simple prefix of new' ' - - prefix=refs/1l && - test_update_rejected $prefix "a c e" false "b c/x d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" - -' - -test_expect_success 'existing packed ref is a simple prefix of new' ' - - prefix=refs/1p && - test_update_rejected $prefix "a c e" true "b c/x d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" - -' - -test_expect_success 'existing loose ref is a deeper prefix of new' ' - - prefix=refs/2l && - test_update_rejected $prefix "a c e" false "b c/x/y d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" - -' - -test_expect_success 'existing packed ref is a deeper prefix of new' ' - - prefix=refs/2p && - test_update_rejected $prefix "a c e" true "b c/x/y d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" - -' - -test_expect_success 'new ref is a simple prefix of existing loose' ' - - prefix=refs/3l && - test_update_rejected $prefix "a c/x e" false "b c d" \ - "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'new ref is a simple prefix of existing packed' ' - - prefix=refs/3p && - test_update_rejected $prefix "a c/x e" true "b c d" \ - "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'new ref is a deeper prefix of existing loose' ' - - prefix=refs/4l && - test_update_rejected $prefix "a c/x/y e" false "b c d" \ - "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'new ref is a deeper prefix of existing packed' ' - - prefix=refs/4p && - test_update_rejected $prefix "a c/x/y e" true "b c d" \ - "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'one new ref is a simple prefix of another' ' - - prefix=refs/5 && - test_update_rejected $prefix "a e" false "b c c/x d" \ - "cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time" - -' - -test_done diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh new file mode 100755 index 0000000000..c34ece48f5 --- /dev/null +++ b/t/t1404-update-ref-errors.sh @@ -0,0 +1,407 @@ +#!/bin/sh + +test_description='Test git update-ref error handling' +. ./test-lib.sh + +# Create some references, perhaps run pack-refs --all, then try to +# create some more references. Ensure that the second creation fails +# with the correct error message. +# Usage: test_update_rejected <before> <pack> <create> <error> +# <before> is a ws-separated list of refs to create before the test +# <pack> (true or false) tells whether to pack the refs before the test +# <create> is a list of variables to attempt creating +# <error> is a string to look for in the stderr of update-ref. +# All references are created in the namespace specified by the current +# value of $prefix. +test_update_rejected () { + before="$1" && + pack="$2" && + create="$3" && + error="$4" && + printf "create $prefix/%s $C\n" $before | + git update-ref --stdin && + git for-each-ref $prefix >unchanged && + if $pack + then + git pack-refs --all + fi && + printf "create $prefix/%s $C\n" $create >input && + test_must_fail git update-ref --stdin <input 2>output.err && + grep -F "$error" output.err && + git for-each-ref $prefix >actual && + test_cmp unchanged actual +} + +Q="'" + +test_expect_success 'setup' ' + + git commit --allow-empty -m Initial && + C=$(git rev-parse HEAD) && + git commit --allow-empty -m Second && + D=$(git rev-parse HEAD) && + git commit --allow-empty -m Third && + E=$(git rev-parse HEAD) +' + +test_expect_success 'existing loose ref is a simple prefix of new' ' + + prefix=refs/1l && + test_update_rejected "a c e" false "b c/x d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" + +' + +test_expect_success 'existing packed ref is a simple prefix of new' ' + + prefix=refs/1p && + test_update_rejected "a c e" true "b c/x d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" + +' + +test_expect_success 'existing loose ref is a deeper prefix of new' ' + + prefix=refs/2l && + test_update_rejected "a c e" false "b c/x/y d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" + +' + +test_expect_success 'existing packed ref is a deeper prefix of new' ' + + prefix=refs/2p && + test_update_rejected "a c e" true "b c/x/y d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" + +' + +test_expect_success 'new ref is a simple prefix of existing loose' ' + + prefix=refs/3l && + test_update_rejected "a c/x e" false "b c d" \ + "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'new ref is a simple prefix of existing packed' ' + + prefix=refs/3p && + test_update_rejected "a c/x e" true "b c d" \ + "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'new ref is a deeper prefix of existing loose' ' + + prefix=refs/4l && + test_update_rejected "a c/x/y e" false "b c d" \ + "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'new ref is a deeper prefix of existing packed' ' + + prefix=refs/4p && + test_update_rejected "a c/x/y e" true "b c d" \ + "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'one new ref is a simple prefix of another' ' + + prefix=refs/5 && + test_update_rejected "a e" false "b c c/x d" \ + "cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time" + +' + +test_expect_success 'empty directory should not fool rev-parse' ' + prefix=refs/e-rev-parse && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + echo "$C" >expected && + git rev-parse $prefix/foo >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool for-each-ref' ' + prefix=refs/e-for-each-ref && + git update-ref $prefix/foo $C && + git for-each-ref $prefix >expected && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + git for-each-ref $prefix >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool create' ' + prefix=refs/e-create && + mkdir -p .git/$prefix/foo/bar/baz && + printf "create %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool verify' ' + prefix=refs/e-verify && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "verify %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg update' ' + prefix=refs/e-update-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 2-arg update' ' + prefix=refs/e-update-2 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 0-arg delete' ' + prefix=refs/e-delete-0 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg delete' ' + prefix=refs/e-delete-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s $C\n" $prefix/foo | + git update-ref --stdin +' + +# Test various errors when reading the old values of references... + +test_expect_success 'missing old value blocks update' ' + prefix=refs/missing-update && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/foo $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks update' ' + prefix=refs/incorrect-update && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D + EOF + printf "%s\n" "update $prefix/foo $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'existing old value blocks create' ' + prefix=refs/existing-create && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: reference already exists + EOF + printf "%s\n" "create $prefix/foo $E" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks delete' ' + prefix=refs/incorrect-delete && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D + EOF + printf "%s\n" "delete $prefix/foo $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'missing old value blocks indirect update' ' + prefix=refs/missing-indirect-update && + git symbolic-ref $prefix/symref $prefix/foo && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect update' ' + prefix=refs/incorrect-indirect-update && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'existing old value blocks indirect create' ' + prefix=refs/existing-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists + EOF + printf "%s\n" "create $prefix/symref $E" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect delete' ' + prefix=refs/incorrect-indirect-delete && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "delete $prefix/symref $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'missing old value blocks indirect no-deref update' ' + prefix=refs/missing-noderef-update && + git symbolic-ref $prefix/symref $prefix/foo && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: reference is missing but expected $D + EOF + printf "%s\n" "option no-deref" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect no-deref update' ' + prefix=refs/incorrect-noderef-update && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "option no-deref" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'existing old value blocks indirect no-deref create' ' + prefix=refs/existing-noderef-create && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists + EOF + printf "%s\n" "option no-deref" "create $prefix/symref $E" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect no-deref delete' ' + prefix=refs/incorrect-noderef-delete && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "option no-deref" "delete $prefix/symref $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'non-empty directory blocks create' ' + prefix=refs/ne-create && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks create' ' + prefix=refs/broken-create && + mkdir -p .git/$prefix && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'non-empty directory blocks indirect create' ' + prefix=refs/ne-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks indirect create' ' + prefix=refs/broken-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_done diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index 25ddab4e98..8937e25e49 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -285,7 +285,7 @@ test_expect_success 'update-ref -d cannot delete non-ref in .git dir' ' echo precious >expect && test_must_fail git update-ref -d my-private-file >output 2>error && test_must_be_empty output && - test_i18ngrep -e "cannot lock .*: unable to resolve reference" error && + test_i18ngrep -e "refusing to update ref with bad name" error && test_cmp expect .git/my-private-file ' diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 7ee8ea004f..8f52da2771 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -523,4 +523,26 @@ test_expect_success 'fsck --connectivity-only' ' ) ' +remove_loose_object () { + sha1="$(git rev-parse "$1")" && + remainder=${sha1#??} && + firsttwo=${sha1%$remainder} && + rm .git/objects/$firsttwo/$remainder +} + +test_expect_success 'fsck --name-objects' ' + rm -rf name-objects && + git init name-objects && + ( + cd name-objects && + test_commit julius caesar.t && + test_commit augustus && + test_commit caesar && + remove_loose_object $(git rev-parse julius:caesar.t) && + test_must_fail git fsck --name-objects >out && + tree=$(git rev-parse --verify julius:) && + grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out + ) +' + test_done diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index 8aef49f236..292a0720fc 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -33,14 +33,14 @@ test_expect_success 'add one file' ' git update-index --add one && git ls-files --stage >ls-files.actual && cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 $EMPTY_BLOB 0 one EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && cat >expect <<EOF && base $base -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 $EMPTY_BLOB 0 one replacements: deletions: EOF @@ -51,7 +51,7 @@ test_expect_success 'disable split index' ' git update-index --no-split-index && git ls-files --stage >ls-files.actual && cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 $EMPTY_BLOB 0 one EOF test_cmp ls-files.expect ls-files.actual && @@ -67,7 +67,7 @@ test_expect_success 'enable split index again, "one" now belongs to base index"' git update-index --split-index && git ls-files --stage >ls-files.actual && cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 $EMPTY_BLOB 0 one EOF test_cmp ls-files.expect ls-files.actual && @@ -105,7 +105,7 @@ test_expect_success 'add another file, which stays index' ' git ls-files --stage >ls-files.actual && cat >ls-files.expect <<EOF && 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +100644 $EMPTY_BLOB 0 two EOF test_cmp ls-files.expect ls-files.actual && @@ -113,7 +113,7 @@ EOF q_to_tab >expect <<EOF && $BASE 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +100644 $EMPTY_BLOB 0 two replacements: 0 deletions: EOF @@ -159,14 +159,14 @@ test_expect_success 'add original file back' ' git update-index --add one && git ls-files --stage >ls-files.actual && cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 $EMPTY_BLOB 0 one EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && cat >expect <<EOF && $BASE -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 $EMPTY_BLOB 0 one replacements: deletions: 0 EOF @@ -178,8 +178,8 @@ test_expect_success 'add new file' ' git update-index --add two && git ls-files --stage >actual && cat >expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +100644 $EMPTY_BLOB 0 one +100644 $EMPTY_BLOB 0 two EOF test_cmp expect actual ' @@ -188,8 +188,8 @@ test_expect_success 'unify index, two files remain' ' git update-index --no-split-index && git ls-files --stage >ls-files.actual && cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +100644 $EMPTY_BLOB 0 one +100644 $EMPTY_BLOB 0 two EOF test_cmp ls-files.expect ls-files.actual && diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh new file mode 100755 index 0000000000..8298aaf97f --- /dev/null +++ b/t/t2028-worktree-move.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +test_description='test git worktree move, remove, lock and unlock' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git worktree add source && + git worktree list --porcelain | grep "^worktree" >actual && + cat <<-EOF >expected && + worktree $(pwd) + worktree $(pwd)/source + EOF + test_cmp expected actual +' + +test_expect_success 'lock main worktree' ' + test_must_fail git worktree lock . +' + +test_expect_success 'lock linked worktree' ' + git worktree lock --reason hahaha source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock linked worktree from another worktree' ' + rm .git/worktrees/source/locked && + git worktree add elsewhere && + git -C elsewhere worktree lock --reason hahaha ../source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock worktree twice' ' + test_must_fail git worktree lock source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock worktree twice (from the locked worktree)' ' + test_must_fail git -C source worktree lock . && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_done diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 2a4a749b4f..8f22c43e24 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -82,5 +82,36 @@ test_expect_success 'cache-tree invalidates i-t-a paths' ' test_cmp expect actual ' +test_expect_success 'cache-tree does not ignore dir that has i-t-a entries' ' + git init ita-in-dir && + ( + cd ita-in-dir && + mkdir 2 && + for f in 1 2/1 2/2 3 + do + echo "$f" >"$f" + done && + git add 1 2/2 3 && + git add -N 2/1 && + git commit -m committed && + git ls-tree -r HEAD >actual && + grep 2/2 actual + ) +' + +test_expect_success 'cache-tree does skip dir that becomes empty' ' + rm -fr ita-in-dir && + git init ita-in-dir && + ( + cd ita-in-dir && + mkdir -p 1/2/3 && + echo 4 >1/2/3/4 && + git add -N 1/2/3/4 && + git write-tree >actual && + echo $EMPTY_TREE >expected && + test_cmp expected actual + ) +' + test_done diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh index 4d4b02e760..e804377f1c 100755 --- a/t/t3102-ls-tree-wildcards.sh +++ b/t/t3102-ls-tree-wildcards.sh @@ -12,16 +12,16 @@ test_expect_success 'setup' ' ' test_expect_success 'ls-tree a[a] matches literally' ' - cat >expect <<-\EOF && - 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three + cat >expect <<-EOF && + 100644 blob $EMPTY_BLOB a[a]/three EOF git ls-tree -r HEAD "a[a]" >actual && test_cmp expect actual ' test_expect_success 'ls-tree outside prefix' ' - cat >expect <<-\EOF && - 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ../a[a]/three + cat >expect <<-EOF && + 100644 blob $EMPTY_BLOB ../a[a]/three EOF ( cd aa && git ls-tree -r HEAD "../a[a]"; ) >actual && test_cmp expect actual diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ac9c764799..8a833f354e 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -79,6 +79,15 @@ test_expect_success 'git branch -m dumps usage' ' test_i18ngrep "branch name required" err ' +test_expect_success 'git branch -m m broken_symref should work' ' + test_when_finished "git branch -D broken_symref" && + git branch -l m && + git symbolic-ref refs/heads/broken_symref refs/heads/i_am_broken && + git branch -m m broken_symref && + git reflog exists refs/heads/broken_symref && + test_must_fail git reflog exists refs/heads/i_am_broken +' + test_expect_success 'git branch -m m m/m should work' ' git branch -l m && git branch -m m m/m && diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3532c482fc..197914bbd8 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1281,4 +1281,12 @@ test_expect_success 'editor saves as CR/LF' ' ) ' +SQ="'" +test_expect_success 'rebase -i --gpg-sign=<key-id>' ' + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \ + >out 2>err && + grep "$SQ-S\"S I Gner\"$SQ" err +' + test_done diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 8f53e54ce4..48346f1cc0 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -271,4 +271,37 @@ test_expect_success 'autosquash with custom inst format' ' test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l) ' +set_backup_editor () { + write_script backup-editor.sh <<-\EOF + cp "$1" .git/backup-"$(basename "$1")" + EOF + test_set_editor "$PWD/backup-editor.sh" +} + +test_expect_failure 'autosquash with multiple empty patches' ' + test_tick && + git commit --allow-empty -m "empty" && + test_tick && + git commit --allow-empty -m "empty2" && + test_tick && + >fixup && + git add fixup && + git commit --fixup HEAD^^ && + ( + set_backup_editor && + GIT_USE_REBASE_HELPER=false \ + git rebase -i --force-rebase --autosquash HEAD~4 && + grep empty2 .git/backup-git-rebase-todo + ) +' + +test_expect_success 'extra spaces after fixup!' ' + base=$(git rev-parse HEAD) && + test_commit to-fixup && + git commit --allow-empty -m "fixup! to-fixup" && + git rebase -i --autosquash --keep-empty HEAD~2 && + parent=$(git rev-parse HEAD^) && + test $base = $parent +' + test_done diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 43c488b545..35b35a81c8 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -78,8 +78,6 @@ test_expect_success 'diff-tree pathspec' ' test_cmp expected current ' -EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 - test_expect_success 'diff-tree with wildcard shows dir also matches' ' git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result && echo file0 >expected && diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh index 3c9932edf3..113304dc59 100755 --- a/t/t4033-diff-patience.sh +++ b/t/t4033-diff-patience.sh @@ -5,6 +5,14 @@ test_description='patience diff algorithm' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff-alternative.sh +test_expect_success '--ignore-space-at-eol with a single appended character' ' + printf "a\nb\nc\n" >pre && + printf "a\nbX\nc\n" >post && + test_must_fail git diff --no-index \ + --patience --ignore-space-at-eol pre post >diff && + grep "^+.*X" diff +' + test_diff_frobnitz "patience" test_diff_unique "patience" diff --git a/t/t4054-diff-bogus-tree.sh b/t/t4054-diff-bogus-tree.sh index 1d6efab3c5..18f42c5fff 100755 --- a/t/t4054-diff-bogus-tree.sh +++ b/t/t4054-diff-bogus-tree.sh @@ -3,8 +3,6 @@ test_description='test diff with a bogus tree containing the null sha1' . ./test-lib.sh -empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 - test_expect_success 'create bogus tree' ' bogus_tree=$( printf "100644 fooQQQQQQQQQQQQQQQQQQQQQ" | @@ -22,13 +20,13 @@ test_expect_success 'create tree with matching file' ' test_expect_success 'raw diff shows null sha1 (addition)' ' echo ":000000 100644 $_z40 $_z40 A foo" >expect && - git diff-tree $empty_tree $bogus_tree >actual && + git diff-tree $EMPTY_TREE $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (removal)' ' echo ":100644 000000 $_z40 $_z40 D foo" >expect && - git diff-tree $bogus_tree $empty_tree >actual && + git diff-tree $bogus_tree $EMPTY_TREE >actual && test_cmp expect actual ' @@ -57,11 +55,11 @@ test_expect_success 'raw diff shows null sha1 (index)' ' ' test_expect_success 'patch fails due to bogus sha1 (addition)' ' - test_must_fail git diff-tree -p $empty_tree $bogus_tree + test_must_fail git diff-tree -p $EMPTY_TREE $bogus_tree ' test_expect_success 'patch fails due to bogus sha1 (removal)' ' - test_must_fail git diff-tree -p $bogus_tree $empty_tree + test_must_fail git diff-tree -p $bogus_tree $EMPTY_TREE ' test_expect_success 'patch fails due to bogus sha1 (modification)' ' diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index a9773658f0..ae08b57712 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -184,4 +184,10 @@ test_expect_success 'shortlog with revision pseudo options' ' git shortlog --exclude=refs/heads/m* --all ' +test_expect_success 'shortlog with --output=<file>' ' + git shortlog --output=shortlog -1 master >output && + test ! -s output && + test_line_count = 3 shortlog +' + test_done diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index 4451127eb2..9d87777b59 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -99,4 +99,11 @@ test_expect_success '-L with --first-parent and a merge' ' git log --first-parent -L 1,1:b.c ' +test_expect_success '-L with --output' ' + git checkout parallel-change && + git log --output=log -L :main:b.c >output && + test ! -s output && + test_line_count = 70 log +' + test_done diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 44f3d5fb28..9b19cff729 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -115,8 +115,8 @@ test_expect_success 'push with transfer.fsckobjects' ' test_cmp exp act ' -cat >bogus-commit <<\EOF -tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 +cat >bogus-commit <<EOF +tree $EMPTY_TREE author Bugs Bunny 1234567890 +0000 committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000 diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 88076da1e6..6bd4853079 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -688,4 +688,34 @@ test_expect_success 'fetching with auto-gc does not lock up' ' ) ' +test_expect_success 'fetch aligned output' ' + git clone . full-output && + test_commit looooooooooooong-tag && + ( + cd full-output && + git -c fetch.output=full fetch origin 2>&1 | \ + grep -e "->" | cut -c 22- >../actual + ) && + cat >expect <<-\EOF && + master -> origin/master + looooooooooooong-tag -> looooooooooooong-tag + EOF + test_cmp expect actual +' + +test_expect_success 'fetch compact output' ' + git clone . compact && + test_commit extraaa && + ( + cd compact && + git -c fetch.output=compact fetch origin 2>&1 | \ + grep -e "->" | cut -c 22- >../actual + ) && + cat >expect <<-\EOF && + master -> origin/* + extraaa -> * + EOF + test_cmp expect actual +' + test_done diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index ca6becfe37..4840c71f02 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -368,5 +368,14 @@ test_expect_success GPG 'push with post-receive to inspect certificate' ' test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status" ' +test_expect_success 'push status output scrubs password' ' + cd "$ROOT_PATH/test_repo_clone" && + git push --porcelain \ + "$HTTPD_URL_USER_PASS/smart/test_repo.git" \ + +HEAD:scrub >status && + # should have been scrubbed down to vanilla URL + grep "^To $HTTPD_URL/smart/test_repo.git" status +' + stop_httpd test_done diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh index 85c10b0940..5e8d5fa50c 100755 --- a/t/t6038-merge-text-auto.sh +++ b/t/t6038-merge-text-auto.sh @@ -16,6 +16,13 @@ test_description='CRLF merge conflict across text=auto change test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b +compare_files () { + tr '\015\000' QN <"$1" >"$1".expect && + tr '\015\000' QN <"$2" >"$2".actual && + test_cmp "$1".expect "$2".actual && + rm "$1".expect "$2".actual +} + test_expect_success setup ' git config core.autocrlf false && @@ -30,7 +37,7 @@ test_expect_success setup ' git branch side && echo "* text=auto" >.gitattributes && - touch file && + echo first line >file && git add .gitattributes file && test_tick && git commit -m "normalize file" && @@ -81,38 +88,49 @@ test_expect_success 'Merge after setting text=auto' ' rm -f .gitattributes && git reset --hard a && git merge b && - test_cmp expected file + compare_files expected file ' -test_expect_success 'Merge addition of text=auto' ' +test_expect_success 'Merge addition of text=auto eol=LF' ' + git config core.eol lf && cat <<-\EOF >expected && first line same line EOF - if test_have_prereq NATIVE_CRLF; then - append_cr <expected >expected.temp && - mv expected.temp expected - fi && git config merge.renormalize true && git rm -fr . && rm -f .gitattributes && git reset --hard b && git merge a && - test_cmp expected file + compare_files expected file +' + +test_expect_success 'Merge addition of text=auto eol=CRLF' ' + git config core.eol crlf && + cat <<-\EOF >expected && + first line + same line + EOF + + append_cr <expected >expected.temp && + mv expected.temp expected && + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard b && + echo >&2 "After git reset --hard b" && + git ls-files -s --eol >&2 && + git merge a && + compare_files expected file ' test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' + git config core.eol native && echo "<<<<<<<" >expected && - if test_have_prereq NATIVE_CRLF; then - echo first line | append_cr >>expected && - echo same line | append_cr >>expected && - echo ======= | append_cr >>expected - else - echo first line >>expected && - echo same line >>expected && - echo ======= >>expected - fi && + echo first line >>expected && + echo same line >>expected && + echo ======= >>expected && echo first line | append_cr >>expected && echo same line | append_cr >>expected && echo ">>>>>>>" >>expected && @@ -121,29 +139,23 @@ test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' git reset --hard a && test_must_fail git merge b && fuzz_conflict file >file.fuzzy && - test_cmp expected file.fuzzy + compare_files expected file.fuzzy ' test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' ' echo "<<<<<<<" >expected && echo first line | append_cr >>expected && echo same line | append_cr >>expected && - if test_have_prereq NATIVE_CRLF; then - echo ======= | append_cr >>expected && - echo first line | append_cr >>expected && - echo same line | append_cr >>expected - else - echo ======= >>expected && - echo first line >>expected && - echo same line >>expected - fi && + echo ======= >>expected && + echo first line >>expected && + echo same line >>expected && echo ">>>>>>>" >>expected && git config merge.renormalize false && rm -f .gitattributes && git reset --hard b && test_must_fail git merge a && fuzz_conflict file >file.fuzzy && - test_cmp expected file.fuzzy + compare_files expected file.fuzzy ' test_expect_failure 'checkout -m after setting text=auto' ' @@ -158,7 +170,7 @@ test_expect_failure 'checkout -m after setting text=auto' ' git reset --hard initial && git checkout a -- . && git checkout -m b && - test_cmp expected file + compare_files expected file ' test_expect_failure 'checkout -m addition of text=auto' ' @@ -173,7 +185,7 @@ test_expect_failure 'checkout -m addition of text=auto' ' git reset --hard initial && git checkout b -- . && git checkout -m a && - test_cmp expected file + compare_files expected file ' test_expect_failure 'cherry-pick patch from after text=auto was added' ' @@ -187,7 +199,7 @@ test_expect_failure 'cherry-pick patch from after text=auto was added' ' git reset --hard b && test_must_fail git cherry-pick a >err 2>&1 && grep "[Nn]othing added" err && - test_cmp expected file + compare_files expected file ' test_expect_success 'Test delete/normalize conflict' ' diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index 88d60c1ce2..84f41451ec 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -23,17 +23,15 @@ S sub/1 H sub/2 EOF -NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 - setup_absent() { test -f 1 && rm 1 git update-index --remove 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_absent() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && test ! -f 1 @@ -42,12 +40,12 @@ test_absent() { setup_dirty() { git update-index --force-remove 1 && echo dirty > 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_dirty() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && echo dirty > expected @@ -120,7 +118,7 @@ test_expect_success 'grep with skip-worktree file' ' test "$(git grep --no-ext-grep test)" = "1:test" ' -echo ":000000 100644 $_z40 $NULL_SHA1 A 1" > expected +echo ":000000 100644 $_z40 $EMPTY_BLOB A 1" > expected test_expect_success 'diff-index does not examine skip-worktree absent entries' ' setup_absent && git diff-index HEAD -- 1 > result && diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index 9ceaa4049f..9d1abe50ef 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -53,17 +53,15 @@ test_expect_success 'read-tree removes worktree, dirty case' ' git update-index --no-skip-worktree added ' -NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 - setup_absent() { test -f 1 && rm 1 git update-index --remove 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_absent() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && test ! -f 1 @@ -72,12 +70,12 @@ test_absent() { setup_dirty() { git update-index --force-remove 1 && echo dirty > 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_dirty() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && echo dirty > expected diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index 38b3890532..c23a21cb74 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -53,7 +53,7 @@ A two EOF cat >../dump.expect <<EOF && -info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 @@ -137,7 +137,7 @@ EOF test_expect_success 'verify untracked cache dump' ' test-dump-untracked-cache >../actual && cat >../expect <<EOF && -info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 @@ -184,7 +184,7 @@ EOF test_expect_success 'verify untracked cache dump' ' test-dump-untracked-cache >../actual && cat >../expect <<EOF && -info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 diff --git a/t/t7508-status.sh b/t/t7508-status.sh index b3bdd162aa..fb00e6d9b0 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -803,7 +803,7 @@ EOF ' cat >expect <<EOF -:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M dir1/modified +:100644 100644 $EMPTY_BLOB 0000000000000000000000000000000000000000 M dir1/modified EOF test_expect_success 'status refreshes the index' ' touch dir2/added && diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 76306cf268..7217f3968d 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -589,7 +589,12 @@ test_expect_success 'filenames seen by tools start with ./' ' git reset --hard master >/dev/null 2>&1 ' -test_expect_success 'temporary filenames are used with mergetool.writeToTemp' ' +test_lazy_prereq MKTEMP ' + tempdir=$(mktemp -d -t foo.XXXXXX) && + test -d "$tempdir" +' + +test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' git checkout -b test16 branch1 && test_config mergetool.writeToTemp true && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh new file mode 100755 index 0000000000..169fd8d706 --- /dev/null +++ b/t/t7812-grep-icase-non-ascii.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +test_description='grep icase on non-English locales' + +. ./lib-gettext.sh + +test_expect_success GETTEXT_LOCALE 'setup' ' + test_write_lines "TILRAUN: Halló Heimur!" >file && + git add file && + LC_ALL="$is_IS_locale" && + export LC_ALL +' + +test_have_prereq GETTEXT_LOCALE && +test-regex "HALLÓ" "Halló" ICASE && +test_set_prereq REGEX_LOCALE + +test_expect_success REGEX_LOCALE 'grep literal string, no -F' ' + git grep -i "TILRAUN: Halló Heimur!" && + git grep -i "TILRAUN: HALLÓ HEIMUR!" +' + +test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 icase' ' + git grep --perl-regexp "TILRAUN: H.lló Heimur!" && + git grep --perl-regexp -i "TILRAUN: H.lló Heimur!" && + git grep --perl-regexp -i "TILRAUN: H.LLÓ HEIMUR!" +' + +test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 string with "+"' ' + test_write_lines "TILRAUN: Hallóó Heimur!" >file2 && + git add file2 && + git grep -l --perl-regexp "TILRAUN: H.lló+ Heimur!" >actual && + echo file >expected && + echo file2 >>expected && + test_cmp expected actual +' + +test_expect_success REGEX_LOCALE 'grep literal string, with -F' ' + git grep --debug -i -F "TILRAUN: Halló Heimur!" 2>&1 >/dev/null | + grep fixed >debug1 && + test_write_lines "fixed TILRAUN: Halló Heimur!" >expect1 && + test_cmp expect1 debug1 && + + git grep --debug -i -F "TILRAUN: HALLÓ HEIMUR!" 2>&1 >/dev/null | + grep fixed >debug2 && + test_write_lines "fixed TILRAUN: HALLÓ HEIMUR!" >expect2 && + test_cmp expect2 debug2 +' + +test_expect_success REGEX_LOCALE 'grep string with regex, with -F' ' + test_write_lines "^*TILR^AUN:.* \\Halló \$He[]imur!\$" >file && + + git grep --debug -i -F "^*TILR^AUN:.* \\Halló \$He[]imur!\$" 2>&1 >/dev/null | + grep fixed >debug1 && + test_write_lines "fixed \\^*TILR^AUN:\\.\\* \\\\Halló \$He\\[]imur!\\\$" >expect1 && + test_cmp expect1 debug1 && + + git grep --debug -i -F "^*TILR^AUN:.* \\HALLÓ \$HE[]IMUR!\$" 2>&1 >/dev/null | + grep fixed >debug2 && + test_write_lines "fixed \\^*TILR^AUN:\\.\\* \\\\HALLÓ \$HE\\[]IMUR!\\\$" >expect2 && + test_cmp expect2 debug2 +' + +test_expect_success REGEX_LOCALE 'pickaxe -i on non-ascii' ' + git commit -m first && + git log --format=%f -i -S"TILRAUN: HALLÓ HEIMUR!" >actual && + echo first >expected && + test_cmp expected actual +' + +test_done diff --git a/t/t7813-grep-icase-iso.sh b/t/t7813-grep-icase-iso.sh new file mode 100755 index 0000000000..efef7fb81f --- /dev/null +++ b/t/t7813-grep-icase-iso.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='grep icase on non-English locales' + +. ./lib-gettext.sh + +test_expect_success GETTEXT_ISO_LOCALE 'setup' ' + printf "TILRAUN: Halló Heimur!" >file && + git add file && + LC_ALL="$is_IS_iso_locale" && + export LC_ALL +' + +test_expect_success GETTEXT_ISO_LOCALE,LIBPCRE 'grep pcre string' ' + git grep --perl-regexp -i "TILRAUN: H.lló Heimur!" && + git grep --perl-regexp -i "TILRAUN: H.LLÓ HEIMUR!" +' + +test_done diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index a9b266f0d3..e48370dfa0 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -41,12 +41,12 @@ test_expect_success setup ' test_tick && GIT_AUTHOR_NAME=Fourth git commit -m Fourth && - { - echo ABC - echo DEF - echo XXXX - echo GHIJK - } >cow && + cat >cow <<-\EOF && + ABC + DEF + XXXX + GHIJK + EOF git add cow && test_tick && GIT_AUTHOR_NAME=Fifth git commit -m Fifth @@ -115,11 +115,11 @@ test_expect_success 'append with -C -C -C' ' test_expect_success 'blame wholesale copy' ' git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current && - { - echo mouse-Initial - echo mouse-Second - echo mouse-Third - } >expected && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + mouse-Third + EOF test_cmp expected current ' @@ -127,16 +127,61 @@ test_expect_success 'blame wholesale copy' ' test_expect_success 'blame wholesale copy and more' ' git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current && - { - echo mouse-Initial - echo mouse-Second - echo cow-Fifth - echo mouse-Third - } >expected && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + cow-Fifth + mouse-Third + EOF test_cmp expected current ' +test_expect_success 'blame wholesale copy and more in the index' ' + + cat >horse <<-\EOF && + ABC + DEF + XXXX + YYYY + GHIJK + EOF + git add horse && + test_when_finished "git rm -f horse" && + git blame -f -C -C1 -- horse | sed -e "$pick_fc" >current && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + cow-Fifth + horse-Not + mouse-Third + EOF + test_cmp expected current + +' + +test_expect_success 'blame during cherry-pick with file rename conflict' ' + + test_when_finished "git reset --hard && git checkout master" && + git checkout HEAD~3 && + echo MOUSE >> mouse && + git mv mouse rodent && + git add rodent && + GIT_AUTHOR_NAME=Rodent git commit -m "rodent" && + git checkout --detach master && + (git cherry-pick HEAD@{1} || test $? -eq 1) && + git show HEAD@{1}:rodent > rodent && + git add rodent && + git blame -f -C -C1 rodent | sed -e "$pick_fc" >current && + cat current && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + rodent-Not + EOF + test_cmp expected current +' + test_expect_success 'blame path that used to be a directory' ' mkdir path && echo A A A A A >path/file && diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh index 0aafd03334..6a86d6996b 100755 --- a/t/t9801-git-p4-branch.sh +++ b/t/t9801-git-p4-branch.sh @@ -300,7 +300,7 @@ test_expect_success 'git p4 clone complex branches' ' test_path_is_file file2 && test_path_is_file file3 && ! grep update file2 && - test_path_is_missing .git/git-p4-tmp + test_must_fail git show-ref --verify refs/git-p4-tmp ) ' @@ -352,7 +352,7 @@ test_expect_success 'git p4 sync changes to two branches in the same changelist' test_path_is_file file2 && test_path_is_file file3 && ! grep update file2 && - test_path_is_missing .git/git-p4-tmp + test_must_fail git show-ref --verify refs/git-p4-tmp ) ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 90856d67e5..4f7eadb596 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -612,7 +612,7 @@ test_must_fail () { then echo >&2 "test_must_fail: command succeeded: $*" return 1 - elif test $exit_code -eq 141 && list_contains "$_test_ok" sigpipe + elif test_match_signal 13 $exit_code && list_contains "$_test_ok" sigpipe then return 0 elif test $exit_code -gt 129 && test $exit_code -le 192 @@ -962,6 +962,21 @@ test_env () { ) } +# Returns true if the numeric exit code in "$2" represents the expected signal +# in "$1". Signals should be given numerically. +test_match_signal () { + if test "$2" = "$((128 + $1))" + then + # POSIX + return 0 + elif test "$2" = "$((256 + $1))" + then + # ksh + return 0 + fi + return 1 +} + # Read up to "$1" bytes (or to EOF) from stdin and write them to stdout. test_copy_bytes () { perl -e ' diff --git a/t/test-lib.sh b/t/test-lib.sh index 11201e9cf8..d731d66e36 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -162,6 +162,9 @@ _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" # Zero SHA-1 _z40=0000000000000000000000000000000000000000 +EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 + # Line feed LF=' ' @@ -170,7 +173,7 @@ LF=' # when case-folding filenames u200c=$(printf '\342\200\214') -export _x05 _x40 _z40 LF u200c +export _x05 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB # Each test should start with something like this, after copyright notices: # @@ -798,7 +801,7 @@ then # override all git executables in TEST_DIRECTORY/.. GIT_VALGRIND=$TEST_DIRECTORY/valgrind mkdir -p "$GIT_VALGRIND"/bin - for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-* + for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/t/helper/test-* do make_valgrind_symlink $file done diff --git a/transport.c b/transport.c index 59b911e5ff..b233e3ee5e 100644 --- a/transport.c +++ b/transport.c @@ -359,8 +359,11 @@ static void print_ok_ref_status(struct ref *ref, int porcelain) static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain) { - if (!count) - fprintf(porcelain ? stdout : stderr, "To %s\n", dest); + if (!count) { + char *url = transport_anonymize_url(dest); + fprintf(porcelain ? stdout : stderr, "To %s\n", url); + free(url); + } switch(ref->status) { case REF_STATUS_NONE: diff --git a/upload-pack.c b/upload-pack.c index f93787003a..d4cc414bc2 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -828,7 +828,7 @@ static int upload_pack_config(const char *var, const char *value, void *unused) return parse_hide_refs_config(var, value, "uploadpack"); } -int main(int argc, const char **argv) +int cmd_main(int argc, const char **argv) { const char *dir; int strict = 0; @@ -844,10 +844,7 @@ int main(int argc, const char **argv) OPT_END() }; - git_setup_gettext(); - packet_trace_identity("upload-pack"); - git_extract_argv0_path(argv[0]); check_replace_refs = 0; argc = parse_options(argc, argv, NULL, options, upload_pack_usage, 0); @@ -9,10 +9,14 @@ static unsigned char current_commit_sha1[20]; -void walker_say(struct walker *walker, const char *fmt, const char *hex) +void walker_say(struct walker *walker, const char *fmt, ...) { - if (walker->get_verbosely) - fprintf(stderr, fmt, hex); + if (walker->get_verbosely) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } } static void report_missing(const struct object *obj) @@ -19,7 +19,8 @@ struct walker { }; /* Report what we got under get_verbosely */ -void walker_say(struct walker *walker, const char *, const char *); +__attribute__((format (printf, 2, 3))) +void walker_say(struct walker *walker, const char *fmt, ...); /* Load pull targets from stdin */ int walker_targets_stdin(char ***target, const char ***write_ref); diff --git a/worktree.c b/worktree.c index e2a94e0476..5acfe4cd64 100644 --- a/worktree.c +++ b/worktree.c @@ -13,6 +13,7 @@ void free_worktrees(struct worktree **worktrees) free(worktrees[i]->path); free(worktrees[i]->id); free(worktrees[i]->head_ref); + free(worktrees[i]->lock_reason); free(worktrees[i]); } free (worktrees); @@ -80,7 +81,7 @@ static struct worktree *get_main_worktree(void) int is_bare = 0; int is_detached = 0; - strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir())); + strbuf_add_absolute_path(&worktree_path, get_git_common_dir()); is_bare = !strbuf_strip_suffix(&worktree_path, "/.git"); if (is_bare) strbuf_strip_suffix(&worktree_path, "/."); @@ -98,6 +99,8 @@ static struct worktree *get_main_worktree(void) worktree->is_detached = is_detached; worktree->is_current = 0; add_head_info(&head_ref, worktree); + worktree->lock_reason = NULL; + worktree->lock_reason_valid = 0; done: strbuf_release(&path); @@ -125,7 +128,7 @@ static struct worktree *get_linked_worktree(const char *id) strbuf_rtrim(&worktree_path); if (!strbuf_strip_suffix(&worktree_path, "/.git")) { strbuf_reset(&worktree_path); - strbuf_addstr(&worktree_path, absolute_path(".")); + strbuf_add_absolute_path(&worktree_path, "."); strbuf_strip_suffix(&worktree_path, "/."); } @@ -143,6 +146,8 @@ static struct worktree *get_linked_worktree(const char *id) worktree->is_detached = is_detached; worktree->is_current = 0; add_head_info(&head_ref, worktree); + worktree->lock_reason = NULL; + worktree->lock_reason_valid = 0; done: strbuf_release(&path); @@ -214,6 +219,78 @@ const char *get_worktree_git_dir(const struct worktree *wt) return git_common_path("worktrees/%s", wt->id); } +static struct worktree *find_worktree_by_suffix(struct worktree **list, + const char *suffix) +{ + struct worktree *found = NULL; + int nr_found = 0, suffixlen; + + suffixlen = strlen(suffix); + if (!suffixlen) + return NULL; + + for (; *list && nr_found < 2; list++) { + const char *path = (*list)->path; + int pathlen = strlen(path); + int start = pathlen - suffixlen; + + /* suffix must start at directory boundary */ + if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) && + !fspathcmp(suffix, path + start)) { + found = *list; + nr_found++; + } + } + return nr_found == 1 ? found : NULL; +} + +struct worktree *find_worktree(struct worktree **list, + const char *prefix, + const char *arg) +{ + struct worktree *wt; + char *path; + + if ((wt = find_worktree_by_suffix(list, arg))) + return wt; + + arg = prefix_filename(prefix, strlen(prefix), arg); + path = xstrdup(real_path(arg)); + for (; *list; list++) + if (!fspathcmp(path, real_path((*list)->path))) + break; + free(path); + return *list; +} + +int is_main_worktree(const struct worktree *wt) +{ + return !wt->id; +} + +const char *is_worktree_locked(struct worktree *wt) +{ + assert(!is_main_worktree(wt)); + + if (!wt->lock_reason_valid) { + struct strbuf path = STRBUF_INIT; + + strbuf_addstr(&path, worktree_git_path(wt, "locked")); + if (file_exists(path.buf)) { + struct strbuf lock_reason = STRBUF_INIT; + if (strbuf_read_file(&lock_reason, path.buf, 0) < 0) + die_errno(_("failed to read '%s'"), path.buf); + strbuf_trim(&lock_reason); + wt->lock_reason = strbuf_detach(&lock_reason, NULL); + } else + wt->lock_reason = NULL; + wt->lock_reason_valid = 1; + strbuf_release(&path); + } + + return wt->lock_reason; +} + int is_worktree_being_rebased(const struct worktree *wt, const char *target) { diff --git a/worktree.h b/worktree.h index 13949093cc..90e1311fa7 100644 --- a/worktree.h +++ b/worktree.h @@ -5,10 +5,12 @@ struct worktree { char *path; char *id; char *head_ref; + char *lock_reason; /* internal use */ unsigned char head_sha1[20]; int is_detached; int is_bare; int is_current; + int lock_reason_valid; }; /* Functions for acting on the information about worktrees. */ @@ -30,6 +32,25 @@ extern struct worktree **get_worktrees(void); extern const char *get_worktree_git_dir(const struct worktree *wt); /* + * Search a worktree that can be unambiguously identified by + * "arg". "prefix" must not be NULL. + */ +extern struct worktree *find_worktree(struct worktree **list, + const char *prefix, + const char *arg); + +/* + * Return true if the given worktree is the main one. + */ +extern int is_main_worktree(const struct worktree *wt); + +/* + * Return the reason string if the given worktree is locked or NULL + * otherwise. + */ +extern const char *is_worktree_locked(struct worktree *wt); + +/* * Free up the memory for worktree(s) */ extern void free_worktrees(struct worktree **); @@ -227,6 +227,24 @@ int xopen(const char *path, int oflag, ...) } } +static int handle_nonblock(int fd, short poll_events, int err) +{ + struct pollfd pfd; + + if (err != EAGAIN && err != EWOULDBLOCK) + return 0; + + pfd.fd = fd; + pfd.events = poll_events; + + /* + * no need to check for errors, here; + * a subsequent read/write will detect unrecoverable errors + */ + poll(&pfd, 1, -1); + return 1; +} + /* * xread() is the same a read(), but it automatically restarts read() * operations with a recoverable error (EAGAIN and EINTR). xread() @@ -242,20 +260,8 @@ ssize_t xread(int fd, void *buf, size_t len) if (nr < 0) { if (errno == EINTR) continue; - if (errno == EAGAIN || errno == EWOULDBLOCK) { - struct pollfd pfd; - pfd.events = POLLIN; - pfd.fd = fd; - /* - * it is OK if this poll() failed; we - * want to leave this infinite loop - * only when read() returns with - * success, or an expected failure, - * which would be checked by the next - * call to read(2). - */ - poll(&pfd, 1, -1); - } + if (handle_nonblock(fd, POLLIN, errno)) + continue; } return nr; } @@ -273,8 +279,13 @@ ssize_t xwrite(int fd, const void *buf, size_t len) len = MAX_IO_SIZE; while (1) { nr = write(fd, buf, len); - if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) - continue; + if (nr < 0) { + if (errno == EINTR) + continue; + if (handle_nonblock(fd, POLLOUT, errno)) + continue; + } + return nr; } } @@ -640,56 +651,28 @@ int xsnprintf(char *dst, size_t max, const char *fmt, ...) return len; } -static int write_file_v(const char *path, int fatal, - const char *fmt, va_list params) +void write_file_buf(const char *path, const char *buf, size_t len) { - struct strbuf sb = STRBUF_INIT; - int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666); - if (fd < 0) { - if (fatal) - die_errno(_("could not open %s for writing"), path); - return -1; - } - strbuf_vaddf(&sb, fmt, params); - strbuf_complete_line(&sb); - if (write_in_full(fd, sb.buf, sb.len) != sb.len) { - int err = errno; - close(fd); - strbuf_release(&sb); - errno = err; - if (fatal) - die_errno(_("could not write to %s"), path); - return -1; - } - strbuf_release(&sb); - if (close(fd)) { - if (fatal) - die_errno(_("could not close %s"), path); - return -1; - } - return 0; + int fd = xopen(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (write_in_full(fd, buf, len) != len) + die_errno(_("could not write to %s"), path); + if (close(fd)) + die_errno(_("could not close %s"), path); } -int write_file(const char *path, const char *fmt, ...) +void write_file(const char *path, const char *fmt, ...) { - int status; va_list params; + struct strbuf sb = STRBUF_INIT; va_start(params, fmt); - status = write_file_v(path, 1, fmt, params); + strbuf_vaddf(&sb, fmt, params); va_end(params); - return status; -} -int write_file_gently(const char *path, const char *fmt, ...) -{ - int status; - va_list params; + strbuf_complete_line(&sb); - va_start(params, fmt); - status = write_file_v(path, 0, fmt, params); - va_end(params); - return status; + write_file_buf(path, sb.buf, sb.len); + strbuf_release(&sb); } void sleep_millisec(int millisec) diff --git a/wt-status.c b/wt-status.c index c19b52ce87..19cbc3939c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -432,7 +432,8 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, d->worktree_status = p->status; d->dirty_submodule = p->two->dirty_submodule; if (S_ISGITLINK(p->two->mode)) - d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1); + d->new_submodule_commits = !!oidcmp(&p->one->oid, + &p->two->oid); } } @@ -1062,7 +1063,7 @@ static void abbrev_sha1_in_line(struct strbuf *line) strbuf_addf(split[1], "%s ", abbrev); strbuf_reset(line); for (i = 0; split[i]; i++) - strbuf_addf(line, "%s", split[i]->buf); + strbuf_addbuf(line, split[i]); } } strbuf_list_free(split); diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c index 04e1a1ab2a..a613efc703 100644 --- a/xdiff/xpatience.c +++ b/xdiff/xpatience.c @@ -1,6 +1,6 @@ /* * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin + * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 62cb23dfd3..027192a1c7 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -200,8 +200,10 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) return 0; } } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { - while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++]) - ; /* keep going */ + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } } /* |