summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml5
-rw-r--r--.gitignore2
-rw-r--r--Documentation/RelNotes/2.29.0.txt55
-rw-r--r--Documentation/config/core.txt2
-rw-r--r--Documentation/git-checkout.txt1
-rw-r--r--Documentation/git-for-each-ref.txt10
-rw-r--r--Documentation/git-imap-send.txt34
-rw-r--r--Documentation/git-worktree.txt39
-rw-r--r--Makefile71
-rw-r--r--add-patch.c26
-rw-r--r--archive.c4
-rw-r--r--blame.c4
-rw-r--r--branch.c2
-rw-r--r--builtin/add.c12
-rw-r--r--builtin/checkout.c4
-rw-r--r--builtin/clean.c22
-rw-r--r--builtin/clone.c7
-rw-r--r--builtin/config.c12
-rw-r--r--builtin/fast-export.c2
-rw-r--r--builtin/fast-import.c8
-rw-r--r--builtin/fetch.c11
-rw-r--r--builtin/grep.c2
-rw-r--r--builtin/index-pack.c456
-rw-r--r--builtin/init-db.c26
-rw-r--r--builtin/log.c24
-rw-r--r--builtin/merge.c2
-rw-r--r--builtin/push.c54
-rw-r--r--builtin/repack.c12
-rw-r--r--builtin/reset.c2
-rw-r--r--builtin/rev-parse.c2
-rw-r--r--builtin/show-branch.c2
-rw-r--r--builtin/stash.c2
-rw-r--r--builtin/submodule--helper.c431
-rw-r--r--builtin/worktree.c40
-rw-r--r--bundle.c2
-rw-r--r--cache.h27
-rw-r--r--commit.c2
-rw-r--r--commit.h1
-rw-r--r--compat/vcbuild/README4
-rwxr-xr-xcompat/vcbuild/scripts/clink.pl2
-rw-r--r--contrib/buildsystems/Generators/Vcxproj.pm1
-rwxr-xr-xcontrib/buildsystems/engine.pl2
-rw-r--r--diff-lib.c35
-rw-r--r--diff.c10
-rw-r--r--diff.h7
-rwxr-xr-xgit-add--interactive.perl2
-rwxr-xr-xgit-submodule.sh186
-rw-r--r--interdiff.c28
-rw-r--r--interdiff.h8
-rw-r--r--log-tree.c25
-rw-r--r--midx.c19
-rw-r--r--pack-bitmap-write.c3
-rw-r--r--packfile.c11
-rw-r--r--packfile.h1
-rw-r--r--pretty.c20
-rw-r--r--pretty.h3
-rw-r--r--quote.c47
-rw-r--r--quote.h11
-rw-r--r--read-cache.c2
-rw-r--r--ref-filter.c158
-rw-r--r--refs.c20
-rw-r--r--refs.h12
-rw-r--r--refs/files-backend.c7
-rw-r--r--refs/refs-internal.h7
-rw-r--r--refspec.c18
-rw-r--r--refspec.h2
-rw-r--r--remote.c12
-rw-r--r--revision.c27
-rw-r--r--revision.h1
-rw-r--r--sha1-name.c45
-rw-r--r--submodule.c26
-rw-r--r--submodule.h2
-rwxr-xr-xt/t0001-init.sh28
-rwxr-xr-xt/t1300-config.sh13
-rwxr-xr-xt/t2406-worktree-repair.sh179
-rwxr-xr-xt/t3200-branch.sh1
-rwxr-xr-xt/t3701-add-interactive.sh8
-rwxr-xr-xt/t5319-multi-pack-index.sh44
-rwxr-xr-xt/t6300-for-each-ref.sh35
-rwxr-xr-xt/t7401-submodule-summary.sh6
-rwxr-xr-xt/t7421-submodule-summary-add.sh69
-rwxr-xr-xt/t7508-status.sh39
-rw-r--r--worktree.c135
-rw-r--r--worktree.h23
-rw-r--r--wt-status.c39
85 files changed, 1987 insertions, 816 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 30425404eb..fcfd138ff1 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -12,7 +12,6 @@ jobs:
enabled: ${{ steps.check-ref.outputs.enabled }}
steps:
- name: try to clone ci-config branch
- continue-on-error: true
run: |
git -c protocol.version=2 clone \
--no-tags \
@@ -23,8 +22,8 @@ jobs:
--filter=blob:none \
https://github.com/${{ github.repository }} \
config-repo &&
- cd config-repo &&
- git checkout HEAD -- ci/config
+ cd config-repo &&
+ git checkout HEAD -- ci/config || : ignore
- id: check-ref
name: check whether CI is enabled for ref
run: |
diff --git a/.gitignore b/.gitignore
index 9673e792db..d0f692a355 100644
--- a/.gitignore
+++ b/.gitignore
@@ -196,6 +196,7 @@
/git.spec
*.exe
*.[aos]
+*.o.json
*.py[co]
.depend/
*.gcda
@@ -217,6 +218,7 @@
/tags
/TAGS
/cscope*
+/compile_commands.json
*.hcc
*.obj
*.lib
diff --git a/Documentation/RelNotes/2.29.0.txt b/Documentation/RelNotes/2.29.0.txt
index a640fbc02f..67d9801da3 100644
--- a/Documentation/RelNotes/2.29.0.txt
+++ b/Documentation/RelNotes/2.29.0.txt
@@ -55,6 +55,18 @@ UI, Workflows & Features
object names of blobs involved in the patch, but its length was not
affected by the --abbrev option. Now it is.
+ * "git worktree" gained a "repair" subcommand to help users recover
+ after moving the worktrees or repository manually without telling
+ Git. Also, "git init --separate-git-dir" no longer corrupts
+ administrative data related to linked worktrees.
+
+ * The "--format=" option to the "for-each-ref" command and friends
+ learned a few more tricks, e.g. the ":short" suffix that applies to
+ "objectname" now also can be used for "parent", "tree", etc.
+
+ * "git worktree add" learns that the "-d" is a synonym to "--detach"
+ option to create a new worktree without being on a branch.
+
Performance, Internal Implementation, Development Support etc.
@@ -128,6 +140,21 @@ Performance, Internal Implementation, Development Support etc.
execute the git subcommands, especially built-ins, in "git-foo"
form, which have been corrected.
+ * When a packfile is removed by "git repack", multi-pack-index gets
+ cleared; the code was taught to do so less aggressively by first
+ checking if the midx actually refers to a pack that no longer
+ exists.
+
+ * Internal API clean-up to handle two options "diff-index" and "log"
+ have, which happen to share the same short form, more sensibly.
+
+ * The "add -i/-p" machinery has been written in C but it is not used
+ by default yet. It is made default to those who are participating
+ in feature.experimental experiment.
+
+ * Allow maintainers to tweak $(TAR) invocations done while making
+ distribution tarballs.
+
Fixes since v2.28
-----------------
@@ -286,6 +313,29 @@ Fixes since v2.28
been freed, which has been fixed.
(merge 6479ea4a8a jk/xrealloc-avoid-use-after-free later to maint).
+ * "git status" has trouble showing where it came from by interpreting
+ reflog entries that record certain events, e.g. "checkout @{u}", and
+ gives a hard/fatal error. Even though it inherently is impossible
+ to give a correct answer because the reflog entries lose some
+ information (e.g. "@{u}" does not record what branch the user was
+ on hence which branch 'the upstream' needs to be computed, and even
+ if the record were available, the relationship between branches may
+ have changed), at least hide the error to allow "status" show its
+ output.
+
+ * "git status --short" quoted a path with SP in it when tracked, but
+ not those that are untracked, ignored or unmerged. They are all
+ shown quoted consistently.
+
+ * "git diff/show" on a change that involves a submodule used to read
+ the information on commits in the submodule from a wrong repository
+ and gave a wrong information when the commit-graph is involved.
+ (merge 85a1ec2c32 mf/submodule-summary-with-correct-repository later to maint).
+
+ * Unlike "git config --local", "git config --worktree" did not fail
+ early and cleanly when started outside a git repository.
+ (merge 378fe5fc3d mt/config-fail-nongit-early later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 84544f2ea3 sk/typofixes later to maint).
(merge b17f411ab5 ar/help-guides-doc later to maint).
@@ -311,3 +361,8 @@ Fixes since v2.28
(merge ee22a29215 so/pretty-abbrev-doc later to maint).
(merge 3100fd5588 jc/post-checkout-doc later to maint).
(merge 17bae89476 pb/doc-external-diff-env later to maint).
+ (merge 27ed6ccc12 jk/worktree-check-clean-leakfix later to maint).
+ (merge 1302badd16 ea/blame-use-oideq later to maint).
+ (merge e6d5a11fed al/t3200-back-on-a-branch later to maint).
+ (merge 324efcf6b6 pw/add-p-leakfix later to maint).
+ (merge 1c6ffb546b jk/add-i-fixes later to maint).
diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
index 74619a9c03..02002cf109 100644
--- a/Documentation/config/core.txt
+++ b/Documentation/config/core.txt
@@ -399,7 +399,7 @@ the largest projects. You probably do not need to adjust this value.
Common unit suffixes of 'k', 'm', or 'g' are supported.
core.deltaBaseCacheLimit::
- Maximum number of bytes to reserve for caching base objects
+ Maximum number of bytes per thread to reserve for caching base objects
that may be referenced by multiple deltified objects. By storing the
entire decompressed base objects in a cache Git is able
to avoid unpacking and decompressing frequently used base
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 5b697eee1b..afa5c11fd3 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -198,6 +198,7 @@ Use `--no-guess` to disable this.
Create the new branch's reflog; see linkgit:git-branch[1] for
details.
+-d::
--detach::
Rather than checking out a branch to work on it, check out a
commit for inspection and discardable experiments.
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 2ea71c5f6c..616ce46087 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -222,6 +222,8 @@ worktreepath::
In addition to the above, for commit and tag objects, the header
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
be used to specify the value in the header field.
+Fields `tree` and `parent` can also be used with modifier `:short` and
+`:short=<length>` just like `objectname`.
For commit and tag objects, the special `creatordate` and `creator`
fields will correspond to the appropriate date or name-email-date tuple
@@ -230,7 +232,10 @@ These are intended for working on a mix of annotated and lightweight tags.
Fields that have name-email-date tuple as its value (`author`,
`committer`, and `tagger`) can be suffixed with `name`, `email`,
-and `date` to extract the named component.
+and `date` to extract the named component. For email fields (`authoremail`,
+`committeremail` and `taggeremail`), `:trim` can be appended to get the email
+without angle brackets, and `:localpart` to get the part before the `@` symbol
+out of the trimmed email.
The message in a commit or a tag object is `contents`, from which
`contents:<part>` can be used to extract various parts out of:
@@ -242,6 +247,9 @@ contents:subject::
The first paragraph of the message, which typically is a
single line, is taken as the "subject" of the commit or the
tag message.
+ Instead of `contents:subject`, field `subject` can also be used to
+ obtain same results. `:sanitize` can be appended to `subject` for
+ subject line suitable for filename.
contents:body::
The remainder of the commit or the tag message that follows
diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
index 65b53fcc47..63cf498ce9 100644
--- a/Documentation/git-imap-send.txt
+++ b/Documentation/git-imap-send.txt
@@ -51,17 +51,13 @@ OPTIONS
CONFIGURATION
-------------
-To use the tool, imap.folder and either imap.tunnel or imap.host must be set
+To use the tool, `imap.folder` and either `imap.tunnel` or `imap.host` must be set
to appropriate values.
-Variables
-~~~~~~~~~
-
include::config/imap.txt[]
-Examples
-~~~~~~~~
-
+EXAMPLES
+--------
Using tunnel mode:
..........................
@@ -89,14 +85,18 @@ Using direct mode with SSL:
user = bob
pass = p4ssw0rd
port = 123
- sslverify = false
+ ; sslVerify = false
.........................
-EXAMPLES
---------
-To submit patches using GMail's IMAP interface, first, edit your ~/.gitconfig
-to specify your account settings:
+[NOTE]
+You may want to use `sslVerify=false`
+while troubleshooting, if you suspect that the reason you are
+having trouble connecting is because the certificate you use at
+the private server `example.com` you are trying to set up (or
+have set up) may not be verified correctly.
+
+Using Gmail's IMAP interface:
---------
[imap]
@@ -104,17 +104,21 @@ to specify your account settings:
host = imaps://imap.gmail.com
user = user@gmail.com
port = 993
- sslverify = false
---------
-You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
+[NOTE]
+You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error
that the "Folder doesn't exist".
+[NOTE]
+If your Gmail account is set to another language than English, the name of the "Drafts"
+folder will be localized.
+
Once the commits are ready to be sent, run the following command:
$ git format-patch --cover-letter -M --stdout origin/master | git imap-send
-Just make sure to disable line wrapping in the email client (GMail's web
+Just make sure to disable line wrapping in the email client (Gmail's web
interface will wrap lines no matter what, so you need to use a real
IMAP client).
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 6ee6ec7982..32e8440cde 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -15,6 +15,7 @@ SYNOPSIS
'git worktree move' <worktree> <new-path>
'git worktree prune' [-n] [-v] [--expire <expire>]
'git worktree remove' [-f] <worktree>
+'git worktree repair' [<path>...]
'git worktree unlock' <worktree>
DESCRIPTION
@@ -31,6 +32,18 @@ A repository has one main working tree (if it's not a
bare repository) and zero or more linked working trees. When you are done
with a linked working tree, remove it with `git worktree remove`.
+In its simplest form, `git worktree add <path>` automatically creates a
+new branch whose name is the final component of `<path>`, which is
+convenient if you plan to work on a new topic. For instance, `git
+worktree add ../hotfix` creates new branch `hotfix` and checks it out at
+path `../hotfix`. To instead work on an existing branch in a new working
+tree, use `git worktree add <path> <branch>`. On the other hand, if you
+just plan to make some experimental changes or do testing without
+disturbing existing development, it is often convenient to create a
+'throwaway' working tree not associated with any branch. For instance,
+`git worktree add -d <path>` creates a new working tree with a detached
+`HEAD` at the same commit as the current branch.
+
If a working tree is deleted without using `git worktree remove`, then
its associated administrative files, which reside in the repository
(see "DETAILS" below), will eventually be removed automatically (see
@@ -97,7 +110,10 @@ with `--reason`.
move::
Move a working tree to a new location. Note that the main working tree
-or linked working trees containing submodules cannot be moved.
+or linked working trees containing submodules cannot be moved with this
+command. (The `git worktree repair` command, however, can reestablish
+the connection with linked working trees if you move the main working
+tree manually.)
prune::
@@ -110,6 +126,23 @@ and no modification in tracked files) can be removed. Unclean working
trees or ones with submodules can be removed with `--force`. The main
working tree cannot be removed.
+repair [<path>...]::
+
+Repair working tree administrative files, if possible, if they have
+become corrupted or outdated due to external factors.
++
+For instance, if the main working tree (or bare repository) is moved,
+linked working trees will be unable to locate it. Running `repair` in
+the main working tree will reestablish the connection from linked
+working trees back to the main working tree.
++
+Similarly, if a linked working tree is moved without using `git worktree
+move`, the main working tree (or bare repository) will be unable to
+locate it. Running `repair` within the recently-moved working tree will
+reestablish the connection. If multiple linked working trees are moved,
+running `repair` from any working tree with each tree's new `<path>` as
+an argument, will reestablish the connection to all the specified paths.
+
unlock::
Unlock a working tree, allowing it to be pruned, moved or deleted.
@@ -143,6 +176,7 @@ To remove a locked working tree, specify `--force` twice.
exists. `-B` overrides this safeguard, resetting `<new-branch>` to
`<commit-ish>`.
+-d::
--detach::
With `add`, detach `HEAD` in the new working tree. See "DETACHED HEAD"
in linkgit:git-checkout[1].
@@ -303,7 +337,8 @@ in the entry's directory. For example, if a linked working tree is moved
to `/newpath/test-next` and its `.git` file points to
`/path/main/.git/worktrees/test-next`, then update
`/path/main/.git/worktrees/test-next/gitdir` to reference `/newpath/test-next`
-instead.
+instead. Better yet, run `git worktree repair` to reestablish the connection
+automatically.
To prevent a `$GIT_DIR/worktrees` entry from being pruned (which
can be useful in some situations, such as when the
diff --git a/Makefile b/Makefile
index 583b8c526e..92d188fd37 100644
--- a/Makefile
+++ b/Makefile
@@ -462,6 +462,12 @@ all::
# the global variable _wpgmptr containing the absolute path of the current
# executable (this is the case on Windows).
#
+# Define GENERATE_COMPILATION_DATABASE to "yes" to generate JSON compilation
+# database entries during compilation if your compiler supports it, using the
+# `-MJ` flag. The JSON entries will be placed in the `compile_commands/`
+# directory, and the JSON compilation database 'compile_commands.json' will be
+# created at the root of the repository.
+#
# Define DEVELOPER to enable more compiler warnings. Compiler version
# and family are auto detected, but could be overridden by defining
# COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -883,7 +889,6 @@ LIB_OBJS += hashmap.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o
-LIB_OBJS += interdiff.o
LIB_OBJS += json-writer.o
LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
@@ -1257,6 +1262,27 @@ $(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
endif
endif
+ifndef GENERATE_COMPILATION_DATABASE
+GENERATE_COMPILATION_DATABASE = no
+endif
+
+ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
+compdb_check = $(shell $(CC) $(ALL_CFLAGS) \
+ -c -MJ /dev/null \
+ -x c /dev/null -o /dev/null 2>&1; \
+ echo $$?)
+ifneq ($(compdb_check),0)
+override GENERATE_COMPILATION_DATABASE = no
+$(warning GENERATE_COMPILATION_DATABASE is set to "yes", but your compiler does not \
+support generating compilation database entries)
+endif
+else
+ifneq ($(GENERATE_COMPILATION_DATABASE),no)
+$(error please set GENERATE_COMPILATION_DATABASE to "yes" or "no" \
+(not "$(GENERATE_COMPILATION_DATABASE)"))
+endif
+endif
+
ifdef SANE_TOOL_PATH
SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH))
BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix "$(SANE_TOOL_PATH_SQ)"|'
@@ -2370,16 +2396,30 @@ missing_dep_dirs =
dep_args =
endif
+compdb_dir = compile_commands
+
+ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
+missing_compdb_dir = $(compdb_dir)
+$(missing_compdb_dir):
+ @mkdir -p $@
+
+compdb_file = $(compdb_dir)/$(subst /,-,$@.json)
+compdb_args = -MJ $(compdb_file)
+else
+missing_compdb_dir =
+compdb_args =
+endif
+
ASM_SRC := $(wildcard $(OBJECTS:o=S))
ASM_OBJ := $(ASM_SRC:S=o)
C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS))
.SUFFIXES:
-$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs)
- $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
-$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs)
- $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
%.s: %.c GIT-CFLAGS FORCE
$(QUIET_CC)$(CC) -o $@ -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
@@ -2402,6 +2442,14 @@ else
$(OBJECTS): $(LIB_H) $(GENERATED_H)
endif
+ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
+all:: compile_commands.json
+compile_commands.json:
+ @$(RM) $@
+ $(QUIET_GEN)sed -e '1s/^/[/' -e '$$s/,$$/]/' $(compdb_dir)/*.o.json > $@+
+ @if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
+endif
+
exec-cmd.sp exec-cmd.s exec-cmd.o: GIT-PREFIX
exec-cmd.sp exec-cmd.s exec-cmd.o: EXTRA_CPPFLAGS = \
'-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
@@ -2993,6 +3041,9 @@ quick-install-html:
### Maintainer's dist rules
+# Allow tweaking to hide local environment effects, like perm bits.
+# With GNU tar, "--mode=u+rwX,og+rX,og-w" would be a good idea, for example.
+TAR_DIST_EXTRA_OPTS =
GIT_TARNAME = git-$(GIT_VERSION)
dist: git-archive$(X) configure
./git-archive --format=tar \
@@ -3001,7 +3052,7 @@ dist: git-archive$(X) configure
@cp configure $(GIT_TARNAME)
@echo $(GIT_VERSION) > $(GIT_TARNAME)/version
@$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version
- $(TAR) rf $(GIT_TARNAME).tar \
+ $(TAR) rf $(GIT_TARNAME).tar $(TAR_DIST_EXTRA_OPTS) \
$(GIT_TARNAME)/configure \
$(GIT_TARNAME)/version \
$(GIT_TARNAME)/git-gui/version
@@ -3015,7 +3066,7 @@ ifdef DC_SHA1_SUBMODULE
$(GIT_TARNAME)/sha1collisiondetection/lib/
@cp sha1collisiondetection/lib/ubc_check.[ch] \
$(GIT_TARNAME)/sha1collisiondetection/lib/
- $(TAR) rf $(GIT_TARNAME).tar \
+ $(TAR) rf $(GIT_TARNAME).tar $(TAR_DIST_EXTRA_OPTS) \
$(GIT_TARNAME)/sha1collisiondetection/LICENSE.txt \
$(GIT_TARNAME)/sha1collisiondetection/lib/sha1.[ch] \
$(GIT_TARNAME)/sha1collisiondetection/lib/ubc_check.[ch]
@@ -3049,7 +3100,7 @@ dist-doc:
$(RM) -r .doc-tmp-dir
mkdir .doc-tmp-dir
$(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc
- cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar .
+ cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar $(TAR_DIST_EXTRA_OPTS) .
gzip -n -9 -f $(htmldocs).tar
:
$(RM) -r .doc-tmp-dir
@@ -3059,7 +3110,7 @@ dist-doc:
man5dir=../.doc-tmp-dir/man5 \
man7dir=../.doc-tmp-dir/man7 \
install
- cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
+ cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar $(TAR_DIST_EXTRA_OPTS) .
gzip -n -9 -f $(manpages).tar
$(RM) -r .doc-tmp-dir
@@ -3086,7 +3137,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) $(TEST_PROGRAMS)
$(RM) $(FUZZ_PROGRAMS)
$(RM) $(HCC)
- $(RM) -r bin-wrappers $(dep_dirs)
+ $(RM) -r bin-wrappers $(dep_dirs) $(compdb_dir) compile_commands.json
$(RM) -r po/build/
$(RM) *.pyc *.pyo */*.pyc */*.pyo $(GENERATED_H) $(ETAGS_TARGET) tags cscope*
$(RM) -r $(GIT_TARNAME) .doc-tmp-dir
diff --git a/add-patch.c b/add-patch.c
index 457b8c550e..cd5cfc93fa 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -266,6 +266,20 @@ struct add_p_state {
const char *revision;
};
+static void add_p_state_clear(struct add_p_state *s)
+{
+ size_t i;
+
+ strbuf_release(&s->answer);
+ strbuf_release(&s->buf);
+ strbuf_release(&s->plain);
+ strbuf_release(&s->colored);
+ for (i = 0; i < s->file_diff_nr; i++)
+ free(s->file_diff[i].hunk);
+ free(s->file_diff);
+ clear_add_i_state(&s->s);
+}
+
static void err(struct add_p_state *s, const char *fmt, ...)
{
va_list args;
@@ -1641,7 +1655,7 @@ soft_increment:
NULL, 0, NULL, 0))
error(_("'git apply' failed"));
}
- if (!repo_read_index(s->s.r))
+ if (repo_read_index(s->s.r) >= 0)
repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
1, NULL, NULL, NULL);
}
@@ -1690,9 +1704,7 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
NULL, NULL, NULL) < 0) ||
parse_diff(&s, ps) < 0) {
- strbuf_release(&s.plain);
- strbuf_release(&s.colored);
- clear_add_i_state(&s.s);
+ add_p_state_clear(&s);
return -1;
}
@@ -1707,10 +1719,6 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
else if (binary_count == s.file_diff_nr)
fprintf(stderr, _("Only binary files changed.\n"));
- strbuf_release(&s.answer);
- strbuf_release(&s.buf);
- strbuf_release(&s.plain);
- strbuf_release(&s.colored);
- clear_add_i_state(&s.s);
+ add_p_state_clear(&s);
return 0;
}
diff --git a/archive.c b/archive.c
index fb39706120..0de6048bfc 100644
--- a/archive.c
+++ b/archive.c
@@ -397,10 +397,10 @@ static void parse_treeish_arg(const char **argv,
const char *colon = strchrnul(name, ':');
int refnamelen = colon - name;
- if (!dwim_ref(name, refnamelen, &oid, &ref))
+ if (!dwim_ref(name, refnamelen, &oid, &ref, 0))
die(_("no such ref: %.*s"), refnamelen, name);
} else {
- dwim_ref(name, strlen(name), &oid, &ref);
+ dwim_ref(name, strlen(name), &oid, &ref, 0);
}
if (get_oid(name, &oid))
diff --git a/blame.c b/blame.c
index 1be1cd82a2..b475bfa1c0 100644
--- a/blame.c
+++ b/blame.c
@@ -1353,8 +1353,8 @@ static struct blame_origin *find_origin(struct repository *r,
else {
int compute_diff = 1;
if (origin->commit->parents &&
- !oidcmp(&parent->object.oid,
- &origin->commit->parents->item->object.oid))
+ oideq(&parent->object.oid,
+ &origin->commit->parents->item->object.oid))
compute_diff = maybe_changed_path(r, origin, bd);
if (compute_diff)
diff --git a/branch.c b/branch.c
index 7095f78058..9c9dae1eae 100644
--- a/branch.c
+++ b/branch.c
@@ -281,7 +281,7 @@ void create_branch(struct repository *r,
die(_("Not a valid object name: '%s'."), start_name);
}
- switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref)) {
+ switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
case 0:
/* Not branching from any existing branch */
if (explicit_tracking)
diff --git a/builtin/add.c b/builtin/add.c
index b36a99eb7c..26b6ced09e 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -192,9 +192,15 @@ int run_add_interactive(const char *revision, const char *patch_mode,
int use_builtin_add_i =
git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
- if (use_builtin_add_i < 0)
- git_config_get_bool("add.interactive.usebuiltin",
- &use_builtin_add_i);
+ if (use_builtin_add_i < 0) {
+ int experimental;
+ if (!git_config_get_bool("add.interactive.usebuiltin",
+ &use_builtin_add_i))
+ ; /* ok */
+ else if (!git_config_get_bool("feature.experimental", &experimental) &&
+ experimental)
+ use_builtin_add_i = 1;
+ }
if (use_builtin_add_i == 1) {
enum add_p_mode mode;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 6ec82097a2..0951f8fee5 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -651,7 +651,7 @@ static void setup_branch_path(struct branch_info *branch)
* If this is a ref, resolve it; otherwise, look up the OID for our
* expression. Failure here is okay.
*/
- if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+ if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname, 0))
repo_get_oid_committish(the_repository, branch->name, &branch->oid);
strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
@@ -1345,7 +1345,7 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
struct object_id oid;
char *to_free;
- if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
+ if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
const char *ref = to_free;
if (skip_prefix(ref, "refs/tags/", &ref))
diff --git a/builtin/clean.c b/builtin/clean.c
index e53ea52d89..687ab473c2 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -162,7 +162,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
is_nonbare_repository_dir(path)) {
if (!quiet) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
quoted.buf);
}
@@ -177,7 +177,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -202,7 +202,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
ret = 1;
if (gone) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
string_list_append(&dels, quoted.buf);
} else
*dir_gone = 0;
@@ -210,11 +210,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
} else {
res = dry_run ? 0 : unlink(path->buf);
if (!res) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
string_list_append(&dels, quoted.buf);
} else {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -238,7 +238,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
*dir_gone = 1;
else {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -266,7 +266,7 @@ static void pretty_print_dels(void)
struct column_options copts;
for_each_string_list_item(item, &del_list) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
string_list_append(&list, qname);
}
@@ -753,7 +753,7 @@ static int ask_each_cmd(void)
for_each_string_list_item(item, &del_list) {
/* Ctrl-D should stop removing files */
if (!eof) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
/* TRANSLATORS: Make sure to keep [y/N] as is */
printf(_("Remove %s [y/N]? "), qname);
if (git_read_line_interactively(&confirm) == EOF) {
@@ -1047,19 +1047,19 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
errors++;
if (gone && !quiet) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
} else {
res = dry_run ? 0 : unlink(abs_path.buf);
if (res) {
int saved_errno = errno;
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), qname);
errors++;
} else if (!quiet) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
diff --git a/builtin/clone.c b/builtin/clone.c
index b087ee40c2..fbfd6568cd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -953,7 +953,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
struct ref *mapped_refs;
const struct ref *ref;
struct strbuf key = STRBUF_INIT;
- struct strbuf default_refspec = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
const char *src_ref_prefix = "refs/heads/";
@@ -1157,9 +1156,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote = remote_get(option_origin);
- strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,
- branch_top.buf);
- refspec_append(&remote->fetch, default_refspec.buf);
+ refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix,
+ branch_top.buf);
transport = transport_get(remote, remote->url[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1332,7 +1330,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
- strbuf_release(&default_refspec);
junk_mode = JUNK_LEAVE_ALL;
strvec_clear(&ref_prefixes);
diff --git a/builtin/config.c b/builtin/config.c
index 5e39f61885..53e411d68a 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -628,11 +628,15 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config();
}
- if (use_local_config && nongit)
- die(_("--local can only be used inside a git repository"));
+ if (nongit) {
+ if (use_local_config)
+ die(_("--local can only be used inside a git repository"));
+ if (given_config_source.blob)
+ die(_("--blob can only be used inside a git repository"));
+ if (use_worktree_config)
+ die(_("--worktree can only be used inside a git repository"));
- if (given_config_source.blob && nongit)
- die(_("--blob can only be used inside a git repository"));
+ }
if (given_config_source.file &&
!strcmp(given_config_source.file, "-")) {
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 9f37895d4c..1b8fca3ee0 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -943,7 +943,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
if (e->flags & UNINTERESTING)
continue;
- if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1)
+ if (dwim_ref(e->name, strlen(e->name), &oid, &full_name, 0) != 1)
continue;
if (refspecs.nr) {
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index 1c85eafe43..1bf50a73dc 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -739,7 +739,6 @@ static void start_packfile(void)
{
struct strbuf tmp_file = STRBUF_INIT;
struct packed_git *p;
- struct pack_header hdr;
int pack_fd;
pack_fd = odb_mkstemp(&tmp_file, "pack/tmp_pack_XXXXXX");
@@ -750,13 +749,8 @@ static void start_packfile(void)
p->do_not_close = 1;
pack_file = hashfd(pack_fd, p->pack_name);
- hdr.hdr_signature = htonl(PACK_SIGNATURE);
- hdr.hdr_version = htonl(2);
- hdr.hdr_entries = 0;
- hashwrite(pack_file, &hdr, sizeof(hdr));
-
pack_data = p;
- pack_size = sizeof(hdr);
+ pack_size = write_pack_header(pack_file, 0);
object_count = 0;
REALLOC_ARRAY(all_packs, pack_id + 1);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index a6d3268661..76a4d14f57 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -966,8 +966,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
ref->force = rm->peer_ref->force;
}
- if (recurse_submodules != RECURSE_SUBMODULES_OFF)
+ if (recurse_submodules != RECURSE_SUBMODULES_OFF &&
+ (!rm->peer_ref || !oideq(&ref->old_oid, &ref->new_oid))) {
check_for_new_submodule_commits(&rm->old_oid);
+ }
if (!strcmp(rm->name, "HEAD")) {
kind = "";
@@ -1738,15 +1740,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
- char *tag;
i++;
if (i >= argc)
die(_("You need to specify a tag name."));
- tag = xstrfmt("refs/tags/%s:refs/tags/%s",
- argv[i], argv[i]);
- refspec_append(&rs, tag);
- free(tag);
+ refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s",
+ argv[i], argv[i]);
} else {
refspec_append(&rs, argv[i]);
}
diff --git a/builtin/grep.c b/builtin/grep.c
index f58979bc3f..c8037388c6 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -319,7 +319,7 @@ static void grep_source_name(struct grep_opt *opt, const char *filename,
}
if (opt->relative && opt->prefix_length)
- quote_path_relative(filename + tree_name_len, opt->prefix, out);
+ quote_path(filename + tree_name_len, opt->prefix, out, 0);
else
quote_c_style(filename + tree_name_len, out, NULL, 0);
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 9721bf1ffe..8acd078aa0 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -33,19 +33,61 @@ struct object_stat {
};
struct base_data {
+ /* Initialized by make_base(). */
struct base_data *base;
- struct base_data *child;
struct object_entry *obj;
- void *data;
- unsigned long size;
int ref_first, ref_last;
int ofs_first, ofs_last;
+ /*
+ * Threads should increment retain_data if they are about to call
+ * patch_delta() using this struct's data as a base, and decrement this
+ * when they are done. While retain_data is nonzero, this struct's data
+ * will not be freed even if the delta base cache limit is exceeded.
+ */
+ int retain_data;
+ /*
+ * The number of direct children that have not been fully processed
+ * (entered work_head, entered done_head, left done_head). When this
+ * number reaches zero, this struct base_data can be freed.
+ */
+ int children_remaining;
+
+ /* Not initialized by make_base(). */
+ struct list_head list;
+ void *data;
+ unsigned long size;
};
+/*
+ * Stack of struct base_data that have unprocessed children.
+ * threaded_second_pass() uses this as a source of work (the other being the
+ * objects array).
+ *
+ * Guarded by work_mutex.
+ */
+static LIST_HEAD(work_head);
+
+/*
+ * Stack of struct base_data that have children, all of whom have been
+ * processed or are being processed, and at least one child is being processed.
+ * These struct base_data must be kept around until the last child is
+ * processed.
+ *
+ * Guarded by work_mutex.
+ */
+static LIST_HEAD(done_head);
+
+/*
+ * All threads share one delta base cache.
+ *
+ * base_cache_used is guarded by work_mutex, and base_cache_limit is read-only
+ * in a thread.
+ */
+static size_t base_cache_used;
+static size_t base_cache_limit;
+
struct thread_local {
pthread_t thread;
- struct base_data *base_cache;
- size_t base_cache_used;
int pack_fd;
};
@@ -364,56 +406,42 @@ static void set_thread_data(struct thread_local *data)
pthread_setspecific(key, data);
}
-static struct base_data *alloc_base_data(void)
-{
- struct base_data *base = xcalloc(1, sizeof(struct base_data));
- base->ref_last = -1;
- base->ofs_last = -1;
- return base;
-}
-
static void free_base_data(struct base_data *c)
{
if (c->data) {
FREE_AND_NULL(c->data);
- get_thread_data()->base_cache_used -= c->size;
+ base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
- struct base_data *b;
- struct thread_local *data = get_thread_data();
- for (b = data->base_cache;
- data->base_cache_used > delta_base_cache_limit && b;
- b = b->child) {
- if (b->data && b != retain)
- free_base_data(b);
- }
-}
+ struct list_head *pos;
-static void link_base_data(struct base_data *base, struct base_data *c)
-{
- if (base)
- base->child = c;
- else
- get_thread_data()->base_cache = c;
+ if (base_cache_used <= base_cache_limit)
+ return;
- c->base = base;
- c->child = NULL;
- if (c->data)
- get_thread_data()->base_cache_used += c->size;
- prune_base_data(c);
-}
+ list_for_each_prev(pos, &done_head) {
+ struct base_data *b = list_entry(pos, struct base_data, list);
+ if (b->retain_data || b == retain)
+ continue;
+ if (b->data) {
+ free_base_data(b);
+ if (base_cache_used <= base_cache_limit)
+ return;
+ }
+ }
-static void unlink_base_data(struct base_data *c)
-{
- struct base_data *base = c->base;
- if (base)
- base->child = NULL;
- else
- get_thread_data()->base_cache = NULL;
- free_base_data(c);
+ list_for_each_prev(pos, &work_head) {
+ struct base_data *b = list_entry(pos, struct base_data, list);
+ if (b->retain_data || b == retain)
+ continue;
+ if (b->data) {
+ free_base_data(b);
+ if (base_cache_used <= base_cache_limit)
+ return;
+ }
+ }
}
static int is_delta_type(enum object_type type)
@@ -614,7 +642,7 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
0;
}
-static int find_ofs_delta(const off_t offset, enum object_type type)
+static int find_ofs_delta(const off_t offset)
{
int first = 0, last = nr_ofs_deltas;
@@ -624,7 +652,8 @@ static int find_ofs_delta(const off_t offset, enum object_type type)
int cmp;
cmp = compare_ofs_delta_bases(offset, delta->offset,
- type, objects[delta->obj_no].type);
+ OBJ_OFS_DELTA,
+ objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -637,10 +666,9 @@ static int find_ofs_delta(const off_t offset, enum object_type type)
}
static void find_ofs_delta_children(off_t offset,
- int *first_index, int *last_index,
- enum object_type type)
+ int *first_index, int *last_index)
{
- int first = find_ofs_delta(offset, type);
+ int first = find_ofs_delta(offset);
int last = first;
int end = nr_ofs_deltas - 1;
@@ -668,7 +696,7 @@ static int compare_ref_delta_bases(const struct object_id *oid1,
return oidcmp(oid1, oid2);
}
-static int find_ref_delta(const struct object_id *oid, enum object_type type)
+static int find_ref_delta(const struct object_id *oid)
{
int first = 0, last = nr_ref_deltas;
@@ -678,7 +706,8 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type)
int cmp;
cmp = compare_ref_delta_bases(oid, &delta->oid,
- type, objects[delta->obj_no].type);
+ OBJ_REF_DELTA,
+ objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -691,10 +720,9 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type)
}
static void find_ref_delta_children(const struct object_id *oid,
- int *first_index, int *last_index,
- enum object_type type)
+ int *first_index, int *last_index)
{
- int first = find_ref_delta(oid, type);
+ int first = find_ref_delta(oid);
int last = first;
int end = nr_ref_deltas - 1;
@@ -866,15 +894,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
}
/*
- * This function is part of find_unresolved_deltas(). There are two
- * walkers going in the opposite ways.
- *
- * The first one in find_unresolved_deltas() traverses down from
- * parent node to children, deflating nodes along the way. However,
- * memory for deflated nodes is limited by delta_base_cache_limit, so
- * at some point parent node's deflated content may be freed.
- *
- * The second walker is this function, which goes from current node up
+ * Walk from current node up
* to top parent if necessary to deflate the node. In normal
* situation, its parent node would be already deflated, so it just
* needs to apply delta.
@@ -902,7 +922,7 @@ static void *get_base_data(struct base_data *c)
if (!delta_nr) {
c->data = get_data_from_pack(obj);
c->size = obj->size;
- get_thread_data()->base_cache_used += c->size;
+ base_cache_used += c->size;
prune_base_data(c);
}
for (; delta_nr > 0; delta_nr--) {
@@ -918,7 +938,7 @@ static void *get_base_data(struct base_data *c)
free(raw);
if (!c->data)
bad_object(obj->idx.offset, _("failed to apply delta"));
- get_thread_data()->base_cache_used += c->size;
+ base_cache_used += c->size;
prune_base_data(c);
}
free(delta);
@@ -926,10 +946,27 @@ static void *get_base_data(struct base_data *c)
return c->data;
}
-static void resolve_delta(struct object_entry *delta_obj,
- struct base_data *base, struct base_data *result)
+static struct base_data *make_base(struct object_entry *obj,
+ struct base_data *parent)
+{
+ struct base_data *base = xcalloc(1, sizeof(struct base_data));
+ base->base = parent;
+ base->obj = obj;
+ find_ref_delta_children(&obj->idx.oid,
+ &base->ref_first, &base->ref_last);
+ find_ofs_delta_children(obj->idx.offset,
+ &base->ofs_first, &base->ofs_last);
+ base->children_remaining = base->ref_last - base->ref_first +
+ base->ofs_last - base->ofs_first + 2;
+ return base;
+}
+
+static struct base_data *resolve_delta(struct object_entry *delta_obj,
+ struct base_data *base)
{
- void *base_data, *delta_data;
+ void *delta_data, *result_data;
+ struct base_data *result;
+ unsigned long result_size;
if (show_stat) {
int i = delta_obj - objects;
@@ -942,115 +979,26 @@ static void resolve_delta(struct object_entry *delta_obj,
obj_stat[i].base_object_no = j;
}
delta_data = get_data_from_pack(delta_obj);
- base_data = get_base_data(base);
- result->obj = delta_obj;
- result->data = patch_delta(base_data, base->size,
- delta_data, delta_obj->size, &result->size);
+ assert(base->data);
+ result_data = patch_delta(base->data, base->size,
+ delta_data, delta_obj->size, &result_size);
free(delta_data);
- if (!result->data)
+ if (!result_data)
bad_object(delta_obj->idx.offset, _("failed to apply delta"));
- hash_object_file(the_hash_algo, result->data, result->size,
+ hash_object_file(the_hash_algo, result_data, result_size,
type_name(delta_obj->real_type), &delta_obj->idx.oid);
- sha1_object(result->data, NULL, result->size, delta_obj->real_type,
+ sha1_object(result_data, NULL, result_size, delta_obj->real_type,
&delta_obj->idx.oid);
+
+ result = make_base(delta_obj, base);
+ result->data = result_data;
+ result->size = result_size;
+
counter_lock();
nr_resolved_deltas++;
counter_unlock();
-}
-
-/*
- * Standard boolean compare-and-swap: atomically check whether "*type" is
- * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
- * and return false.
- */
-static int compare_and_swap_type(signed char *type,
- enum object_type want,
- enum object_type set)
-{
- enum object_type old;
-
- type_cas_lock();
- old = *type;
- if (old == want)
- *type = set;
- type_cas_unlock();
- return old == want;
-}
-
-static struct base_data *find_unresolved_deltas_1(struct base_data *base,
- struct base_data *prev_base)
-{
- if (base->ref_last == -1 && base->ofs_last == -1) {
- find_ref_delta_children(&base->obj->idx.oid,
- &base->ref_first, &base->ref_last,
- OBJ_REF_DELTA);
-
- find_ofs_delta_children(base->obj->idx.offset,
- &base->ofs_first, &base->ofs_last,
- OBJ_OFS_DELTA);
-
- if (base->ref_last == -1 && base->ofs_last == -1) {
- free(base->data);
- return NULL;
- }
-
- link_base_data(prev_base, base);
- }
-
- if (base->ref_first <= base->ref_last) {
- struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no;
- struct base_data *result = alloc_base_data();
-
- if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
- base->obj->real_type))
- die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)",
- (uintmax_t)child->idx.offset,
- oid_to_hex(&base->obj->idx.oid));
-
- resolve_delta(child, base, result);
- if (base->ref_first == base->ref_last && base->ofs_last == -1)
- free_base_data(base);
-
- base->ref_first++;
- return result;
- }
-
- if (base->ofs_first <= base->ofs_last) {
- struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
- struct base_data *result = alloc_base_data();
-
- assert(child->real_type == OBJ_OFS_DELTA);
- child->real_type = base->obj->real_type;
- resolve_delta(child, base, result);
- if (base->ofs_first == base->ofs_last)
- free_base_data(base);
-
- base->ofs_first++;
- return result;
- }
-
- unlink_base_data(base);
- return NULL;
-}
-
-static void find_unresolved_deltas(struct base_data *base)
-{
- struct base_data *new_base, *prev_base = NULL;
- for (;;) {
- new_base = find_unresolved_deltas_1(base, prev_base);
-
- if (new_base) {
- prev_base = base;
- base = new_base;
- } else {
- free(base);
- base = prev_base;
- if (!base)
- return;
- prev_base = base->base;
- }
- }
+ return result;
}
static int compare_ofs_delta_entry(const void *a, const void *b)
@@ -1071,34 +1019,131 @@ static int compare_ref_delta_entry(const void *a, const void *b)
return oidcmp(&delta_a->oid, &delta_b->oid);
}
-static void resolve_base(struct object_entry *obj)
-{
- struct base_data *base_obj = alloc_base_data();
- base_obj->obj = obj;
- base_obj->data = NULL;
- find_unresolved_deltas(base_obj);
-}
-
static void *threaded_second_pass(void *data)
{
- set_thread_data(data);
+ if (data)
+ set_thread_data(data);
for (;;) {
- int i;
- counter_lock();
- display_progress(progress, nr_resolved_deltas);
- counter_unlock();
+ struct base_data *parent = NULL;
+ struct object_entry *child_obj;
+ struct base_data *child;
+
work_lock();
- while (nr_dispatched < nr_objects &&
- is_delta_type(objects[nr_dispatched].type))
- nr_dispatched++;
- if (nr_dispatched >= nr_objects) {
- work_unlock();
- break;
+ if (list_empty(&work_head)) {
+ /*
+ * Take an object from the object array.
+ */
+ while (nr_dispatched < nr_objects &&
+ is_delta_type(objects[nr_dispatched].type))
+ nr_dispatched++;
+ if (nr_dispatched >= nr_objects) {
+ work_unlock();
+ break;
+ }
+ child_obj = &objects[nr_dispatched++];
+ } else {
+ /*
+ * Peek at the top of the stack, and take a child from
+ * it.
+ */
+ parent = list_first_entry(&work_head, struct base_data,
+ list);
+
+ if (parent->ref_first <= parent->ref_last) {
+ int offset = ref_deltas[parent->ref_first++].obj_no;
+ child_obj = objects + offset;
+ if (child_obj->real_type != OBJ_REF_DELTA)
+ die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)",
+ (uintmax_t) child_obj->idx.offset,
+ oid_to_hex(&parent->obj->idx.oid));
+ child_obj->real_type = parent->obj->real_type;
+ } else {
+ child_obj = objects +
+ ofs_deltas[parent->ofs_first++].obj_no;
+ assert(child_obj->real_type == OBJ_OFS_DELTA);
+ child_obj->real_type = parent->obj->real_type;
+ }
+
+ if (parent->ref_first > parent->ref_last &&
+ parent->ofs_first > parent->ofs_last) {
+ /*
+ * This parent has run out of children, so move
+ * it to done_head.
+ */
+ list_del(&parent->list);
+ list_add(&parent->list, &done_head);
+ }
+
+ /*
+ * Ensure that the parent has data, since we will need
+ * it later.
+ *
+ * NEEDSWORK: If parent data needs to be reloaded, this
+ * prolongs the time that the current thread spends in
+ * the mutex. A mitigating factor is that parent data
+ * needs to be reloaded only if the delta base cache
+ * limit is exceeded, so in the typical case, this does
+ * not happen.
+ */
+ get_base_data(parent);
+ parent->retain_data++;
}
- i = nr_dispatched++;
work_unlock();
- resolve_base(&objects[i]);
+ if (parent) {
+ child = resolve_delta(child_obj, parent);
+ if (!child->children_remaining)
+ FREE_AND_NULL(child->data);
+ } else {
+ child = make_base(child_obj, NULL);
+ if (child->children_remaining) {
+ /*
+ * Since this child has its own delta children,
+ * we will need this data in the future.
+ * Inflate now so that future iterations will
+ * have access to this object's data while
+ * outside the work mutex.
+ */
+ child->data = get_data_from_pack(child_obj);
+ child->size = child_obj->size;
+ }
+ }
+
+ work_lock();
+ if (parent)
+ parent->retain_data--;
+ if (child->data) {
+ /*
+ * This child has its own children, so add it to
+ * work_head.
+ */
+ list_add(&child->list, &work_head);
+ base_cache_used += child->size;
+ prune_base_data(NULL);
+ } else {
+ /*
+ * This child does not have its own children. It may be
+ * the last descendant of its ancestors; free those
+ * that we can.
+ */
+ struct base_data *p = parent;
+
+ while (p) {
+ struct base_data *next_p;
+
+ p->children_remaining--;
+ if (p->children_remaining)
+ break;
+
+ next_p = p->base;
+ free_base_data(p);
+ list_del(&p->list);
+ free(p);
+
+ p = next_p;
+ }
+ }
+ work_unlock();
}
return NULL;
}
@@ -1199,6 +1244,7 @@ static void resolve_deltas(void)
nr_ref_deltas + nr_ofs_deltas);
nr_dispatched = 0;
+ base_cache_limit = delta_base_cache_limit * nr_threads;
if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
init_thread();
for (i = 0; i < nr_threads; i++) {
@@ -1213,15 +1259,7 @@ static void resolve_deltas(void)
cleanup_thread();
return;
}
-
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = &objects[i];
-
- if (is_delta_type(obj->type))
- continue;
- resolve_base(obj);
- display_progress(progress, nr_resolved_deltas);
- }
+ threaded_second_pass(&nothread_data);
}
/*
@@ -1376,22 +1414,28 @@ static void fix_unresolved_deltas(struct hashfile *f)
for (i = 0; i < nr_ref_deltas; i++) {
struct ref_delta_entry *d = sorted_by_pos[i];
enum object_type type;
- struct base_data *base_obj = alloc_base_data();
+ void *data;
+ unsigned long size;
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj->data = read_object_file(&d->oid, &type,
- &base_obj->size);
- if (!base_obj->data)
+ data = read_object_file(&d->oid, &type, &size);
+ if (!data)
continue;
if (check_object_signature(the_repository, &d->oid,
- base_obj->data, base_obj->size,
+ data, size,
type_name(type)))
die(_("local object %s is corrupt"), oid_to_hex(&d->oid));
- base_obj->obj = append_obj_to_pack(f, d->oid.hash,
- base_obj->data, base_obj->size, type);
- find_unresolved_deltas(base_obj);
+
+ /*
+ * Add this as an object to the objects array and call
+ * threaded_second_pass() (which will pick up the added
+ * object).
+ */
+ append_obj_to_pack(f, d->oid.hash, data, size, type);
+ threaded_second_pass(NULL);
+
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index bbc9bc78f9..cd3e760541 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -9,6 +9,7 @@
#include "builtin.h"
#include "exec-cmd.h"
#include "parse-options.h"
+#include "worktree.h"
#ifndef DEFAULT_GIT_TEMPLATE_DIR
#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
@@ -364,6 +365,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
if (rename(src, git_dir))
die_errno(_("unable to move %s to %s"), src, git_dir);
+ repair_worktrees(NULL, NULL);
}
write_file(git_link, "gitdir: %s", git_dir);
@@ -640,6 +642,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
if (!git_dir)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+ /*
+ * When --separate-git-dir is used inside a linked worktree, take
+ * care to ensure that the common .git/ directory is relocated, not
+ * the worktree-specific .git/worktrees/<id>/ directory.
+ */
+ if (real_git_dir) {
+ int err;
+ const char *p;
+ struct strbuf sb = STRBUF_INIT;
+
+ p = read_gitfile_gently(git_dir, &err);
+ if (p && get_common_dir(&sb, p)) {
+ struct strbuf mainwt = STRBUF_INIT;
+
+ strbuf_addbuf(&mainwt, &sb);
+ strbuf_strip_suffix(&mainwt, "/.git");
+ if (chdir(mainwt.buf) < 0)
+ die_errno(_("cannot chdir to %s"), mainwt.buf);
+ strbuf_release(&mainwt);
+ git_dir = strbuf_detach(&sb, NULL);
+ }
+ strbuf_release(&sb);
+ }
+
if (is_bare_repository_cfg < 0)
is_bare_repository_cfg = guess_repository_type(git_dir);
diff --git a/builtin/log.c b/builtin/log.c
index b58f8da09e..b8824d898f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -33,7 +33,6 @@
#include "commit-slab.h"
#include "repository.h"
#include "commit-reach.h"
-#include "interdiff.h"
#include "range-diff.h"
#define MAIL_DEFAULT_WRAP 72
@@ -1061,7 +1060,7 @@ static char *find_branch_name(struct rev_info *rev)
return NULL;
ref = rev->cmdline.rev[positive].name;
tip_oid = &rev->cmdline.rev[positive].item->oid;
- if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) &&
+ if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref, 0) &&
skip_prefix(full_ref, "refs/heads/", &v) &&
oideq(tip_oid, &branch_oid))
branch = xstrdup(v);
@@ -1207,7 +1206,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
if (rev->idiff_oid1) {
fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title);
- show_interdiff(rev, 0);
+ show_interdiff(rev->idiff_oid1, rev->idiff_oid2, 0,
+ &rev->diffopt);
}
if (rev->rdiff1) {
@@ -1595,16 +1595,20 @@ static void infer_range_diff_ranges(struct strbuf *r1,
struct commit *head)
{
const char *head_oid = oid_to_hex(&head->object.oid);
+ int prev_is_range = !!strstr(prev, "..");
- if (!strstr(prev, "..")) {
+ if (prev_is_range)
+ strbuf_addstr(r1, prev);
+ else
strbuf_addf(r1, "%s..%s", head_oid, prev);
+
+ if (origin)
+ strbuf_addf(r2, "%s..%s", oid_to_hex(&origin->object.oid), head_oid);
+ else if (prev_is_range)
+ die(_("failed to infer range-diff origin of current series"));
+ else {
+ warning(_("using '%s' as range-diff origin of current series"), prev);
strbuf_addf(r2, "%s..%s", prev, head_oid);
- } else if (!origin) {
- die(_("failed to infer range-diff ranges"));
- } else {
- strbuf_addstr(r1, prev);
- strbuf_addf(r2, "%s..%s",
- oid_to_hex(&origin->object.oid), head_oid);
}
}
diff --git a/builtin/merge.c b/builtin/merge.c
index b9a89ba858..032a8f5434 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -500,7 +500,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
if (!remote_head)
die(_("'%s' does not point to a commit"), remote);
- if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) {
+ if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref, 0) > 0) {
if (starts_with(found_ref, "refs/heads/")) {
strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
oid_to_hex(&branch_head), remote);
diff --git a/builtin/push.c b/builtin/push.c
index bc94078e72..0eeb2c8dd5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -61,26 +61,27 @@ static struct refspec rs = REFSPEC_INIT_PUSH;
static struct string_list push_options_config = STRING_LIST_INIT_DUP;
-static const char *map_refspec(const char *ref,
- struct remote *remote, struct ref *local_refs)
+static void refspec_append_mapped(struct refspec *refspec, const char *ref,
+ struct remote *remote, struct ref *local_refs)
{
const char *branch_name;
struct ref *matched = NULL;
/* Does "ref" uniquely name our ref? */
- if (count_refspec_match(ref, local_refs, &matched) != 1)
- return ref;
+ if (count_refspec_match(ref, local_refs, &matched) != 1) {
+ refspec_append(refspec, ref);
+ return;
+ }
if (remote->push.nr) {
struct refspec_item query;
memset(&query, 0, sizeof(struct refspec_item));
query.src = matched->name;
if (!query_refspecs(&remote->push, &query) && query.dst) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s%s:%s",
- query.force ? "+" : "",
- query.src, query.dst);
- return strbuf_detach(&buf, NULL);
+ refspec_appendf(refspec, "%s%s:%s",
+ query.force ? "+" : "",
+ query.src, query.dst);
+ return;
}
}
@@ -88,14 +89,13 @@ static const char *map_refspec(const char *ref,
skip_prefix(matched->name, "refs/heads/", &branch_name)) {
struct branch *branch = branch_get(branch_name);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s:%s",
- ref, branch->merge[0]->src);
- return strbuf_detach(&buf, NULL);
+ refspec_appendf(refspec, "%s:%s",
+ ref, branch->merge[0]->src);
+ return;
}
}
- return ref;
+ refspec_append(refspec, ref);
}
static void set_refspecs(const char **refs, int nr, const char *repo)
@@ -107,30 +107,26 @@ static void set_refspecs(const char **refs, int nr, const char *repo)
for (i = 0; i < nr; i++) {
const char *ref = refs[i];
if (!strcmp("tag", ref)) {
- struct strbuf tagref = STRBUF_INIT;
if (nr <= ++i)
die(_("tag shorthand without <tag>"));
ref = refs[i];
if (deleterefs)
- strbuf_addf(&tagref, ":refs/tags/%s", ref);
+ refspec_appendf(&rs, ":refs/tags/%s", ref);
else
- strbuf_addf(&tagref, "refs/tags/%s", ref);
- ref = strbuf_detach(&tagref, NULL);
+ refspec_appendf(&rs, "refs/tags/%s", ref);
} else if (deleterefs) {
- struct strbuf delref = STRBUF_INIT;
if (strchr(ref, ':'))
die(_("--delete only accepts plain target ref names"));
- strbuf_addf(&delref, ":%s", ref);
- ref = strbuf_detach(&delref, NULL);
+ refspec_appendf(&rs, ":%s", ref);
} else if (!strchr(ref, ':')) {
if (!remote) {
/* lazily grab remote and local_refs */
remote = remote_get(repo);
local_refs = get_local_heads();
}
- ref = map_refspec(ref, remote, local_refs);
- }
- refspec_append(&rs, ref);
+ refspec_append_mapped(&rs, ref, remote, local_refs);
+ } else
+ refspec_append(&rs, ref);
}
}
@@ -192,8 +188,6 @@ static const char message_detached_head_die[] =
static void setup_push_upstream(struct remote *remote, struct branch *branch,
int triangular, int simple)
{
- struct strbuf refspec = STRBUF_INIT;
-
if (!branch)
die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
@@ -219,18 +213,14 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch,
die_push_simple(branch, remote);
}
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src);
- refspec_append(&rs, refspec.buf);
+ refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src);
}
static void setup_push_current(struct remote *remote, struct branch *branch)
{
- struct strbuf refspec = STRBUF_INIT;
-
if (!branch)
die(_(message_detached_head_die), remote->name);
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname);
- refspec_append(&rs, refspec.buf);
+ refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname);
}
static int is_workflow_triangular(struct remote *remote)
diff --git a/builtin/repack.c b/builtin/repack.c
index 04c5ceaf7e..01e7767c79 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -133,7 +133,11 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list,
static void remove_redundant_pack(const char *dir_name, const char *base_name)
{
struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s/%s.pack", dir_name, base_name);
+ struct multi_pack_index *m = get_local_multi_pack_index(the_repository);
+ strbuf_addf(&buf, "%s.pack", base_name);
+ if (m && midx_contains_pack(m, buf.buf))
+ clear_midx_file(the_repository);
+ strbuf_insertf(&buf, 0, "%s/", dir_name);
unlink_pack_path(buf.buf, 1);
strbuf_release(&buf);
}
@@ -286,7 +290,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
int keep_unreachable = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
int no_update_server_info = 0;
- int midx_cleared = 0;
struct pack_objects_args po_args = {NULL};
struct option builtin_repack_options[] = {
@@ -439,11 +442,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
char *fname, *fname_old;
- if (!midx_cleared) {
- clear_midx_file(the_repository);
- midx_cleared = 1;
- }
-
fname = mkpathdup("%s/pack-%s%s", packdir,
item->string, exts[ext].name);
if (!file_exists(fname)) {
diff --git a/builtin/reset.c b/builtin/reset.c
index 8ae69d6f2b..c635b062c3 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -423,7 +423,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
char *ref = NULL;
int err;
- dwim_ref(rev, strlen(rev), &dummy, &ref);
+ dwim_ref(rev, strlen(rev), &dummy, &ref, 0);
if (ref && !starts_with(ref, "refs/"))
ref = NULL;
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 669dd2fd6f..ed200c8af1 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -136,7 +136,7 @@ static void show_rev(int type, const struct object_id *oid, const char *name)
struct object_id discard;
char *full;
- switch (dwim_ref(name, strlen(name), &discard, &full)) {
+ switch (dwim_ref(name, strlen(name), &discard, &full, 0)) {
case 0:
/*
* Not found -- not a ref. We could
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 7eae5f3801..d6d2dabeca 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -741,7 +741,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
die(Q_("only %d entry can be shown at one time.",
"only %d entries can be shown at one time.",
MAX_REVS), MAX_REVS);
- if (!dwim_ref(*av, strlen(*av), &oid, &ref))
+ if (!dwim_ref(*av, strlen(*av), &oid, &ref, 0))
die(_("no such ref %s"), *av);
/* Has the base been specified? */
diff --git a/builtin/stash.c b/builtin/stash.c
index 4bdfaf8397..3f811f3050 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -185,7 +185,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
end_of_rev = strchrnul(revision, '@');
strbuf_add(&symbolic, revision, end_of_rev - revision);
- ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+ ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref, 0);
strbuf_release(&symbolic);
switch (ret) {
case 0: /* Not found, but valid ref */
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a59d8e4bda..de5ad73bb8 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -612,7 +612,6 @@ struct init_cb {
const char *prefix;
unsigned int flags;
};
-
#define INIT_CB_INIT { NULL, 0 }
static void init_submodule(const char *path, const char *prefix,
@@ -742,7 +741,6 @@ struct status_cb {
const char *prefix;
unsigned int flags;
};
-
#define STATUS_CB_INIT { NULL, 0 }
static void print_status(unsigned int flags, char state, const char *path,
@@ -929,11 +927,437 @@ static int module_name(int argc, const char **argv, const char *prefix)
return 0;
}
+struct module_cb {
+ unsigned int mod_src;
+ unsigned int mod_dst;
+ struct object_id oid_src;
+ struct object_id oid_dst;
+ char status;
+ const char *sm_path;
+};
+#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL }
+
+struct module_cb_list {
+ struct module_cb **entries;
+ int alloc, nr;
+};
+#define MODULE_CB_LIST_INIT { NULL, 0, 0 }
+
+struct summary_cb {
+ int argc;
+ const char **argv;
+ const char *prefix;
+ unsigned int cached: 1;
+ unsigned int for_status: 1;
+ unsigned int files: 1;
+ int summary_limit;
+};
+#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 }
+
+enum diff_cmd {
+ DIFF_INDEX,
+ DIFF_FILES
+};
+
+static char *verify_submodule_committish(const char *sm_path,
+ const char *committish)
+{
+ struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+ struct strbuf result = STRBUF_INIT;
+
+ cp_rev_parse.git_cmd = 1;
+ cp_rev_parse.dir = sm_path;
+ prepare_submodule_repo_env(&cp_rev_parse.env_array);
+ strvec_pushl(&cp_rev_parse.args, "rev-parse", "-q", "--short", NULL);
+ strvec_pushf(&cp_rev_parse.args, "%s^0", committish);
+ strvec_push(&cp_rev_parse.args, "--");
+
+ if (capture_command(&cp_rev_parse, &result, 0))
+ return NULL;
+
+ strbuf_trim_trailing_newline(&result);
+ return strbuf_detach(&result, NULL);
+}
+
+static void print_submodule_summary(struct summary_cb *info, char *errmsg,
+ int total_commits, const char *displaypath,
+ const char *src_abbrev, const char *dst_abbrev,
+ struct module_cb *p)
+{
+ if (p->status == 'T') {
+ if (S_ISGITLINK(p->mod_dst))
+ printf(_("* %s %s(blob)->%s(submodule)"),
+ displaypath, src_abbrev, dst_abbrev);
+ else
+ printf(_("* %s %s(submodule)->%s(blob)"),
+ displaypath, src_abbrev, dst_abbrev);
+ } else {
+ printf("* %s %s...%s",
+ displaypath, src_abbrev, dst_abbrev);
+ }
+
+ if (total_commits < 0)
+ printf(":\n");
+ else
+ printf(" (%d):\n", total_commits);
+
+ if (errmsg) {
+ printf(_("%s"), errmsg);
+ } else if (total_commits > 0) {
+ struct child_process cp_log = CHILD_PROCESS_INIT;
+
+ cp_log.git_cmd = 1;
+ cp_log.dir = p->sm_path;
+ prepare_submodule_repo_env(&cp_log.env_array);
+ strvec_pushl(&cp_log.args, "log", NULL);
+
+ if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) {
+ if (info->summary_limit > 0)
+ strvec_pushf(&cp_log.args, "-%d",
+ info->summary_limit);
+
+ strvec_pushl(&cp_log.args, "--pretty= %m %s",
+ "--first-parent", NULL);
+ strvec_pushf(&cp_log.args, "%s...%s",
+ src_abbrev, dst_abbrev);
+ } else if (S_ISGITLINK(p->mod_dst)) {
+ strvec_pushl(&cp_log.args, "--pretty= > %s",
+ "-1", dst_abbrev, NULL);
+ } else {
+ strvec_pushl(&cp_log.args, "--pretty= < %s",
+ "-1", src_abbrev, NULL);
+ }
+ run_command(&cp_log);
+ }
+ printf("\n");
+}
+
+static void generate_submodule_summary(struct summary_cb *info,
+ struct module_cb *p)
+{
+ char *displaypath, *src_abbrev = NULL, *dst_abbrev;
+ int missing_src = 0, missing_dst = 0;
+ char *errmsg = NULL;
+ int total_commits = -1;
+
+ if (!info->cached && oideq(&p->oid_dst, &null_oid)) {
+ if (S_ISGITLINK(p->mod_dst)) {
+ struct ref_store *refs = get_submodule_ref_store(p->sm_path);
+ if (refs)
+ refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst);
+ } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) {
+ struct stat st;
+ int fd = open(p->sm_path, O_RDONLY);
+
+ if (fd < 0 || fstat(fd, &st) < 0 ||
+ index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB,
+ p->sm_path, 0))
+ error(_("couldn't hash object from '%s'"), p->sm_path);
+ } else {
+ /* for a submodule removal (mode:0000000), don't warn */
+ if (p->mod_dst)
+ warning(_("unexpected mode %o\n"), p->mod_dst);
+ }
+ }
+
+ if (S_ISGITLINK(p->mod_src)) {
+ if (p->status != 'D')
+ src_abbrev = verify_submodule_committish(p->sm_path,
+ oid_to_hex(&p->oid_src));
+ if (!src_abbrev) {
+ missing_src = 1;
+ /*
+ * As `rev-parse` failed, we fallback to getting
+ * the abbreviated hash using oid_src. We do
+ * this as we might still need the abbreviated
+ * hash in cases like a submodule type change, etc.
+ */
+ src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+ }
+ } else {
+ /*
+ * The source does not point to a submodule.
+ * So, we fallback to getting the abbreviation using
+ * oid_src as we might still need the abbreviated
+ * hash in cases like submodule add, etc.
+ */
+ src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+ }
+
+ if (S_ISGITLINK(p->mod_dst)) {
+ dst_abbrev = verify_submodule_committish(p->sm_path,
+ oid_to_hex(&p->oid_dst));
+ if (!dst_abbrev) {
+ missing_dst = 1;
+ /*
+ * As `rev-parse` failed, we fallback to getting
+ * the abbreviated hash using oid_dst. We do
+ * this as we might still need the abbreviated
+ * hash in cases like a submodule type change, etc.
+ */
+ dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+ }
+ } else {
+ /*
+ * The destination does not point to a submodule.
+ * So, we fallback to getting the abbreviation using
+ * oid_dst as we might still need the abbreviated
+ * hash in cases like a submodule removal, etc.
+ */
+ dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+ }
+
+ displaypath = get_submodule_displaypath(p->sm_path, info->prefix);
+
+ if (!missing_src && !missing_dst) {
+ struct child_process cp_rev_list = CHILD_PROCESS_INIT;
+ struct strbuf sb_rev_list = STRBUF_INIT;
+
+ strvec_pushl(&cp_rev_list.args, "rev-list",
+ "--first-parent", "--count", NULL);
+ if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst))
+ strvec_pushf(&cp_rev_list.args, "%s...%s",
+ src_abbrev, dst_abbrev);
+ else
+ strvec_push(&cp_rev_list.args, S_ISGITLINK(p->mod_src) ?
+ src_abbrev : dst_abbrev);
+ strvec_push(&cp_rev_list.args, "--");
+
+ cp_rev_list.git_cmd = 1;
+ cp_rev_list.dir = p->sm_path;
+ prepare_submodule_repo_env(&cp_rev_list.env_array);
+
+ if (!capture_command(&cp_rev_list, &sb_rev_list, 0))
+ total_commits = atoi(sb_rev_list.buf);
+
+ strbuf_release(&sb_rev_list);
+ } else {
+ /*
+ * Don't give error msg for modification whose dst is not
+ * submodule, i.e., deleted or changed to blob
+ */
+ if (S_ISGITLINK(p->mod_dst)) {
+ struct strbuf errmsg_str = STRBUF_INIT;
+ if (missing_src && missing_dst) {
+ strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commits %s and %s\n",
+ displaypath, oid_to_hex(&p->oid_src),
+ oid_to_hex(&p->oid_dst));
+ } else {
+ strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commit %s\n",
+ displaypath, missing_src ?
+ oid_to_hex(&p->oid_src) :
+ oid_to_hex(&p->oid_dst));
+ }
+ errmsg = strbuf_detach(&errmsg_str, NULL);
+ }
+ }
+
+ print_submodule_summary(info, errmsg, total_commits,
+ displaypath, src_abbrev,
+ dst_abbrev, p);
+
+ free(displaypath);
+ free(src_abbrev);
+ free(dst_abbrev);
+}
+
+static void prepare_submodule_summary(struct summary_cb *info,
+ struct module_cb_list *list)
+{
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ const struct submodule *sub;
+ struct module_cb *p = list->entries[i];
+ struct strbuf sm_gitdir = STRBUF_INIT;
+
+ if (p->status == 'D' || p->status == 'T') {
+ generate_submodule_summary(info, p);
+ continue;
+ }
+
+ if (info->for_status && p->status != 'A' &&
+ (sub = submodule_from_path(the_repository,
+ &null_oid, p->sm_path))) {
+ char *config_key = NULL;
+ const char *value;
+ int ignore_all = 0;
+
+ config_key = xstrfmt("submodule.%s.ignore",
+ sub->name);
+ if (!git_config_get_string_tmp(config_key, &value))
+ ignore_all = !strcmp(value, "all");
+ else if (sub->ignore)
+ ignore_all = !strcmp(sub->ignore, "all");
+
+ free(config_key);
+ if (ignore_all)
+ continue;
+ }
+
+ /* Also show added or modified modules which are checked out */
+ strbuf_addstr(&sm_gitdir, p->sm_path);
+ if (is_nonbare_repository_dir(&sm_gitdir))
+ generate_submodule_summary(info, p);
+ strbuf_release(&sm_gitdir);
+ }
+}
+
+static void submodule_summary_callback(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+ struct module_cb_list *list = data;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ struct module_cb *temp;
+
+ if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode))
+ continue;
+ temp = (struct module_cb*)malloc(sizeof(struct module_cb));
+ temp->mod_src = p->one->mode;
+ temp->mod_dst = p->two->mode;
+ temp->oid_src = p->one->oid;
+ temp->oid_dst = p->two->oid;
+ temp->status = p->status;
+ temp->sm_path = xstrdup(p->one->path);
+
+ ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+ list->entries[list->nr++] = temp;
+ }
+}
+
+static const char *get_diff_cmd(enum diff_cmd diff_cmd)
+{
+ switch (diff_cmd) {
+ case DIFF_INDEX: return "diff-index";
+ case DIFF_FILES: return "diff-files";
+ default: BUG("bad diff_cmd value %d", diff_cmd);
+ }
+}
+
+static int compute_summary_module_list(struct object_id *head_oid,
+ struct summary_cb *info,
+ enum diff_cmd diff_cmd)
+{
+ struct strvec diff_args = STRVEC_INIT;
+ struct rev_info rev;
+ struct module_cb_list list = MODULE_CB_LIST_INIT;
+
+ strvec_push(&diff_args, get_diff_cmd(diff_cmd));
+ if (info->cached)
+ strvec_push(&diff_args, "--cached");
+ strvec_pushl(&diff_args, "--ignore-submodules=dirty", "--raw", NULL);
+ if (head_oid)
+ strvec_push(&diff_args, oid_to_hex(head_oid));
+ strvec_push(&diff_args, "--");
+ if (info->argc)
+ strvec_pushv(&diff_args, info->argv);
+
+ git_config(git_diff_basic_config, NULL);
+ init_revisions(&rev, info->prefix);
+ rev.abbrev = 0;
+ precompose_argv(diff_args.nr, diff_args.v);
+ setup_revisions(diff_args.nr, diff_args.v, &rev, NULL);
+ rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = submodule_summary_callback;
+ rev.diffopt.format_callback_data = &list;
+
+ if (!info->cached) {
+ if (diff_cmd == DIFF_INDEX)
+ setup_work_tree();
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ } else if (read_cache() < 0) {
+ perror("read_cache");
+ return -1;
+ }
+
+ if (diff_cmd == DIFF_INDEX)
+ run_diff_index(&rev, info->cached);
+ else
+ run_diff_files(&rev, 0);
+ prepare_submodule_summary(info, &list);
+ strvec_clear(&diff_args);
+ return 0;
+}
+
+static int module_summary(int argc, const char **argv, const char *prefix)
+{
+ struct summary_cb info = SUMMARY_CB_INIT;
+ int cached = 0;
+ int for_status = 0;
+ int files = 0;
+ int summary_limit = -1;
+ enum diff_cmd diff_cmd = DIFF_INDEX;
+ struct object_id head_oid;
+ int ret;
+
+ struct option module_summary_options[] = {
+ OPT_BOOL(0, "cached", &cached,
+ N_("use the commit stored in the index instead of the submodule HEAD")),
+ OPT_BOOL(0, "files", &files,
+ N_("to compare the commit in the index with that in the submodule HEAD")),
+ OPT_BOOL(0, "for-status", &for_status,
+ N_("skip submodules with 'ignore_config' value set to 'all'")),
+ OPT_INTEGER('n', "summary-limit", &summary_limit,
+ N_("limit the summary size")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper summary [<options>] [commit] [--] [<path>]"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, module_summary_options,
+ git_submodule_helper_usage, 0);
+
+ if (!summary_limit)
+ return 0;
+
+ if (!get_oid(argc ? argv[0] : "HEAD", &head_oid)) {
+ if (argc) {
+ argv++;
+ argc--;
+ }
+ } else if (!argc || !strcmp(argv[0], "HEAD")) {
+ /* before the first commit: compare with an empty tree */
+ oidcpy(&head_oid, the_hash_algo->empty_tree);
+ if (argc) {
+ argv++;
+ argc--;
+ }
+ } else {
+ if (get_oid("HEAD", &head_oid))
+ die(_("could not fetch a revision for HEAD"));
+ }
+
+ if (files) {
+ if (cached)
+ die(_("--cached and --files are mutually exclusive"));
+ diff_cmd = DIFF_FILES;
+ }
+
+ info.argc = argc;
+ info.argv = argv;
+ info.prefix = prefix;
+ info.cached = !!cached;
+ info.files = !!files;
+ info.for_status = !!for_status;
+ info.summary_limit = summary_limit;
+
+ ret = compute_summary_module_list((diff_cmd == DIFF_INDEX) ? &head_oid : NULL,
+ &info, diff_cmd);
+ return ret;
+}
+
struct sync_cb {
const char *prefix;
unsigned int flags;
};
-
#define SYNC_CB_INIT { NULL, 0 }
static void sync_submodule(const char *path, const char *prefix,
@@ -2344,6 +2768,7 @@ static struct cmd_struct commands[] = {
{"print-default-remote", print_default_remote, 0},
{"sync", module_sync, SUPPORT_SUPER_PREFIX},
{"deinit", module_deinit, 0},
+ {"summary", module_summary, SUPPORT_SUPER_PREFIX},
{"remote-branch", resolve_remote_submodule_branch, 0},
{"push-check", push_check, 0},
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 378f332b5d..99abaeec6c 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -555,7 +555,7 @@ static int add(int ac, const char **av, const char *prefix)
N_("create a new branch")),
OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
N_("create or reset a branch")),
- OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
+ OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
@@ -924,7 +924,6 @@ static int move_worktree(int ac, const char **av, const char *prefix)
static void check_clean_worktree(struct worktree *wt,
const char *original_path)
{
- struct strvec child_env = STRVEC_INIT;
struct child_process cp;
char buf[1];
int ret;
@@ -935,15 +934,14 @@ static void check_clean_worktree(struct worktree *wt,
*/
validate_no_submodules(wt);
- strvec_pushf(&child_env, "%s=%s/.git",
+ child_process_init(&cp);
+ strvec_pushf(&cp.env_array, "%s=%s/.git",
GIT_DIR_ENVIRONMENT, wt->path);
- strvec_pushf(&child_env, "%s=%s",
+ strvec_pushf(&cp.env_array, "%s=%s",
GIT_WORK_TREE_ENVIRONMENT, wt->path);
- memset(&cp, 0, sizeof(cp));
strvec_pushl(&cp.args, "status",
"--porcelain", "--ignore-submodules=none",
NULL);
- cp.env = child_env.v;
cp.git_cmd = 1;
cp.dir = wt->path;
cp.out = -1;
@@ -1030,6 +1028,34 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
return ret;
}
+static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)
+{
+ if (!iserr) {
+ printf_ln(_("repair: %s: %s"), msg, path);
+ } else {
+ int *exit_status = (int *)cb_data;
+ fprintf_ln(stderr, _("error: %s: %s"), msg, path);
+ *exit_status = 1;
+ }
+}
+
+static int repair(int ac, const char **av, const char *prefix)
+{
+ const char **p;
+ const char *self[] = { ".", NULL };
+ struct option options[] = {
+ OPT_END()
+ };
+ int rc = 0;
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ repair_worktrees(report_repair, &rc);
+ p = ac > 0 ? av : self;
+ for (; *p; p++)
+ repair_worktree_at_path(*p, report_repair, &rc);
+ return rc;
+}
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
@@ -1056,5 +1082,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
return move_worktree(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "remove"))
return remove_worktree(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "repair"))
+ return repair(ac - 1, av + 1, prefix);
usage_with_options(worktree_usage, options);
}
diff --git a/bundle.c b/bundle.c
index 995a940dfd..cb0e5931ac 100644
--- a/bundle.c
+++ b/bundle.c
@@ -403,7 +403,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)
if (e->item->flags & UNINTERESTING)
continue;
- if (dwim_ref(e->name, strlen(e->name), &oid, &ref) != 1)
+ if (dwim_ref(e->name, strlen(e->name), &oid, &ref, 0) != 1)
goto skip_write_ref;
if (read_ref_full(e->name, RESOLVE_REF_READING, &oid, &flag))
flag = 0;
diff --git a/cache.h b/cache.h
index 4cad61ffa4..cee8aa5dc3 100644
--- a/cache.h
+++ b/cache.h
@@ -1557,21 +1557,32 @@ int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end);
*
* If the input was ok but there are not N branch switches in the
* reflog, it returns 0.
- *
- * If "allowed" is non-zero, it is a treated as a bitfield of allowable
- * expansions: local branches ("refs/heads/"), remote branches
- * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
- * allowed, even ones to refs outside of those namespaces.
*/
#define INTERPRET_BRANCH_LOCAL (1<<0)
#define INTERPRET_BRANCH_REMOTE (1<<1)
#define INTERPRET_BRANCH_HEAD (1<<2)
+struct interpret_branch_name_options {
+ /*
+ * If "allowed" is non-zero, it is a treated as a bitfield of allowable
+ * expansions: local branches ("refs/heads/"), remote branches
+ * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
+ * allowed, even ones to refs outside of those namespaces.
+ */
+ unsigned allowed;
+
+ /*
+ * If ^{upstream} or ^{push} (or equivalent) is requested, and the
+ * branch in question does not have such a reference, return -1 instead
+ * of die()-ing.
+ */
+ unsigned nonfatal_dangling_mark : 1;
+};
int repo_interpret_branch_name(struct repository *r,
const char *str, int len,
struct strbuf *buf,
- unsigned allowed);
-#define interpret_branch_name(str, len, buf, allowed) \
- repo_interpret_branch_name(the_repository, str, len, buf, allowed)
+ const struct interpret_branch_name_options *options);
+#define interpret_branch_name(str, len, buf, options) \
+ repo_interpret_branch_name(the_repository, str, len, buf, options)
int validate_headref(const char *ref);
diff --git a/commit.c b/commit.c
index bc741e78ad..f53429c0ac 100644
--- a/commit.c
+++ b/commit.c
@@ -921,7 +921,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
struct commit *ret = NULL;
char *full_refname;
- switch (dwim_ref(refname, strlen(refname), &oid, &full_refname)) {
+ switch (dwim_ref(refname, strlen(refname), &oid, &full_refname, 0)) {
case 0:
die("No such ref: '%s'", refname);
case 1:
diff --git a/commit.h b/commit.h
index bd73b84472..e6f8f7c26f 100644
--- a/commit.h
+++ b/commit.h
@@ -95,7 +95,6 @@ static inline int parse_commit_no_graph(struct commit *commit)
#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
#define parse_commit_internal(item, quiet, use) repo_parse_commit_internal(the_repository, item, quiet, use)
-#define parse_commit_gently(item, quiet) repo_parse_commit_gently(the_repository, item, quiet)
#define parse_commit(item) repo_parse_commit(the_repository, item)
#endif
diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index 42292e7c09..51fb083dbb 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -26,8 +26,8 @@ The Steps to Build Git with VS2015 or VS2017 from the command line.
Use ONE of the following forms which should match how you want to
compile git.exe.
- $ ./compat/vcbuild/vcpkg_copy_packages.bat debug
- $ ./compat/vcbuild/vcpkg_copy_packages.bat release
+ $ ./compat/vcbuild/vcpkg_copy_dlls.bat debug
+ $ ./compat/vcbuild/vcpkg_copy_dlls.bat release
3. Build git using MSVC from an SDK bash window using one of the
following commands:
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index 61ad084a7b..df167d1e1a 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -66,7 +66,7 @@ while (@ARGV) {
}
push(@args, $lib);
} elsif ("$arg" eq "-lexpat") {
- push(@args, "expat.lib");
+ push(@args, "libexpat.lib");
} elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
$arg =~ s/^-L/-LIBPATH:/;
push(@lflags, $arg);
diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 5c666f9ac0..d2584450ba 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -80,6 +80,7 @@ sub createProject {
$libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
$libs_debug = $libs_release;
$libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
+ $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g;
$libs_debug =~ s/libcurl\.lib/libcurl-d\.lib/g;
}
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 070978506a..2ff9620459 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -349,7 +349,7 @@ sub handleLinkLine
} elsif ("$part" eq "-lcurl") {
push(@libs, "libcurl.lib");
} elsif ("$part" eq "-lexpat") {
- push(@libs, "expat.lib");
+ push(@libs, "libexpat.lib");
} elsif ("$part" eq "-liconv") {
push(@libs, "libiconv.lib");
} elsif ($part =~ /^[-\/]/) {
diff --git a/diff-lib.c b/diff-lib.c
index 50521e2093..346fdcf0b0 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -405,14 +405,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
/* if the entry is not checked out, don't examine work tree */
cached = o->index_only ||
(idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
- /*
- * Backward compatibility wart - "diff-index -m" does
- * not mean "do not ignore merges", but "match_missing".
- *
- * But with the revision flag parsing, that's found in
- * "!revs->ignore_merges".
- */
- match_missing = !revs->ignore_merges;
+
+ match_missing = revs->match_missing;
if (cached && idx && ce_stage(idx)) {
struct diff_filepair *pair;
@@ -571,3 +565,28 @@ int index_differs_from(struct repository *r,
object_array_clear(&rev.pending);
return (rev.diffopt.flags.has_changes != 0);
}
+
+static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data)
+{
+ return data;
+}
+
+void show_interdiff(const struct object_id *oid1, const struct object_id *oid2,
+ int indent, struct diff_options *diffopt)
+{
+ struct diff_options opts;
+ struct strbuf prefix = STRBUF_INIT;
+
+ memcpy(&opts, diffopt, sizeof(opts));
+ opts.output_format = DIFF_FORMAT_PATCH;
+ opts.output_prefix = idiff_prefix_cb;
+ strbuf_addchars(&prefix, ' ', indent);
+ opts.output_prefix_data = &prefix;
+ diff_setup_done(&opts);
+
+ diff_tree_oid(oid1, oid2, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+
+ strbuf_release(&prefix);
+}
diff --git a/diff.c b/diff.c
index 0299a73079..ee8e8189e9 100644
--- a/diff.c
+++ b/diff.c
@@ -482,14 +482,14 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
static char *quote_two(const char *one, const char *two)
{
- int need_one = quote_c_style(one, NULL, NULL, 1);
- int need_two = quote_c_style(two, NULL, NULL, 1);
+ int need_one = quote_c_style(one, NULL, NULL, CQUOTE_NODQ);
+ int need_two = quote_c_style(two, NULL, NULL, CQUOTE_NODQ);
struct strbuf res = STRBUF_INIT;
if (need_one + need_two) {
strbuf_addch(&res, '"');
- quote_c_style(one, &res, NULL, 1);
- quote_c_style(two, &res, NULL, 1);
+ quote_c_style(one, &res, NULL, CQUOTE_NODQ);
+ quote_c_style(two, &res, NULL, CQUOTE_NODQ);
strbuf_addch(&res, '"');
} else {
strbuf_addstr(&res, one);
@@ -3432,7 +3432,7 @@ static void builtin_diff(const char *name_a,
if (o->submodule_format == DIFF_SUBMODULE_LOG &&
(!one->mode || S_ISGITLINK(one->mode)) &&
(!two->mode || S_ISGITLINK(two->mode))) {
- show_submodule_summary(o, one->path ? one->path : two->path,
+ show_submodule_diff_summary(o, one->path ? one->path : two->path,
&one->oid, &two->oid,
two->dirty_submodule);
return;
diff --git a/diff.h b/diff.h
index e0c0af6286..49242d2733 100644
--- a/diff.h
+++ b/diff.h
@@ -601,6 +601,13 @@ int index_differs_from(struct repository *r, const char *def,
int ita_invisible_in_index);
/*
+ * Emit an interdiff of two object ID's to 'diff_options.file' optionally
+ * indented by 'indent' spaces.
+ */
+void show_interdiff(const struct object_id *, const struct object_id *,
+ int indent, struct diff_options *);
+
+/*
* Fill the contents of the filespec "df", respecting any textconv defined by
* its userdiff driver. The "driver" parameter must come from a
* previous call to get_textconv(), and therefore should either be NULL or have
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index f36c0078ac..b6cdcfef61 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -714,7 +714,7 @@ sub parse_diff {
if (defined $patch_mode_revision) {
push @diff_cmd, get_diff_reference($patch_mode_revision);
}
- my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
+ my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path);
my @colored = ();
if ($diff_use_color) {
my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
diff --git a/git-submodule.sh b/git-submodule.sh
index 43eb6051d2..6fb12585cb 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -59,31 +59,6 @@ die_if_unmatched ()
fi
}
-#
-# Print a submodule configuration setting
-#
-# $1 = submodule name
-# $2 = option name
-# $3 = default value
-#
-# Checks in the usual git-config places first (for overrides),
-# otherwise it falls back on .gitmodules. This allows you to
-# distribute project-wide defaults in .gitmodules, while still
-# customizing individual repositories if necessary. If the option is
-# not in .gitmodules either, print a default value.
-#
-get_submodule_config () {
- name="$1"
- option="$2"
- default="$3"
- value=$(git config submodule."$name"."$option")
- if test -z "$value"
- then
- value=$(git submodule--helper config submodule."$name"."$option")
- fi
- printf '%s' "${value:-$default}"
-}
-
isnumber()
{
n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
@@ -831,166 +806,7 @@ cmd_summary() {
shift
done
- test $summary_limit = 0 && return
-
- if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"})
- then
- head=$rev
- test $# = 0 || shift
- elif test -z "$1" || test "$1" = "HEAD"
- then
- # before the first commit: compare with an empty tree
- head=$(git hash-object -w -t tree --stdin </dev/null)
- test -z "$1" || shift
- else
- head="HEAD"
- fi
-
- if [ -n "$files" ]
- then
- test -n "$cached" &&
- die "$(gettext "The --cached option cannot be used with the --files option")"
- diff_cmd=diff-files
- head=
- fi
-
- cd_to_toplevel
- eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
- # Get modified modules cared by user
- modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
- sane_egrep '^:([0-7]* )?160000' |
- while read -r mod_src mod_dst sha1_src sha1_dst status sm_path
- do
- # Always show modules deleted or type-changed (blob<->module)
- if test "$status" = D || test "$status" = T
- then
- printf '%s\n' "$sm_path"
- continue
- fi
- # Respect the ignore setting for --for-status.
- if test -n "$for_status"
- then
- name=$(git submodule--helper name "$sm_path")
- ignore_config=$(get_submodule_config "$name" ignore none)
- test $status != A && test $ignore_config = all && continue
- fi
- # Also show added or modified modules which are checked out
- GIT_DIR="$sm_path/.git" git rev-parse --git-dir >/dev/null 2>&1 &&
- printf '%s\n' "$sm_path"
- done
- )
-
- test -z "$modules" && return
-
- git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules |
- sane_egrep '^:([0-7]* )?160000' |
- cut -c2- |
- while read -r mod_src mod_dst sha1_src sha1_dst status name
- do
- if test -z "$cached" &&
- is_zero_oid $sha1_dst
- then
- case "$mod_dst" in
- 160000)
- sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
- ;;
- 100644 | 100755 | 120000)
- sha1_dst=$(git hash-object $name)
- ;;
- 000000)
- ;; # removed
- *)
- # unexpected type
- eval_gettextln "unexpected mode \$mod_dst" >&2
- continue ;;
- esac
- fi
- missing_src=
- missing_dst=
-
- test $mod_src = 160000 &&
- ! GIT_DIR="$name/.git" git rev-parse -q --verify $sha1_src^0 >/dev/null &&
- missing_src=t
-
- test $mod_dst = 160000 &&
- ! GIT_DIR="$name/.git" git rev-parse -q --verify $sha1_dst^0 >/dev/null &&
- missing_dst=t
-
- display_name=$(git submodule--helper relative-path "$name" "$wt_prefix")
-
- total_commits=
- case "$missing_src,$missing_dst" in
- t,)
- errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_src")"
- ;;
- ,t)
- errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_dst")"
- ;;
- t,t)
- errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst")"
- ;;
- *)
- errmsg=
- total_commits=$(
- if test $mod_src = 160000 && test $mod_dst = 160000
- then
- range="$sha1_src...$sha1_dst"
- elif test $mod_src = 160000
- then
- range=$sha1_src
- else
- range=$sha1_dst
- fi
- GIT_DIR="$name/.git" \
- git rev-list --first-parent $range -- | wc -l
- )
- total_commits=" ($(($total_commits + 0)))"
- ;;
- esac
-
- sha1_abbr_src=$(GIT_DIR="$name/.git" git rev-parse --short $sha1_src 2>/dev/null ||
- echo $sha1_src | cut -c1-7)
- sha1_abbr_dst=$(GIT_DIR="$name/.git" git rev-parse --short $sha1_dst 2>/dev/null ||
- echo $sha1_dst | cut -c1-7)
-
- if test $status = T
- then
- blob="$(gettext "blob")"
- submodule="$(gettext "submodule")"
- if test $mod_dst = 160000
- then
- echo "* $display_name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
- else
- echo "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
- fi
- else
- echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
- fi
- if test -n "$errmsg"
- then
- # Don't give error msg for modification whose dst is not submodule
- # i.e. deleted or changed to blob
- test $mod_dst = 160000 && echo "$errmsg"
- else
- if test $mod_src = 160000 && test $mod_dst = 160000
- then
- limit=
- test $summary_limit -gt 0 && limit="-$summary_limit"
- GIT_DIR="$name/.git" \
- git log $limit --pretty='format: %m %s' \
- --first-parent $sha1_src...$sha1_dst
- elif test $mod_dst = 160000
- then
- GIT_DIR="$name/.git" \
- git log --pretty='format: > %s' -1 $sha1_dst
- else
- GIT_DIR="$name/.git" \
- git log --pretty='format: < %s' -1 $sha1_src
- fi
- echo
- fi
- echo
- done
+ git ${wt_prefix:+-C "$wt_prefix"} submodule--helper summary ${prefix:+--prefix "$prefix"} ${files:+--files} ${cached:+--cached} ${for_status:+--for-status} ${summary_limit:+-n $summary_limit} -- "$@"
}
#
# List all submodules, prefixed with:
diff --git a/interdiff.c b/interdiff.c
deleted file mode 100644
index c81d680a6c..0000000000
--- a/interdiff.c
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "revision.h"
-#include "interdiff.h"
-
-static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data)
-{
- return data;
-}
-
-void show_interdiff(struct rev_info *rev, int indent)
-{
- struct diff_options opts;
- struct strbuf prefix = STRBUF_INIT;
-
- memcpy(&opts, &rev->diffopt, sizeof(opts));
- opts.output_format = DIFF_FORMAT_PATCH;
- opts.output_prefix = idiff_prefix_cb;
- strbuf_addchars(&prefix, ' ', indent);
- opts.output_prefix_data = &prefix;
- diff_setup_done(&opts);
-
- diff_tree_oid(rev->idiff_oid1, rev->idiff_oid2, "", &opts);
- diffcore_std(&opts);
- diff_flush(&opts);
-
- strbuf_release(&prefix);
-}
diff --git a/interdiff.h b/interdiff.h
deleted file mode 100644
index 01c730a5c9..0000000000
--- a/interdiff.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef INTERDIFF_H
-#define INTERDIFF_H
-
-struct rev_info;
-
-void show_interdiff(struct rev_info *, int indent);
-
-#endif
diff --git a/log-tree.c b/log-tree.c
index 55a68d0c61..cb8942fec1 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -15,7 +15,6 @@
#include "sequencer.h"
#include "line-log.h"
#include "help.h"
-#include "interdiff.h"
#include "range-diff.h"
static struct decoration name_decoration = { "object names" };
@@ -800,7 +799,8 @@ void show_log(struct rev_info *opt)
next_commentary_block(opt, NULL);
fprintf_ln(opt->diffopt.file, "%s", opt->idiff_title);
- show_interdiff(opt, 2);
+ show_interdiff(opt->idiff_oid1, opt->idiff_oid2, 2,
+ &opt->diffopt);
memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff));
}
@@ -917,26 +917,15 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
}
/* More than one parent? */
- if (parents && parents->next) {
+ if (parents->next) {
if (opt->ignore_merges)
return 0;
else if (opt->combine_merges)
return do_diff_combined(opt, commit);
- else if (opt->first_parent_only) {
- /*
- * Generate merge log entry only for the first
- * parent, showing summary diff of the others
- * we merged _in_.
- */
- parse_commit_or_die(parents->item);
- diff_tree_oid(get_commit_tree_oid(parents->item),
- oid, "", &opt->diffopt);
- log_tree_diff_flush(opt);
- return !opt->loginfo;
+ else if (!opt->first_parent_only) {
+ /* If we show multiple diffs, show the parent info */
+ log->parent = parents->item;
}
-
- /* If we show individual diffs, show the parent info */
- log->parent = parents->item;
}
showed_log = 0;
@@ -952,7 +941,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
/* Set up the log info for the next parent, if any.. */
parents = parents->next;
- if (!parents)
+ if (!parents || opt->first_parent_only)
break;
log->parent = parents->item;
opt->loginfo = log;
diff --git a/midx.c b/midx.c
index e9b2e1253a..0de42ffdfb 100644
--- a/midx.c
+++ b/midx.c
@@ -416,8 +416,12 @@ int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, i
m = load_multi_pack_index(object_dir, local);
if (m) {
- m->next = r->objects->multi_pack_index;
- r->objects->multi_pack_index = m;
+ struct multi_pack_index *mp = r->objects->multi_pack_index;
+ if (mp) {
+ m->next = mp->next;
+ mp->next = m;
+ } else
+ r->objects->multi_pack_index = m;
return 1;
}
@@ -428,14 +432,11 @@ static size_t write_midx_header(struct hashfile *f,
unsigned char num_chunks,
uint32_t num_packs)
{
- unsigned char byte_values[4];
-
hashwrite_be32(f, MIDX_SIGNATURE);
- byte_values[0] = MIDX_VERSION;
- byte_values[1] = oid_version();
- byte_values[2] = num_chunks;
- byte_values[3] = 0; /* unused */
- hashwrite(f, byte_values, sizeof(byte_values));
+ hashwrite_u8(f, MIDX_VERSION);
+ hashwrite_u8(f, oid_version());
+ hashwrite_u8(f, num_chunks);
+ hashwrite_u8(f, 0); /* unused */
hashwrite_be32(f, num_packs);
return MIDX_HEADER_SIZE;
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index a7a4964b50..5e998bdaa7 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -503,8 +503,7 @@ static void write_hash_cache(struct hashfile *f,
for (i = 0; i < index_nr; ++i) {
struct object_entry *entry = (struct object_entry *)index[i];
- uint32_t hash_value = htonl(entry->hash);
- hashwrite(f, &hash_value, sizeof(hash_value));
+ hashwrite_be32(f, entry->hash);
}
}
diff --git a/packfile.c b/packfile.c
index 6ab5233613..9ef27508f2 100644
--- a/packfile.c
+++ b/packfile.c
@@ -1027,6 +1027,17 @@ struct multi_pack_index *get_multi_pack_index(struct repository *r)
return r->objects->multi_pack_index;
}
+struct multi_pack_index *get_local_multi_pack_index(struct repository *r)
+{
+ struct multi_pack_index *m = get_multi_pack_index(r);
+
+ /* no need to iterate; we always put the local one first (if any) */
+ if (m && m->local)
+ return m;
+
+ return NULL;
+}
+
struct packed_git *get_all_packs(struct repository *r)
{
struct multi_pack_index *m;
diff --git a/packfile.h b/packfile.h
index 240aa73b95..a58fc738e0 100644
--- a/packfile.h
+++ b/packfile.h
@@ -57,6 +57,7 @@ void install_packed_git(struct repository *r, struct packed_git *pack);
struct packed_git *get_packed_git(struct repository *r);
struct list_head *get_packed_git_mru(struct repository *r);
struct multi_pack_index *get_multi_pack_index(struct repository *r);
+struct multi_pack_index *get_local_multi_pack_index(struct repository *r);
struct packed_git *get_all_packs(struct repository *r);
/*
diff --git a/pretty.c b/pretty.c
index 2a3d46bf42..7a7708a0ea 100644
--- a/pretty.c
+++ b/pretty.c
@@ -839,21 +839,22 @@ static int istitlechar(char c)
(c >= '0' && c <= '9') || c == '.' || c == '_';
}
-static void format_sanitized_subject(struct strbuf *sb, const char *msg)
+void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len)
{
size_t trimlen;
size_t start_len = sb->len;
int space = 2;
+ int i;
- for (; *msg && *msg != '\n'; msg++) {
- if (istitlechar(*msg)) {
+ for (i = 0; i < len; i++) {
+ if (istitlechar(msg[i])) {
if (space == 1)
strbuf_addch(sb, '-');
space = 0;
- strbuf_addch(sb, *msg);
- if (*msg == '.')
- while (*(msg+1) == '.')
- msg++;
+ strbuf_addch(sb, msg[i]);
+ if (msg[i] == '.')
+ while (msg[i+1] == '.')
+ i++;
} else
space |= 1;
}
@@ -1155,7 +1156,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const struct commit *commit = c->commit;
const char *msg = c->message;
struct commit_list *p;
- const char *arg;
+ const char *arg, *eol;
size_t res;
char **slot;
@@ -1405,7 +1406,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
format_subject(sb, msg + c->subject_off, " ");
return 1;
case 'f': /* sanitized subject */
- format_sanitized_subject(sb, msg + c->subject_off);
+ eol = strchrnul(msg + c->subject_off, '\n');
+ format_sanitized_subject(sb, msg + c->subject_off, eol - (msg + c->subject_off));
return 1;
case 'b': /* body */
strbuf_addstr(sb, msg + c->body_off);
diff --git a/pretty.h b/pretty.h
index 071f2fb8e4..7ce6c0b437 100644
--- a/pretty.h
+++ b/pretty.h
@@ -139,4 +139,7 @@ const char *format_subject(struct strbuf *sb, const char *msg,
/* Check if "cmit_fmt" will produce an empty output. */
int commit_format_is_empty(enum cmit_fmt);
+/* Make subject of commit message suitable for filename */
+void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len);
+
#endif /* PRETTY_H */
diff --git a/quote.c b/quote.c
index ced0245e80..69f4ca45da 100644
--- a/quote.c
+++ b/quote.c
@@ -210,7 +210,7 @@ int sq_dequote_to_strvec(char *arg, struct strvec *array)
*/
#define X8(x) x, x, x, x, x, x, x, x
#define X16(x) X8(x), X8(x)
-static signed char const sq_lookup[256] = {
+static signed char const cq_lookup[256] = {
/* 0 1 2 3 4 5 6 7 */
/* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 'a',
/* 0x08 */ 'b', 't', 'n', 'v', 'f', 'r', 1, 1,
@@ -223,9 +223,9 @@ static signed char const sq_lookup[256] = {
/* 0x80 */ /* set to 0 */
};
-static inline int sq_must_quote(char c)
+static inline int cq_must_quote(char c)
{
- return sq_lookup[(unsigned char)c] + quote_path_fully > 0;
+ return cq_lookup[(unsigned char)c] + quote_path_fully > 0;
}
/* returns the longest prefix not needing a quote up to maxlen if positive.
@@ -235,9 +235,9 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen)
{
size_t len;
if (maxlen < 0) {
- for (len = 0; !sq_must_quote(s[len]); len++);
+ for (len = 0; !cq_must_quote(s[len]); len++);
} else {
- for (len = 0; len < maxlen && !sq_must_quote(s[len]); len++);
+ for (len = 0; len < maxlen && !cq_must_quote(s[len]); len++);
}
return len;
}
@@ -256,7 +256,7 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen)
* Return value is the same as in (1).
*/
static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
- struct strbuf *sb, FILE *fp, int no_dq)
+ struct strbuf *sb, FILE *fp, unsigned flags)
{
#undef EMIT
#define EMIT(c) \
@@ -272,6 +272,7 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
count += (l); \
} while (0)
+ int no_dq = !!(flags & CQUOTE_NODQ);
size_t len, count = 0;
const char *p = name;
@@ -291,8 +292,8 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
ch = (unsigned char)*p++;
if (maxlen >= 0)
maxlen -= len + 1;
- if (sq_lookup[ch] >= ' ') {
- EMIT(sq_lookup[ch]);
+ if (cq_lookup[ch] >= ' ') {
+ EMIT(cq_lookup[ch]);
} else {
EMIT(((ch >> 6) & 03) + '0');
EMIT(((ch >> 3) & 07) + '0');
@@ -309,19 +310,21 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
return count;
}
-size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, int nodq)
+size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, unsigned flags)
{
- return quote_c_style_counted(name, -1, sb, fp, nodq);
+ return quote_c_style_counted(name, -1, sb, fp, flags);
}
-void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path, int nodq)
+void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path,
+ unsigned flags)
{
+ int nodq = !!(flags & CQUOTE_NODQ);
if (quote_c_style(prefix, NULL, NULL, 0) ||
quote_c_style(path, NULL, NULL, 0)) {
if (!nodq)
strbuf_addch(sb, '"');
- quote_c_style(prefix, sb, NULL, 1);
- quote_c_style(path, sb, NULL, 1);
+ quote_c_style(prefix, sb, NULL, CQUOTE_NODQ);
+ quote_c_style(path, sb, NULL, CQUOTE_NODQ);
if (!nodq)
strbuf_addch(sb, '"');
} else {
@@ -352,13 +355,25 @@ void write_name_quoted_relative(const char *name, const char *prefix,
}
/* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, const char *prefix,
- struct strbuf *out)
+char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags)
{
struct strbuf sb = STRBUF_INIT;
const char *rel = relative_path(in, prefix, &sb);
+ int force_dq = ((flags & QUOTE_PATH_QUOTE_SP) && strchr(rel, ' '));
+
strbuf_reset(out);
- quote_c_style_counted(rel, strlen(rel), out, NULL, 0);
+
+ /*
+ * If the caller wants us to enclose the output in a dq-pair
+ * whether quote_c_style_counted() needs to, we do it ourselves
+ * and tell quote_c_style_counted() not to.
+ */
+ if (force_dq)
+ strbuf_addch(out, '"');
+ quote_c_style_counted(rel, strlen(rel), out, NULL,
+ force_dq ? CQUOTE_NODQ : 0);
+ if (force_dq)
+ strbuf_addch(out, '"');
strbuf_release(&sb);
return out->buf;
diff --git a/quote.h b/quote.h
index fa09309cf6..4b72a583cf 100644
--- a/quote.h
+++ b/quote.h
@@ -64,16 +64,19 @@ struct strvec;
int sq_dequote_to_strvec(char *arg, struct strvec *);
int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
-size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
-void quote_two_c_style(struct strbuf *, const char *, const char *, int);
+
+/* Bits in the flags parameter to quote_c_style() */
+#define CQUOTE_NODQ 01
+size_t quote_c_style(const char *name, struct strbuf *, FILE *, unsigned);
+void quote_two_c_style(struct strbuf *, const char *, const char *, unsigned);
void write_name_quoted(const char *name, FILE *, int terminator);
void write_name_quoted_relative(const char *name, const char *prefix,
FILE *fp, int terminator);
/* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, const char *prefix,
- struct strbuf *out);
+char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags);
+#define QUOTE_PATH_QUOTE_SP 01
/* quoting as a string literal for other languages */
void perl_quote_buf(struct strbuf *sb, const char *src);
diff --git a/read-cache.c b/read-cache.c
index fa291cdbee..ecf6f68994 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -2101,7 +2101,7 @@ static unsigned long load_cache_entries_threaded(struct index_state *istate, con
nr = 0;
for (j = p->ieot_start; j < p->ieot_start + p->ieot_blocks; j++)
nr += p->ieot->entries[j].nr;
- istate->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool));
+ p->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool));
if (istate->version == 4) {
mem_pool_init(p->ce_mem_pool,
estimate_cache_size_from_compressed(nr));
diff --git a/ref-filter.c b/ref-filter.c
index 8ba0e31915..110bcd741a 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -127,8 +127,8 @@ static struct used_atom {
unsigned int nobracket : 1, push : 1, push_remote : 1;
} remote_ref;
struct {
- enum { C_BARE, C_BODY, C_BODY_DEP, C_LENGTH,
- C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
+ enum { C_BARE, C_BODY, C_BODY_DEP, C_LENGTH, C_LINES,
+ C_SIG, C_SUB, C_SUB_SANITIZE, C_TRAILERS } option;
struct process_trailer_options trailer_opts;
unsigned int nlines;
} contents;
@@ -139,7 +139,10 @@ static struct used_atom {
struct {
enum { O_FULL, O_LENGTH, O_SHORT } option;
unsigned int length;
- } objectname;
+ } oid;
+ struct email_option {
+ enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
+ } email_option;
struct refname_atom refname;
char *head;
} u;
@@ -298,9 +301,12 @@ static int body_atom_parser(const struct ref_format *format, struct used_atom *a
static int subject_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
- if (arg)
- return strbuf_addf_ret(err, -1, _("%%(subject) does not take arguments"));
- atom->u.contents.option = C_SUB;
+ if (!arg)
+ atom->u.contents.option = C_SUB;
+ else if (!strcmp(arg, "sanitize"))
+ atom->u.contents.option = C_SUB_SANITIZE;
+ else
+ return strbuf_addf_ret(err, -1, _("unrecognized %%(subject) argument: %s"), arg);
return 0;
}
@@ -360,22 +366,36 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato
return 0;
}
-static int objectname_atom_parser(const struct ref_format *format, struct used_atom *atom,
- const char *arg, struct strbuf *err)
+static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,
+ const char *arg, struct strbuf *err)
{
if (!arg)
- atom->u.objectname.option = O_FULL;
+ atom->u.oid.option = O_FULL;
else if (!strcmp(arg, "short"))
- atom->u.objectname.option = O_SHORT;
+ atom->u.oid.option = O_SHORT;
else if (skip_prefix(arg, "short=", &arg)) {
- atom->u.objectname.option = O_LENGTH;
- if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
- atom->u.objectname.length == 0)
- return strbuf_addf_ret(err, -1, _("positive value expected objectname:short=%s"), arg);
- if (atom->u.objectname.length < MINIMUM_ABBREV)
- atom->u.objectname.length = MINIMUM_ABBREV;
+ atom->u.oid.option = O_LENGTH;
+ if (strtoul_ui(arg, 10, &atom->u.oid.length) ||
+ atom->u.oid.length == 0)
+ return strbuf_addf_ret(err, -1, _("positive value expected '%s' in %%(%s)"), arg, atom->name);
+ if (atom->u.oid.length < MINIMUM_ABBREV)
+ atom->u.oid.length = MINIMUM_ABBREV;
} else
- return strbuf_addf_ret(err, -1, _("unrecognized %%(objectname) argument: %s"), arg);
+ return strbuf_addf_ret(err, -1, _("unrecognized argument '%s' in %%(%s)"), arg, atom->name);
+ return 0;
+}
+
+static int person_email_atom_parser(const struct ref_format *format, struct used_atom *atom,
+ const char *arg, struct strbuf *err)
+{
+ if (!arg)
+ atom->u.email_option.option = EO_RAW;
+ else if (!strcmp(arg, "trim"))
+ atom->u.email_option.option = EO_TRIM;
+ else if (!strcmp(arg, "localpart"))
+ atom->u.email_option.option = EO_LOCALPART;
+ else
+ return strbuf_addf_ret(err, -1, _("unrecognized email option: %s"), arg);
return 0;
}
@@ -480,25 +500,25 @@ static struct {
{ "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
{ "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
{ "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
- { "objectname", SOURCE_OTHER, FIELD_STR, objectname_atom_parser },
+ { "objectname", SOURCE_OTHER, FIELD_STR, oid_atom_parser },
{ "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser },
- { "tree", SOURCE_OBJ },
- { "parent", SOURCE_OBJ },
+ { "tree", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
+ { "parent", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
{ "numparent", SOURCE_OBJ, FIELD_ULONG },
{ "object", SOURCE_OBJ },
{ "type", SOURCE_OBJ },
{ "tag", SOURCE_OBJ },
{ "author", SOURCE_OBJ },
{ "authorname", SOURCE_OBJ },
- { "authoremail", SOURCE_OBJ },
+ { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
{ "authordate", SOURCE_OBJ, FIELD_TIME },
{ "committer", SOURCE_OBJ },
{ "committername", SOURCE_OBJ },
- { "committeremail", SOURCE_OBJ },
+ { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
{ "committerdate", SOURCE_OBJ, FIELD_TIME },
{ "tagger", SOURCE_OBJ },
{ "taggername", SOURCE_OBJ },
- { "taggeremail", SOURCE_OBJ },
+ { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
{ "taggerdate", SOURCE_OBJ, FIELD_TIME },
{ "creator", SOURCE_OBJ },
{ "creatordate", SOURCE_OBJ, FIELD_TIME },
@@ -903,21 +923,27 @@ int verify_ref_format(struct ref_format *format)
return 0;
}
-static int grab_objectname(const char *name, const struct object_id *oid,
- struct atom_value *v, struct used_atom *atom)
+static const char *do_grab_oid(const char *field, const struct object_id *oid,
+ struct used_atom *atom)
{
- if (starts_with(name, "objectname")) {
- if (atom->u.objectname.option == O_SHORT) {
- v->s = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
- return 1;
- } else if (atom->u.objectname.option == O_FULL) {
- v->s = xstrdup(oid_to_hex(oid));
- return 1;
- } else if (atom->u.objectname.option == O_LENGTH) {
- v->s = xstrdup(find_unique_abbrev(oid, atom->u.objectname.length));
- return 1;
- } else
- BUG("unknown %%(objectname) option");
+ switch (atom->u.oid.option) {
+ case O_FULL:
+ return oid_to_hex(oid);
+ case O_LENGTH:
+ return find_unique_abbrev(oid, atom->u.oid.length);
+ case O_SHORT:
+ return find_unique_abbrev(oid, DEFAULT_ABBREV);
+ default:
+ BUG("unknown %%(%s) option", field);
+ }
+}
+
+static int grab_oid(const char *name, const char *field, const struct object_id *oid,
+ struct atom_value *v, struct used_atom *atom)
+{
+ if (starts_with(name, field)) {
+ v->s = xstrdup(do_grab_oid(field, oid, atom));
+ return 1;
}
return 0;
}
@@ -945,7 +971,7 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
} else if (!strcmp(name, "deltabase"))
v->s = xstrdup(oid_to_hex(&oi->delta_base_oid));
else if (deref)
- grab_objectname(name, &oi->oid, v, &used_atom[i]);
+ grab_oid(name, "objectname", &oi->oid, v, &used_atom[i]);
}
}
@@ -984,21 +1010,20 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
continue;
if (deref)
name++;
- if (!strcmp(name, "tree")) {
- v->s = xstrdup(oid_to_hex(get_commit_tree_oid(commit)));
- }
- else if (!strcmp(name, "numparent")) {
+ if (grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
+ continue;
+ if (!strcmp(name, "numparent")) {
v->value = commit_list_count(commit->parents);
v->s = xstrfmt("%lu", (unsigned long)v->value);
}
- else if (!strcmp(name, "parent")) {
+ else if (starts_with(name, "parent")) {
struct commit_list *parents;
struct strbuf s = STRBUF_INIT;
for (parents = commit->parents; parents; parents = parents->next) {
- struct commit *parent = parents->item;
+ struct object_id *oid = &parents->item->object.oid;
if (parents != commit->parents)
strbuf_addch(&s, ' ');
- strbuf_addstr(&s, oid_to_hex(&parent->object.oid));
+ strbuf_addstr(&s, do_grab_oid("parent", oid, &used_atom[i]));
}
v->s = strbuf_detach(&s, NULL);
}
@@ -1039,16 +1064,35 @@ static const char *copy_name(const char *buf)
return xstrdup("");
}
-static const char *copy_email(const char *buf)
+static const char *copy_email(const char *buf, struct used_atom *atom)
{
const char *email = strchr(buf, '<');
const char *eoemail;
if (!email)
return xstrdup("");
- eoemail = strchr(email, '>');
+ switch (atom->u.email_option.option) {
+ case EO_RAW:
+ eoemail = strchr(email, '>');
+ if (eoemail)
+ eoemail++;
+ break;
+ case EO_TRIM:
+ email++;
+ eoemail = strchr(email, '>');
+ break;
+ case EO_LOCALPART:
+ email++;
+ eoemail = strchr(email, '@');
+ if (!eoemail)
+ eoemail = strchr(email, '>');
+ break;
+ default:
+ BUG("unknown email option");
+ }
+
if (!eoemail)
return xstrdup("");
- return xmemdupz(email, eoemail + 1 - email);
+ return xmemdupz(email, eoemail - email);
}
static char *copy_subject(const char *buf, unsigned long len)
@@ -1118,7 +1162,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
continue;
if (name[wholen] != 0 &&
strcmp(name + wholen, "name") &&
- strcmp(name + wholen, "email") &&
+ !starts_with(name + wholen, "email") &&
!starts_with(name + wholen, "date"))
continue;
if (!wholine)
@@ -1129,8 +1173,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
v->s = copy_line(wholine);
else if (!strcmp(name + wholen, "name"))
v->s = copy_name(wholine);
- else if (!strcmp(name + wholen, "email"))
- v->s = copy_email(wholine);
+ else if (starts_with(name + wholen, "email"))
+ v->s = copy_email(wholine, &used_atom[i]);
else if (starts_with(name + wholen, "date"))
grab_date(wholine, v, name);
}
@@ -1243,8 +1287,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
continue;
if (deref)
name++;
- if (strcmp(name, "subject") &&
- strcmp(name, "body") &&
+ if (strcmp(name, "body") &&
+ !starts_with(name, "subject") &&
!starts_with(name, "trailers") &&
!starts_with(name, "contents"))
continue;
@@ -1256,7 +1300,11 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
if (atom->u.contents.option == C_SUB)
v->s = copy_subject(subpos, sublen);
- else if (atom->u.contents.option == C_BODY_DEP)
+ else if (atom->u.contents.option == C_SUB_SANITIZE) {
+ struct strbuf sb = STRBUF_INIT;
+ format_sanitized_subject(&sb, subpos, sublen);
+ v->s = strbuf_detach(&sb, NULL);
+ } else if (atom->u.contents.option == C_BODY_DEP)
v->s = xmemdupz(bodypos, bodylen);
else if (atom->u.contents.option == C_LENGTH)
v->s = xstrfmt("%"PRIuMAX, (uintmax_t)strlen(subpos));
@@ -1706,7 +1754,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
v->s = xstrdup(buf + 1);
}
continue;
- } else if (!deref && grab_objectname(name, &ref->objectname, v, atom)) {
+ } else if (!deref && grab_oid(name, "objectname", &ref->objectname, v, atom)) {
continue;
} else if (!strcmp(name, "HEAD")) {
if (atom->u.head && !strcmp(ref->refname, atom->u.head))
diff --git a/refs.c b/refs.c
index 5febfe2281..fa01153151 100644
--- a/refs.c
+++ b/refs.c
@@ -598,10 +598,14 @@ const char *git_default_branch_name(void)
* to name a branch.
*/
static char *substitute_branch_name(struct repository *r,
- const char **string, int *len)
+ const char **string, int *len,
+ int nonfatal_dangling_mark)
{
struct strbuf buf = STRBUF_INIT;
- int ret = repo_interpret_branch_name(r, *string, *len, &buf, 0);
+ struct interpret_branch_name_options options = {
+ .nonfatal_dangling_mark = nonfatal_dangling_mark
+ };
+ int ret = repo_interpret_branch_name(r, *string, *len, &buf, &options);
if (ret == *len) {
size_t size;
@@ -614,19 +618,15 @@ static char *substitute_branch_name(struct repository *r,
}
int repo_dwim_ref(struct repository *r, const char *str, int len,
- struct object_id *oid, char **ref)
+ struct object_id *oid, char **ref, int nonfatal_dangling_mark)
{
- char *last_branch = substitute_branch_name(r, &str, &len);
+ char *last_branch = substitute_branch_name(r, &str, &len,
+ nonfatal_dangling_mark);
int refs_found = expand_ref(r, str, len, oid, ref);
free(last_branch);
return refs_found;
}
-int dwim_ref(const char *str, int len, struct object_id *oid, char **ref)
-{
- return repo_dwim_ref(the_repository, str, len, oid, ref);
-}
-
int expand_ref(struct repository *repo, const char *str, int len,
struct object_id *oid, char **ref)
{
@@ -665,7 +665,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
struct object_id *oid, char **log)
{
struct ref_store *refs = get_main_ref_store(r);
- char *last_branch = substitute_branch_name(r, &str, &len);
+ char *last_branch = substitute_branch_name(r, &str, &len, 0);
const char **p;
int logs_found = 0;
struct strbuf path = STRBUF_INIT;
diff --git a/refs.h b/refs.h
index 04bd25019f..6695518156 100644
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,8 @@
#ifndef REFS_H
#define REFS_H
+#include "cache.h"
+
struct object_id;
struct ref_store;
struct repository;
@@ -151,9 +153,15 @@ struct strvec;
void expand_ref_prefix(struct strvec *prefixes, const char *prefix);
int expand_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
-int repo_dwim_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
+int repo_dwim_ref(struct repository *r, const char *str, int len,
+ struct object_id *oid, char **ref, int nonfatal_dangling_mark);
int repo_dwim_log(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
-int dwim_ref(const char *str, int len, struct object_id *oid, char **ref);
+static inline int dwim_ref(const char *str, int len, struct object_id *oid,
+ char **ref, int nonfatal_dangling_mark)
+{
+ return repo_dwim_ref(the_repository, str, len, oid, ref,
+ nonfatal_dangling_mark);
+}
int dwim_log(const char *str, int len, struct object_id *oid, char **ref);
/*
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dd712e47f4..04e85e7002 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -39,13 +39,6 @@
#define REF_NEEDS_COMMIT (1 << 6)
/*
- * 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 (1 << 7)
-
-/*
* Used as a flag in ref_update::flags when the ref_update was via an
* update to HEAD.
*/
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index fecde40baf..467f4b3c93 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -32,6 +32,13 @@ struct ref_transaction;
#define REF_HAVE_OLD (1 << 3)
/*
+ * 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 (1 << 7)
+
+/*
* Return the length of time to retry acquiring a loose reference lock
* before giving up, in milliseconds:
*/
diff --git a/refspec.c b/refspec.c
index f10ef284ce..8d0affc34a 100644
--- a/refspec.c
+++ b/refspec.c
@@ -153,7 +153,7 @@ void refspec_init(struct refspec *rs, int fetch)
rs->fetch = fetch;
}
-void refspec_append(struct refspec *rs, const char *refspec)
+static void refspec_append_nodup(struct refspec *rs, char *refspec)
{
struct refspec_item item;
@@ -163,7 +163,21 @@ void refspec_append(struct refspec *rs, const char *refspec)
rs->items[rs->nr++] = item;
ALLOC_GROW(rs->raw, rs->raw_nr + 1, rs->raw_alloc);
- rs->raw[rs->raw_nr++] = xstrdup(refspec);
+ rs->raw[rs->raw_nr++] = refspec;
+}
+
+void refspec_append(struct refspec *rs, const char *refspec)
+{
+ refspec_append_nodup(rs, xstrdup(refspec));
+}
+
+void refspec_appendf(struct refspec *rs, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ refspec_append_nodup(rs, xstrvfmt(fmt, ap));
+ va_end(ap);
}
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr)
diff --git a/refspec.h b/refspec.h
index 8d654e3a3a..7569248d11 100644
--- a/refspec.h
+++ b/refspec.h
@@ -56,6 +56,8 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,
void refspec_item_clear(struct refspec_item *item);
void refspec_init(struct refspec *rs, int fetch);
void refspec_append(struct refspec *rs, const char *refspec);
+__attribute__((format (printf,2,3)))
+void refspec_appendf(struct refspec *rs, const char *fmt, ...);
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr);
void refspec_clear(struct refspec *rs);
diff --git a/remote.c b/remote.c
index c5ed74f91c..eafc14cbe7 100644
--- a/remote.c
+++ b/remote.c
@@ -287,19 +287,15 @@ static void read_branches_file(struct remote *remote)
frag = (char *)git_default_branch_name();
add_url_alias(remote, strbuf_detach(&buf, NULL));
- strbuf_addf(&buf, "refs/heads/%s:refs/heads/%s",
- frag, remote->name);
- refspec_append(&remote->fetch, buf.buf);
+ refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s",
+ frag, remote->name);
/*
* Cogito compatible push: push current HEAD to remote #branch
* (master if missing)
*/
- strbuf_reset(&buf);
- strbuf_addf(&buf, "HEAD:refs/heads/%s", frag);
- refspec_append(&remote->push, buf.buf);
+ refspec_appendf(&remote->push, "HEAD:refs/heads/%s", frag);
remote->fetch_tags = 1; /* always auto-follow */
- strbuf_release(&buf);
}
static int handle_config(const char *key, const char *value, void *cb)
@@ -1558,7 +1554,7 @@ static void set_merge(struct branch *ret)
strcmp(ret->remote_name, "."))
continue;
if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
- &oid, &ref) == 1)
+ &oid, &ref, 0) == 1)
ret->merge[i]->dst = ref;
else
ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
diff --git a/revision.c b/revision.c
index 08c2ad23af..067030e64c 100644
--- a/revision.c
+++ b/revision.c
@@ -315,13 +315,14 @@ static void add_pending_object_with_path(struct rev_info *revs,
const char *name, unsigned mode,
const char *path)
{
+ struct interpret_branch_name_options options = { 0 };
if (!obj)
return;
if (revs->no_walk && (obj->flags & UNINTERESTING))
revs->no_walk = 0;
if (revs->reflog_info && obj->type == OBJ_COMMIT) {
struct strbuf buf = STRBUF_INIT;
- int len = interpret_branch_name(name, 0, &buf, 0);
+ int len = interpret_branch_name(name, 0, &buf, &options);
if (0 < len && name[len] && buf.len)
strbuf_addstr(&buf, name + len);
@@ -439,7 +440,7 @@ static struct commit *handle_commit(struct rev_info *revs,
if (object->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)object;
- if (parse_commit(commit) < 0)
+ if (repo_parse_commit(revs->repo, commit) < 0)
die("unable to parse commit %s", name);
if (flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
@@ -1012,7 +1013,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
ts->treesame[0] = 1;
}
}
- if (parse_commit(p) < 0)
+ if (repo_parse_commit(revs->repo, p) < 0)
die("cannot simplify commit %s (because of %s)",
oid_to_hex(&commit->object.oid),
oid_to_hex(&p->object.oid));
@@ -1057,7 +1058,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
* IOW, we pretend this parent is a
* "root" commit.
*/
- if (parse_commit(p) < 0)
+ if (repo_parse_commit(revs->repo, p) < 0)
die("cannot simplify commit %s (invalid %s)",
oid_to_hex(&commit->object.oid),
oid_to_hex(&p->object.oid));
@@ -1125,7 +1126,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
parent = parent->next;
if (p)
p->object.flags |= UNINTERESTING;
- if (parse_commit_gently(p, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
continue;
if (p->parents)
mark_parents_uninteresting(p);
@@ -1156,7 +1157,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
struct commit *p = parent->item;
int gently = revs->ignore_missing_links ||
revs->exclude_promisor_objects;
- if (parse_commit_gently(p, gently) < 0) {
+ if (repo_parse_commit_gently(revs->repo, p, gently) < 0) {
if (revs->exclude_promisor_objects &&
is_promisor_object(&p->object.oid)) {
if (revs->first_parent_only)
@@ -2352,7 +2353,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->diffopt.flags.recursive = 1;
revs->diffopt.flags.tree_in_recursive = 1;
} else if (!strcmp(arg, "-m")) {
+ /*
+ * To "diff-index", "-m" means "match missing", and to the "log"
+ * family of commands, it means "show full diff for merges". Set
+ * both fields appropriately.
+ */
revs->ignore_merges = 0;
+ revs->match_missing = 1;
} else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) {
if (!strcmp(optarg, "off")) {
revs->ignore_merges = 1;
@@ -3330,7 +3337,7 @@ static void explore_walk_step(struct rev_info *revs)
if (!c)
return;
- if (parse_commit_gently(c, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
return;
if (revs->sort_order == REV_SORT_BY_AUTHOR_DATE)
@@ -3368,7 +3375,7 @@ static void indegree_walk_step(struct rev_info *revs)
if (!c)
return;
- if (parse_commit_gently(c, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
return;
explore_to_depth(revs, commit_graph_generation(c));
@@ -3450,7 +3457,7 @@ static void init_topo_walk(struct rev_info *revs)
struct commit *c = list->item;
uint32_t generation;
- if (parse_commit_gently(c, 1))
+ if (repo_parse_commit_gently(revs->repo, c, 1))
continue;
test_flag_and_insert(&info->explore_queue, c, TOPO_WALK_EXPLORED);
@@ -3514,7 +3521,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)
if (parent->object.flags & UNINTERESTING)
continue;
- if (parse_commit_gently(parent, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, parent, 1) < 0)
continue;
generation = commit_graph_generation(parent);
diff --git a/revision.h b/revision.h
index c1e5bcf139..f6bf860d19 100644
--- a/revision.h
+++ b/revision.h
@@ -188,6 +188,7 @@ struct rev_info {
unsigned int diff:1,
full_diff:1,
show_root_diff:1,
+ match_missing:1,
no_commit_id:1,
verbose_header:1,
combine_merges:1,
diff --git a/sha1-name.c b/sha1-name.c
index 0b8cb5247a..0b23b86ceb 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -809,7 +809,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) {
if (warn_ambiguous_refs && warn_on_object_refname_ambiguity) {
- refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref);
+ refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
if (refs_found > 0) {
warning(warn_msg, len, str);
if (advice_object_name_warning)
@@ -860,11 +860,11 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
if (!len && reflog_len)
/* allow "@{...}" to mean the current branch reflog */
- refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref);
+ refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref, 0);
else if (reflog_len)
refs_found = repo_dwim_log(r, str, len, oid, &real_ref);
else
- refs_found = repo_dwim_ref(r, str, len, oid, &real_ref);
+ refs_found = repo_dwim_ref(r, str, len, oid, &real_ref, 0);
if (!refs_found)
return -1;
@@ -1427,9 +1427,12 @@ static int reinterpret(struct repository *r,
struct strbuf tmp = STRBUF_INIT;
int used = buf->len;
int ret;
+ struct interpret_branch_name_options options = {
+ .allowed = allowed
+ };
strbuf_add(buf, name + len, namelen - len);
- ret = repo_interpret_branch_name(r, buf->buf, buf->len, &tmp, allowed);
+ ret = repo_interpret_branch_name(r, buf->buf, buf->len, &tmp, &options);
/* that data was not interpreted, remove our cruft */
if (ret < 0) {
strbuf_setlen(buf, used);
@@ -1471,7 +1474,7 @@ static int interpret_branch_mark(struct repository *r,
int (*get_mark)(const char *, int),
const char *(*get_data)(struct branch *,
struct strbuf *),
- unsigned allowed)
+ const struct interpret_branch_name_options *options)
{
int len;
struct branch *branch;
@@ -1493,10 +1496,16 @@ static int interpret_branch_mark(struct repository *r,
branch = branch_get(NULL);
value = get_data(branch, &err);
- if (!value)
- die("%s", err.buf);
+ if (!value) {
+ if (options->nonfatal_dangling_mark) {
+ strbuf_release(&err);
+ return -1;
+ } else {
+ die("%s", err.buf);
+ }
+ }
- if (!branch_interpret_allowed(value, allowed))
+ if (!branch_interpret_allowed(value, options->allowed))
return -1;
set_shortened_ref(r, buf, value);
@@ -1506,7 +1515,7 @@ static int interpret_branch_mark(struct repository *r,
int repo_interpret_branch_name(struct repository *r,
const char *name, int namelen,
struct strbuf *buf,
- unsigned allowed)
+ const struct interpret_branch_name_options *options)
{
char *at;
const char *start;
@@ -1515,7 +1524,7 @@ int repo_interpret_branch_name(struct repository *r,
if (!namelen)
namelen = strlen(name);
- if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) {
+ if (!options->allowed || (options->allowed & INTERPRET_BRANCH_LOCAL)) {
len = interpret_nth_prior_checkout(r, name, namelen, buf);
if (!len) {
return len; /* syntax Ok, not enough switches */
@@ -1523,7 +1532,8 @@ int repo_interpret_branch_name(struct repository *r,
if (len == namelen)
return len; /* consumed all */
else
- return reinterpret(r, name, namelen, len, buf, allowed);
+ return reinterpret(r, name, namelen, len, buf,
+ options->allowed);
}
}
@@ -1531,22 +1541,22 @@ int repo_interpret_branch_name(struct repository *r,
(at = memchr(start, '@', namelen - (start - name)));
start = at + 1) {
- if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) {
+ if (!options->allowed || (options->allowed & INTERPRET_BRANCH_HEAD)) {
len = interpret_empty_at(name, namelen, at - name, buf);
if (len > 0)
return reinterpret(r, name, namelen, len, buf,
- allowed);
+ options->allowed);
}
len = interpret_branch_mark(r, name, namelen, at - name, buf,
upstream_mark, branch_get_upstream,
- allowed);
+ options);
if (len > 0)
return len;
len = interpret_branch_mark(r, name, namelen, at - name, buf,
push_mark, branch_get_push,
- allowed);
+ options);
if (len > 0)
return len;
}
@@ -1557,7 +1567,10 @@ int repo_interpret_branch_name(struct repository *r,
void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)
{
int len = strlen(name);
- int used = interpret_branch_name(name, len, sb, allowed);
+ struct interpret_branch_name_options options = {
+ .allowed = allowed
+ };
+ int used = interpret_branch_name(name, len, sb, &options);
if (used < 0)
used = 0;
diff --git a/submodule.c b/submodule.c
index 3cbcf40dfc..543b1123ae 100644
--- a/submodule.c
+++ b/submodule.c
@@ -438,13 +438,14 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
*/
}
-static int prepare_submodule_summary(struct rev_info *rev, const char *path,
- struct commit *left, struct commit *right,
- struct commit_list *merge_bases)
+static int prepare_submodule_diff_summary(struct repository *r, struct rev_info *rev,
+ const char *path,
+ struct commit *left, struct commit *right,
+ struct commit_list *merge_bases)
{
struct commit_list *list;
- repo_init_revisions(the_repository, rev, NULL);
+ repo_init_revisions(r, rev, NULL);
setup_revisions(0, NULL, rev, NULL);
rev->left_right = 1;
rev->first_parent_only = 1;
@@ -459,7 +460,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path,
return prepare_revision_walk(rev);
}
-static void print_submodule_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
+static void print_submodule_diff_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
{
static const char format[] = " %m %s";
struct strbuf sb = STRBUF_INIT;
@@ -610,7 +611,7 @@ output_header:
strbuf_release(&sb);
}
-void show_submodule_summary(struct diff_options *o, const char *path,
+void show_submodule_diff_summary(struct diff_options *o, const char *path,
struct object_id *one, struct object_id *two,
unsigned dirty_submodule)
{
@@ -632,12 +633,12 @@ void show_submodule_summary(struct diff_options *o, const char *path,
goto out;
/* Treat revision walker failure the same as missing commits */
- if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
+ if (prepare_submodule_diff_summary(sub, &rev, path, left, right, merge_bases)) {
diff_emit_submodule_error(o, "(revision walker failed)\n");
goto out;
}
- print_submodule_summary(sub, &rev, o);
+ print_submodule_diff_summary(sub, &rev, o);
out:
if (merge_bases)
@@ -840,9 +841,16 @@ static void collect_changed_submodules(struct repository *r,
{
struct rev_info rev;
const struct commit *commit;
+ int save_warning;
+ struct setup_revision_opt s_r_opt = {
+ .assume_dashdash = 1,
+ };
+ save_warning = warn_on_object_refname_ambiguity;
+ warn_on_object_refname_ambiguity = 0;
repo_init_revisions(r, &rev, NULL);
- setup_revisions(argv->nr, argv->v, &rev, NULL);
+ setup_revisions(argv->nr, argv->v, &rev, &s_r_opt);
+ warn_on_object_refname_ambiguity = save_warning;
if (prepare_revision_walk(&rev))
die(_("revision walk setup failed"));
diff --git a/submodule.h b/submodule.h
index 9ce85c03fe..4ac6e31cf1 100644
--- a/submodule.h
+++ b/submodule.h
@@ -69,7 +69,7 @@ int parse_submodule_update_strategy(const char *value,
struct submodule_update_strategy *dst);
const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
void handle_ignore_submodules_arg(struct diff_options *, const char *);
-void show_submodule_summary(struct diff_options *o, const char *path,
+void show_submodule_diff_summary(struct diff_options *o, const char *path,
struct object_id *one, struct object_id *two,
unsigned dirty_submodule);
void show_submodule_inline_diff(struct diff_options *o, const char *path,
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 50222a10c5..2f7c3dcd0f 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -329,6 +329,15 @@ test_expect_success 'implicit bare & --separate-git-dir incompatible' '
test_i18ngrep "incompatible" err
'
+test_expect_success 'bare & --separate-git-dir incompatible within worktree' '
+ test_when_finished "rm -rf bare.git linkwt seprepo" &&
+ test_commit gumby &&
+ git clone --bare . bare.git &&
+ git -C bare.git worktree add --detach ../linkwt &&
+ test_must_fail git -C linkwt init --separate-git-dir seprepo 2>err &&
+ test_i18ngrep "incompatible" err
+'
+
test_lazy_prereq GETCWD_IGNORES_PERMS '
base=GETCWD_TEST_BASE_DIR &&
mkdir -p $base/dir &&
@@ -405,6 +414,25 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
test_path_is_dir realgitdir/refs
'
+sep_git_dir_worktree () {
+ test_when_finished "rm -rf mainwt linkwt seprepo" &&
+ git init mainwt &&
+ test_commit -C mainwt gumby &&
+ git -C mainwt worktree add --detach ../linkwt &&
+ git -C "$1" init --separate-git-dir ../seprepo &&
+ git -C mainwt rev-parse --git-common-dir >expect &&
+ git -C linkwt rev-parse --git-common-dir >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 're-init to move gitdir with linked worktrees' '
+ sep_git_dir_worktree mainwt
+'
+
+test_expect_success 're-init to move gitdir within linked worktree' '
+ sep_git_dir_worktree linkwt
+'
+
test_expect_success MINGW '.git hidden' '
rm -rf newdir &&
(
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 97ebfe1f9d..825d9a184f 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1836,11 +1836,14 @@ test_expect_success '--show-scope with --show-origin' '
test_cmp expect output
'
-test_expect_success '--local requires a repo' '
- # we expect 128 to ensure that we do not simply
- # fail to find anything and return code "1"
- test_expect_code 128 nongit git config --local foo.bar
-'
+for opt in --local --worktree
+do
+ test_expect_success "$opt requires a repo" '
+ # we expect 128 to ensure that we do not simply
+ # fail to find anything and return code "1"
+ test_expect_code 128 nongit git config $opt foo.bar
+ '
+done
cat >.git/config <<-\EOF &&
[core]
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
new file mode 100755
index 0000000000..1fe468bfe8
--- /dev/null
+++ b/t/t2406-worktree-repair.sh
@@ -0,0 +1,179 @@
+#!/bin/sh
+
+test_description='test git worktree repair'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_commit init
+'
+
+test_expect_success 'skip missing worktree' '
+ test_when_finished "git worktree prune" &&
+ git worktree add --detach missing &&
+ rm -rf missing &&
+ git worktree repair >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err
+'
+
+test_expect_success 'worktree path not directory' '
+ test_when_finished "git worktree prune" &&
+ git worktree add --detach notdir &&
+ rm -rf notdir &&
+ >notdir &&
+ test_must_fail git worktree repair >out 2>err &&
+ test_must_be_empty out &&
+ test_i18ngrep "not a directory" err
+'
+
+test_expect_success "don't clobber .git repo" '
+ test_when_finished "rm -rf repo && git worktree prune" &&
+ git worktree add --detach repo &&
+ rm -rf repo &&
+ test_create_repo repo &&
+ test_must_fail git worktree repair >out 2>err &&
+ test_must_be_empty out &&
+ test_i18ngrep ".git is not a file" err
+'
+
+test_corrupt_gitfile () {
+ butcher=$1 &&
+ problem=$2 &&
+ repairdir=${3:-.} &&
+ test_when_finished 'rm -rf corrupt && git worktree prune' &&
+ git worktree add --detach corrupt &&
+ git -C corrupt rev-parse --absolute-git-dir >expect &&
+ eval "$butcher" &&
+ git -C "$repairdir" worktree repair >out 2>err &&
+ test_i18ngrep "$problem" out &&
+ test_must_be_empty err &&
+ git -C corrupt rev-parse --absolute-git-dir >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'repair missing .git file' '
+ test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken"
+'
+
+test_expect_success 'repair bogus .git file' '
+ test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \
+ ".git file broken"
+'
+
+test_expect_success 'repair incorrect .git file' '
+ test_when_finished "rm -rf other && git worktree prune" &&
+ test_create_repo other &&
+ other=$(git -C other rev-parse --absolute-git-dir) &&
+ test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \
+ ".git file incorrect"
+'
+
+test_expect_success 'repair .git file from main/.git' '
+ test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" .git
+'
+
+test_expect_success 'repair .git file from linked worktree' '
+ test_when_finished "rm -rf other && git worktree prune" &&
+ git worktree add --detach other &&
+ test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" other
+'
+
+test_expect_success 'repair .git file from bare.git' '
+ test_when_finished "rm -rf bare.git corrupt && git worktree prune" &&
+ git clone --bare . bare.git &&
+ git -C bare.git worktree add --detach ../corrupt &&
+ git -C corrupt rev-parse --absolute-git-dir >expect &&
+ rm -f corrupt/.git &&
+ git -C bare.git worktree repair &&
+ git -C corrupt rev-parse --absolute-git-dir >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'invalid worktree path' '
+ test_must_fail git worktree repair /notvalid >out 2>err &&
+ test_must_be_empty out &&
+ test_i18ngrep "not a valid path" err
+'
+
+test_expect_success 'repo not found; .git not file' '
+ test_when_finished "rm -rf not-a-worktree" &&
+ test_create_repo not-a-worktree &&
+ test_must_fail git worktree repair not-a-worktree >out 2>err &&
+ test_must_be_empty out &&
+ test_i18ngrep ".git is not a file" err
+'
+
+test_expect_success 'repo not found; .git file broken' '
+ test_when_finished "rm -rf orig moved && git worktree prune" &&
+ git worktree add --detach orig &&
+ echo /invalid >orig/.git &&
+ mv orig moved &&
+ test_must_fail git worktree repair moved >out 2>err &&
+ test_must_be_empty out &&
+ test_i18ngrep ".git file broken" err
+'
+
+test_expect_success 'repair broken gitdir' '
+ test_when_finished "rm -rf orig moved && git worktree prune" &&
+ git worktree add --detach orig &&
+ sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+ rm .git/worktrees/orig/gitdir &&
+ mv orig moved &&
+ git worktree repair moved >out 2>err &&
+ test_cmp expect .git/worktrees/orig/gitdir &&
+ test_i18ngrep "gitdir unreadable" out &&
+ test_must_be_empty err
+'
+
+test_expect_success 'repair incorrect gitdir' '
+ test_when_finished "rm -rf orig moved && git worktree prune" &&
+ git worktree add --detach orig &&
+ sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+ mv orig moved &&
+ git worktree repair moved >out 2>err &&
+ test_cmp expect .git/worktrees/orig/gitdir &&
+ test_i18ngrep "gitdir incorrect" out &&
+ test_must_be_empty err
+'
+
+test_expect_success 'repair gitdir (implicit) from linked worktree' '
+ test_when_finished "rm -rf orig moved && git worktree prune" &&
+ git worktree add --detach orig &&
+ sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+ mv orig moved &&
+ git -C moved worktree repair >out 2>err &&
+ test_cmp expect .git/worktrees/orig/gitdir &&
+ test_i18ngrep "gitdir incorrect" out &&
+ test_must_be_empty err
+'
+
+test_expect_success 'unable to repair gitdir (implicit) from main worktree' '
+ test_when_finished "rm -rf orig moved && git worktree prune" &&
+ git worktree add --detach orig &&
+ cat .git/worktrees/orig/gitdir >expect &&
+ mv orig moved &&
+ git worktree repair >out 2>err &&
+ test_cmp expect .git/worktrees/orig/gitdir &&
+ test_must_be_empty out &&
+ test_must_be_empty err
+'
+
+test_expect_success 'repair multiple gitdir files' '
+ test_when_finished "rm -rf orig1 orig2 moved1 moved2 &&
+ git worktree prune" &&
+ git worktree add --detach orig1 &&
+ git worktree add --detach orig2 &&
+ sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&
+ sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&
+ mv orig1 moved1 &&
+ mv orig2 moved2 &&
+ git worktree repair moved1 moved2 >out 2>err &&
+ test_cmp expect1 .git/worktrees/orig1/gitdir &&
+ test_cmp expect2 .git/worktrees/orig2/gitdir &&
+ test_i18ngrep "gitdir incorrect:.*orig1/gitdir$" out &&
+ test_i18ngrep "gitdir incorrect:.*orig2/gitdir$" out &&
+ test_must_be_empty err
+'
+
+test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 4c0734157b..028c88d1b2 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1287,6 +1287,7 @@ test_expect_success 'detect typo in branch name when using --edit-description' '
'
test_expect_success 'refuse --edit-description on unborn branch for now' '
+ test_when_finished "git checkout master" &&
write_script editor <<-\EOF &&
echo "New contents" >"$1"
EOF
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index fb73a847cb..1590cf6b98 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -569,12 +569,20 @@ test_expect_success 'patch mode ignores unmerged entries' '
diff_cmp expected diff
'
+test_expect_success 'index is refreshed after applying patch' '
+ git reset --hard &&
+ echo content >test &&
+ printf y | git add -p &&
+ git diff-files --exit-code
+'
+
test_expect_success 'diffs can be colorized' '
git reset --hard &&
echo content >test &&
printf y >y &&
force_color git add -p >output 2>&1 <y &&
+ git diff-files --exit-code &&
# We do not want to depend on the exact coloring scheme
# git uses for diffs, so just check that we saw some kind of color.
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index 43b1b5b2af..f340b376bc 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -382,12 +382,52 @@ test_expect_success 'repack with the --no-progress option' '
test_line_count = 0 err
'
-test_expect_success 'repack removes multi-pack-index' '
+test_expect_success 'repack removes multi-pack-index when deleting packs' '
test_path_is_file $objdir/pack/multi-pack-index &&
- GIT_TEST_MULTI_PACK_INDEX=0 git repack -adf &&
+ # Set GIT_TEST_MULTI_PACK_INDEX to 0 to avoid writing a new
+ # multi-pack-index after repacking, but set "core.multiPackIndex" to
+ # true so that "git repack" can read the existing MIDX.
+ GIT_TEST_MULTI_PACK_INDEX=0 git -c core.multiPackIndex repack -adf &&
test_path_is_missing $objdir/pack/multi-pack-index
'
+test_expect_success 'repack preserves multi-pack-index when creating packs' '
+ git init preserve &&
+ test_when_finished "rm -fr preserve" &&
+ (
+ cd preserve &&
+ packdir=.git/objects/pack &&
+ midx=$packdir/multi-pack-index &&
+
+ test_commit 1 &&
+ pack1=$(git pack-objects --all $packdir/pack) &&
+ touch $packdir/pack-$pack1.keep &&
+ test_commit 2 &&
+ pack2=$(git pack-objects --revs $packdir/pack) &&
+ touch $packdir/pack-$pack2.keep &&
+
+ git multi-pack-index write &&
+ cp $midx $midx.bak &&
+
+ cat >pack-input <<-EOF &&
+ HEAD
+ ^HEAD~1
+ EOF
+ test_commit 3 &&
+ pack3=$(git pack-objects --revs $packdir/pack <pack-input) &&
+ test_commit 4 &&
+ pack4=$(git pack-objects --revs $packdir/pack <pack-input) &&
+
+ GIT_TEST_MULTI_PACK_INDEX=0 git -c core.multiPackIndex repack -ad &&
+ ls -la $packdir &&
+ test_path_is_file $packdir/pack-$pack1.pack &&
+ test_path_is_file $packdir/pack-$pack2.pack &&
+ test_path_is_missing $packdir/pack-$pack3.pack &&
+ test_path_is_missing $packdir/pack-$pack4.pack &&
+ test_cmp_bin $midx.bak $midx
+ )
+'
+
compare_results_with_midx "after repack"
test_expect_success 'multi-pack-index and pack-bitmap' '
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 58adee7d18..b359023189 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -116,7 +116,13 @@ test_atom head objectname:short $(git rev-parse --short refs/heads/master)
test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
test_atom head tree $(git rev-parse refs/heads/master^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/master^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/master^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/master^{tree})
test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
test_atom head numparent 0
test_atom head object ''
test_atom head type ''
@@ -125,19 +131,26 @@ test_atom head '*objecttype' ''
test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
test_atom head authorname 'A U Thor'
test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
test_atom head committername 'C O Mitter'
test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
test_atom head tag ''
test_atom head tagger ''
test_atom head taggername ''
test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
test_atom head taggerdate ''
test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
test_atom head contents:subject 'Initial'
test_atom head body ''
test_atom head contents:body ''
@@ -161,7 +174,13 @@ test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
test_atom tag numparent ''
test_atom tag object $(git rev-parse refs/tags/testtag^0)
test_atom tag type 'commit'
@@ -170,19 +189,26 @@ test_atom tag '*objecttype' 'commit'
test_atom tag author ''
test_atom tag authorname ''
test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
test_atom tag authordate ''
test_atom tag committer ''
test_atom tag committername ''
test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
test_atom tag committerdate ''
test_atom tag tag 'testtag'
test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
test_atom tag taggername 'C O Mitter'
test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
test_atom tag contents:subject 'Tagging at 1151968727'
test_atom tag body ''
test_atom tag contents:body ''
@@ -564,10 +590,14 @@ test_atom refs/tags/taggerless tag 'taggerless'
test_atom refs/tags/taggerless tagger ''
test_atom refs/tags/taggerless taggername ''
test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
test_atom refs/tags/taggerless taggerdate ''
test_atom refs/tags/taggerless committer ''
test_atom refs/tags/taggerless committername ''
test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
test_atom refs/tags/taggerless committerdate ''
test_atom refs/tags/taggerless subject 'Broken tag'
@@ -591,6 +621,7 @@ test_expect_success 'create tag with subject and body content' '
git tag -F msg subject-body
'
test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
test_atom refs/tags/subject-body body 'first body line
second body line
'
@@ -611,6 +642,7 @@ test_expect_success 'create tag with multiline subject' '
git tag -F msg multiline
'
test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
test_atom refs/tags/multiline body 'first body line
second body line
@@ -643,6 +675,7 @@ sig='-----BEGIN PGP SIGNATURE-----
PREREQ=GPG
test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
test_atom refs/tags/signed-empty contents:subject ''
test_atom refs/tags/signed-empty body "$sig"
test_atom refs/tags/signed-empty contents:body ''
@@ -650,6 +683,7 @@ test_atom refs/tags/signed-empty contents:signature "$sig"
test_atom refs/tags/signed-empty contents "$sig"
test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
test_atom refs/tags/signed-short contents:subject 'subject line'
test_atom refs/tags/signed-short body "$sig"
test_atom refs/tags/signed-short contents:body ''
@@ -658,6 +692,7 @@ test_atom refs/tags/signed-short contents "subject line
$sig"
test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
test_atom refs/tags/signed-long contents:subject 'subject line'
test_atom refs/tags/signed-long body "body contents
$sig"
diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index cc87d26619..7608814708 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -7,6 +7,12 @@ test_description='Summary support for submodules
This test script tries to verify the sanity of summary subcommand of git submodule.
'
+
+# NOTE: This test script uses 'git add' instead of 'git submodule add' to add
+# submodules to the superproject. Some submodule subcommands such as init and
+# deinit might not work as expected in this script. t7421 does not have this
+# caveat.
+#
# NEEDSWORK: This test script is old fashioned and may need a big cleanup due to
# various reasons, one of them being that there are lots of commands taking place
# outside of 'test_expect_success' block, which is no longer in good-style.
diff --git a/t/t7421-submodule-summary-add.sh b/t/t7421-submodule-summary-add.sh
new file mode 100755
index 0000000000..b070f13714
--- /dev/null
+++ b/t/t7421-submodule-summary-add.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Shourya Shukla
+#
+
+test_description='Summary support for submodules, adding them using git submodule add
+
+This test script tries to verify the sanity of summary subcommand of git submodule
+while making sure to add submodules using `git submodule add` instead of
+`git add` as done in t7401.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'summary test environment setup' '
+ git init sm &&
+ test_commit -C sm "add file" file file-content file-tag &&
+
+ git submodule add ./sm my-subm &&
+ test_tick &&
+ git commit -m "add submodule"
+'
+
+test_expect_success 'submodule summary output for initialized submodule' '
+ test_commit -C sm "add file2" file2 file2-content file2-tag &&
+ git submodule update --remote &&
+ test_tick &&
+ git commit -m "update submodule" my-subm &&
+ git submodule summary HEAD^ >actual &&
+ rev1=$(git -C sm rev-parse --short HEAD^) &&
+ rev2=$(git -C sm rev-parse --short HEAD) &&
+ cat >expected <<-EOF &&
+ * my-subm ${rev1}...${rev2} (1):
+ > add file2
+
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'submodule summary output for deinitialized submodule' '
+ git submodule deinit my-subm &&
+ git submodule summary HEAD^ >actual &&
+ test_must_be_empty actual &&
+ git submodule update --init my-subm &&
+ git submodule summary HEAD^ >actual &&
+ rev1=$(git -C sm rev-parse --short HEAD^) &&
+ rev2=$(git -C sm rev-parse --short HEAD) &&
+ cat >expected <<-EOF &&
+ * my-subm ${rev1}...${rev2} (1):
+ > add file2
+
+ EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'submodule summary output for submodules with changed paths' '
+ git mv my-subm subm &&
+ git commit -m "change submodule path" &&
+ rev=$(git -C sm rev-parse --short HEAD^) &&
+ git submodule summary HEAD^^ -- my-subm >actual 2>err &&
+ test_must_be_empty err &&
+ cat >expected <<-EOF &&
+ * my-subm ${rev}...0000000:
+
+ EOF
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index e81759319f..963fed6821 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -814,6 +814,33 @@ test_expect_success 'status -s without relative paths' '
'
+cat >expect <<\EOF
+ M dir1/modified
+A dir2/added
+A "file with spaces"
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? "file with spaces 2"
+?? untracked
+EOF
+
+test_expect_success 'status -s without relative paths' '
+ test_when_finished "git rm --cached \"file with spaces\"; rm -f file*" &&
+ >"file with spaces" &&
+ >"file with spaces 2" &&
+ >"expect with spaces" &&
+ git add "file with spaces" &&
+
+ git status -s >output &&
+ test_cmp expect output &&
+
+ git status -s --ignored >output &&
+ grep "^!! \"expect with spaces\"$" output &&
+ grep -v "^!! " output >output-wo-ignored &&
+ test_cmp expect output-wo-ignored
+'
+
test_expect_success 'dry-run of partial commit excluding new file in index' '
cat >expect <<EOF &&
On branch master
@@ -846,6 +873,18 @@ test_expect_success 'status refreshes the index' '
test_cmp expect output
'
+test_expect_success 'status shows detached HEAD properly after checking out non-local upstream branch' '
+ test_when_finished rm -rf upstream downstream actual &&
+
+ test_create_repo upstream &&
+ test_commit -C upstream foo &&
+
+ git clone upstream downstream &&
+ git -C downstream checkout @{u} &&
+ git -C downstream status >actual &&
+ test_i18ngrep "HEAD detached at [0-9a-f]\\+" actual
+'
+
test_expect_success 'setup status submodule summary' '
test_create_repo sm && (
cd sm &&
diff --git a/worktree.c b/worktree.c
index 62217b4a6b..46a5fb8447 100644
--- a/worktree.c
+++ b/worktree.c
@@ -571,3 +571,138 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
free_worktrees(worktrees);
return ret;
}
+
+/*
+ * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
+ * pointing at <repo>/worktrees/<id>.
+ */
+static void repair_gitfile(struct worktree *wt,
+ worktree_repair_fn fn, void *cb_data)
+{
+ struct strbuf dotgit = STRBUF_INIT;
+ struct strbuf repo = STRBUF_INIT;
+ char *backlink;
+ const char *repair = NULL;
+ int err;
+
+ /* missing worktree can't be repaired */
+ if (!file_exists(wt->path))
+ return;
+
+ if (!is_directory(wt->path)) {
+ fn(1, wt->path, _("not a directory"), cb_data);
+ return;
+ }
+
+ strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
+ strbuf_addf(&dotgit, "%s/.git", wt->path);
+ backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
+
+ if (err == READ_GITFILE_ERR_NOT_A_FILE)
+ fn(1, wt->path, _(".git is not a file"), cb_data);
+ else if (err)
+ repair = _(".git file broken");
+ else if (fspathcmp(backlink, repo.buf))
+ repair = _(".git file incorrect");
+
+ if (repair) {
+ fn(0, wt->path, repair, cb_data);
+ write_file(dotgit.buf, "gitdir: %s", repo.buf);
+ }
+
+ free(backlink);
+ strbuf_release(&repo);
+ strbuf_release(&dotgit);
+}
+
+static void repair_noop(int iserr, const char *path, const char *msg,
+ void *cb_data)
+{
+ /* nothing */
+}
+
+void repair_worktrees(worktree_repair_fn fn, void *cb_data)
+{
+ struct worktree **worktrees = get_worktrees();
+ struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
+
+ if (!fn)
+ fn = repair_noop;
+ for (; *wt; wt++)
+ repair_gitfile(*wt, fn, cb_data);
+ free_worktrees(worktrees);
+}
+
+static int is_main_worktree_path(const char *path)
+{
+ struct strbuf target = STRBUF_INIT;
+ struct strbuf maindir = STRBUF_INIT;
+ int cmp;
+
+ strbuf_add_real_path(&target, path);
+ strbuf_strip_suffix(&target, "/.git");
+ strbuf_add_real_path(&maindir, get_git_common_dir());
+ strbuf_strip_suffix(&maindir, "/.git");
+ cmp = fspathcmp(maindir.buf, target.buf);
+
+ strbuf_release(&maindir);
+ strbuf_release(&target);
+ return !cmp;
+}
+
+/*
+ * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
+ * the worktree's path.
+ */
+void repair_worktree_at_path(const char *path,
+ worktree_repair_fn fn, void *cb_data)
+{
+ struct strbuf dotgit = STRBUF_INIT;
+ struct strbuf realdotgit = STRBUF_INIT;
+ struct strbuf gitdir = STRBUF_INIT;
+ struct strbuf olddotgit = STRBUF_INIT;
+ char *backlink = NULL;
+ const char *repair = NULL;
+ int err;
+
+ if (!fn)
+ fn = repair_noop;
+
+ if (is_main_worktree_path(path))
+ goto done;
+
+ strbuf_addf(&dotgit, "%s/.git", path);
+ if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
+ fn(1, path, _("not a valid path"), cb_data);
+ goto done;
+ }
+
+ backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
+ if (err == READ_GITFILE_ERR_NOT_A_FILE) {
+ fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
+ goto done;
+ } else if (err) {
+ fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
+ goto done;
+ }
+
+ strbuf_addf(&gitdir, "%s/gitdir", backlink);
+ if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
+ repair = _("gitdir unreadable");
+ else {
+ strbuf_rtrim(&olddotgit);
+ if (fspathcmp(olddotgit.buf, realdotgit.buf))
+ repair = _("gitdir incorrect");
+ }
+
+ if (repair) {
+ fn(0, gitdir.buf, repair, cb_data);
+ write_file(gitdir.buf, "%s", realdotgit.buf);
+ }
+done:
+ free(backlink);
+ strbuf_release(&olddotgit);
+ strbuf_release(&gitdir);
+ strbuf_release(&realdotgit);
+ strbuf_release(&dotgit);
+}
diff --git a/worktree.h b/worktree.h
index 516744c433..ff7b62e434 100644
--- a/worktree.h
+++ b/worktree.h
@@ -89,6 +89,29 @@ int validate_worktree(const struct worktree *wt,
void update_worktree_location(struct worktree *wt,
const char *path_);
+typedef void (* worktree_repair_fn)(int iserr, const char *path,
+ const char *msg, void *cb_data);
+
+/*
+ * Visit each registered linked worktree and repair corruptions. For each
+ * repair made or error encountered while attempting a repair, the callback
+ * function, if non-NULL, is called with the path of the worktree and a
+ * description of the repair or error, along with the callback user-data.
+ */
+void repair_worktrees(worktree_repair_fn, void *cb_data);
+
+/*
+ * Repair administrative files corresponding to the worktree at the given path.
+ * The worktree's .git file pointing at the repository must be intact for the
+ * repair to succeed. Useful for re-associating an orphaned worktree with the
+ * repository if the worktree has been moved manually (without using "git
+ * worktree move"). For each repair made or error encountered while attempting
+ * a repair, the callback function, if non-NULL, is called with the path of the
+ * worktree and a description of the repair or error, along with the callback
+ * user-data.
+ */
+void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
+
/*
* Free up the memory for worktree(s)
*/
diff --git a/wt-status.c b/wt-status.c
index bb0f9120de..160d14970a 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -259,8 +259,6 @@ static void wt_longstatus_print_trailer(struct wt_status *s)
status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
}
-#define quote_path quote_path_relative
-
static const char *wt_status_unmerged_status_string(int stagemask)
{
switch (stagemask) {
@@ -338,7 +336,7 @@ static void wt_longstatus_print_unmerged_data(struct wt_status *s,
memset(padding, ' ', label_width);
}
- one = quote_path(it->string, s->prefix, &onebuf);
+ one = quote_path(it->string, s->prefix, &onebuf, 0);
status_printf(s, color(WT_STATUS_HEADER, s), "\t");
how = wt_status_unmerged_status_string(d->stagemask);
@@ -404,8 +402,8 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
if (d->rename_status == status)
one_name = d->rename_source;
- one = quote_path(one_name, s->prefix, &onebuf);
- two = quote_path(two_name, s->prefix, &twobuf);
+ one = quote_path(one_name, s->prefix, &onebuf, 0);
+ two = quote_path(two_name, s->prefix, &twobuf, 0);
status_printf(s, color(WT_STATUS_HEADER, s), "\t");
what = wt_status_diff_status_string(status);
@@ -966,7 +964,7 @@ static void wt_longstatus_print_other(struct wt_status *s,
struct string_list_item *it;
const char *path;
it = &(l->items[i]);
- path = quote_path(it->string, s->prefix, &buf);
+ path = quote_path(it->string, s->prefix, &buf, 0);
if (column_active(s->colopts)) {
string_list_append(&output, path);
continue;
@@ -1569,7 +1567,7 @@ static void wt_status_get_detached_from(struct repository *r,
return;
}
- if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref) == 1 &&
+ if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref, 1) == 1 &&
/* sha1 is a commit? match without further lookup */
(oideq(&cb.noid, &oid) ||
/* perhaps sha1 is a tag, try to dereference to a commit */
@@ -1850,7 +1848,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it,
} else {
struct strbuf onebuf = STRBUF_INIT;
const char *one;
- one = quote_path(it->string, s->prefix, &onebuf);
+ one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
printf(" %s\n", one);
strbuf_release(&onebuf);
}
@@ -1879,21 +1877,12 @@ static void wt_shortstatus_status(struct string_list_item *it,
const char *one;
if (d->rename_source) {
- one = quote_path(d->rename_source, s->prefix, &onebuf);
- if (*one != '"' && strchr(one, ' ') != NULL) {
- putchar('"');
- strbuf_addch(&onebuf, '"');
- one = onebuf.buf;
- }
+ one = quote_path(d->rename_source, s->prefix, &onebuf,
+ QUOTE_PATH_QUOTE_SP);
printf("%s -> ", one);
strbuf_release(&onebuf);
}
- one = quote_path(it->string, s->prefix, &onebuf);
- if (*one != '"' && strchr(one, ' ') != NULL) {
- putchar('"');
- strbuf_addch(&onebuf, '"');
- one = onebuf.buf;
- }
+ one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
printf("%s\n", one);
strbuf_release(&onebuf);
}
@@ -1907,7 +1896,7 @@ static void wt_shortstatus_other(struct string_list_item *it,
} else {
struct strbuf onebuf = STRBUF_INIT;
const char *one;
- one = quote_path(it->string, s->prefix, &onebuf);
+ one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
printf(" %s\n", one);
strbuf_release(&onebuf);
@@ -2224,9 +2213,9 @@ static void wt_porcelain_v2_print_changed_entry(
*/
sep_char = '\t';
eol_char = '\n';
- path = quote_path(it->string, s->prefix, &buf);
+ path = quote_path(it->string, s->prefix, &buf, 0);
if (d->rename_source)
- path_from = quote_path(d->rename_source, s->prefix, &buf_from);
+ path_from = quote_path(d->rename_source, s->prefix, &buf_from, 0);
}
if (path_from)
@@ -2312,7 +2301,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
if (s->null_termination)
path_index = it->string;
else
- path_index = quote_path(it->string, s->prefix, &buf_index);
+ path_index = quote_path(it->string, s->prefix, &buf_index, 0);
fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c",
unmerged_prefix, key, submodule_token,
@@ -2345,7 +2334,7 @@ static void wt_porcelain_v2_print_other(
path = it->string;
eol_char = '\0';
} else {
- path = quote_path(it->string, s->prefix, &buf);
+ path = quote_path(it->string, s->prefix, &buf, 0);
eol_char = '\n';
}