Merge branch 'ew/svn-crlf'
authorJunio C Hamano <gitster@pobox.com>
Thu, 28 Dec 2017 22:08:46 +0000 (14:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 28 Dec 2017 22:08:46 +0000 (14:08 -0800)
"git svn" has been updated to strip CRs in the commit messages, as
recent versions of Subversion rejects them.

* ew/svn-crlf:
  git-svn: convert CRLF to LF in commit message to SVN

129 files changed:
.travis.yml
Documentation/Makefile
Documentation/RelNotes/2.16.0.txt
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-clone.txt
Documentation/git-pack-objects.txt
Documentation/git-rebase.txt
Documentation/git-rev-list.txt
Documentation/git-worktree.txt
Documentation/git.txt
Documentation/githooks.txt
Documentation/gitworkflows.txt
Documentation/install-doc-quick.sh
Documentation/pretty-formats.txt
Documentation/rebase-config.txt [new file with mode: 0644]
Documentation/rev-list-options.txt
Documentation/revisions.txt
Documentation/technical/api-decorate.txt [deleted file]
Documentation/user-manual.txt
Makefile
advice.c
advice.h
branch.c
branch.h
builtin/am.c
builtin/branch.c
builtin/checkout.c
builtin/clone.c
builtin/diff-tree.c
builtin/fast-export.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/grep.c
builtin/index-pack.c
builtin/mv.c
builtin/notes.c
builtin/pack-objects.c
builtin/rebase--helper.c
builtin/reset.c
builtin/rev-list.c
builtin/rm.c
builtin/show-branch.c
builtin/worktree.c
cache.h
checkout.c [new file with mode: 0644]
checkout.h [new file with mode: 0644]
ci/install-dependencies.sh
ci/lib-travisci.sh
color.c
commit.h
contrib/completion/git-prompt.sh
convert.c
decorate.c
decorate.h
diff.c
diff.h
diffcore-rename.c
dir.c
dir.h
editor.c
environment.c
git-compat-util.h
git-cvsimport.perl
git-gui/lib/commit.tcl
git-rebase--interactive.sh
list-objects-filter-options.c [new file with mode: 0644]
list-objects-filter-options.h [new file with mode: 0644]
list-objects-filter.c [new file with mode: 0644]
list-objects-filter.h [new file with mode: 0644]
list-objects.c
list-objects.h
merge-recursive.c
object.h
oidmap.h
oidset.c
oidset.h
path.h
pathspec.h
pretty.h [new file with mode: 0644]
progress.c
progress.h
revision.h
sequencer.c
sequencer.h
setup.c
sideband.c
strbuf.c
submodule.c
submodule.h
t/helper/.gitignore
t/helper/test-example-decorate.c [new file with mode: 0644]
t/t0027-auto-crlf.sh
t/t2020-checkout-detach.sh
t/t2025-worktree-add.sh
t/t3040-subprojects-basic.sh
t/t3404-rebase-interactive.sh
t/t4001-diff-rename.sh
t/t4013-diff-various.sh
t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial [new file with mode: 0644]
t/t4013/diff.noellipses-diff_--raw_initial [new file with mode: 0644]
t/t4013/diff.noellipses-show_--patch-with-raw_side [new file with mode: 0644]
t/t4013/diff.noellipses-whatchanged_--root_master [new file with mode: 0644]
t/t4013/diff.noellipses-whatchanged_-SF_master [new file with mode: 0644]
t/t4013/diff.noellipses-whatchanged_master [new file with mode: 0644]
t/t4015-diff-whitespace.sh
t/t4045-diff-relative.sh
t/t4065-diff-anchored.sh [new file with mode: 0755]
t/t4208-log-magic-pathspec.sh
t/t5317-pack-objects-filter-objects.sh [new file with mode: 0755]
t/t6112-rev-list-filters-objects.sh [new file with mode: 0755]
t/t9004-example.sh [new file with mode: 0755]
t/t9300-fast-import.sh
trace.c
trace.h
transport-helper.c
transport.c
transport.h
tree-walk.c
xdiff/xdiff.h
xdiff/xpatience.c

index 281f101..7c9aa05 100644 (file)
@@ -21,30 +21,14 @@ addons:
     - git-svn
     - apache2
 
-env:
-  global:
-    - DEVELOPER=1
-    # The Linux build installs the defined dependency versions below.
-    # The OS X build installs the latest available versions. Keep that
-    # in mind when you encounter a broken OS X build!
-    - LINUX_P4_VERSION="16.2"
-    - LINUX_GIT_LFS_VERSION="1.5.2"
-    - DEFAULT_TEST_TARGET=prove
-    - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
-    - GIT_TEST_OPTS="--verbose-log"
-    - GIT_TEST_CLONE_2GB=YesPlease
-    # t9810 occasionally fails on Travis CI OS X
-    # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X
-    - GIT_SKIP_TESTS="t9810 t9816"
-
 matrix:
   include:
-    - env: GETTEXT_POISON=YesPlease
+    - env: jobname=GETTEXT_POISON
       os: linux
       compiler:
       addons:
       before_install:
-    - env: Windows
+    - env: jobname=Windows
       os: linux
       compiler:
       addons:
@@ -55,7 +39,7 @@ matrix:
           test "$TRAVIS_REPO_SLUG" != "git/git" ||
           ci/run-windows-build.sh $TRAVIS_BRANCH $(git rev-parse HEAD)
       after_failure:
-    - env: Linux32
+    - env: jobname=Linux32
       os: linux
       compiler:
       services:
@@ -63,7 +47,7 @@ matrix:
       before_install:
       before_script:
       script: ci/run-linux32-docker.sh
-    - env: Static Analysis
+    - env: jobname=StaticAnalysis
       os: linux
       compiler:
       addons:
@@ -74,7 +58,7 @@ matrix:
       before_script:
       script: ci/run-static-analysis.sh
       after_failure:
-    - env: Documentation
+    - env: jobname=Documentation
       os: linux
       compiler:
       addons:
index 2ab6556..4ae9ba5 100644 (file)
@@ -39,6 +39,7 @@ MAN7_TXT += gitworkflows.txt
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT))
 MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
+GIT_MAN_REF = master
 
 OBSOLETE_HTML += everyday.html
 OBSOLETE_HTML += git-remote-helpers.html
@@ -437,14 +438,14 @@ require-manrepo::
        then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi
 
 quick-install-man: require-manrepo
-       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir)
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir) $(GIT_MAN_REF)
 
 require-htmlrepo::
        @if test ! -d $(HTML_REPO); \
        then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi
 
 quick-install-html: require-htmlrepo
-       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir)
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir) $(GIT_MAN_REF)
 
 print-man1:
        @for i in $(MAN1_TXT); do echo $$i; done
index 3eeeb83..a43b4c9 100644 (file)
@@ -107,6 +107,26 @@ UI, Workflows & Features
    in /usr/lib and /usr/sbin; extend the list of locations to be
    checked to also include directories on $PATH.
 
+ * "git diff" learned, "--anchored", a variant of the "--patience"
+   algorithm, to which the user can specify which 'unique' line to be
+   used as anchoring points.
+
+ * The way "git worktree add" determines what branch to create from
+   where and checkout in the new worktree has been updated a bit.
+
+ * Ancient part of codebase still shows dots after an abbreviated
+   object name just to show that it is not a full object name, but
+   these ellipses are confusing to people who newly discovered Git
+   who are used to seeing abbreviated object names and find them
+   confusing with the range syntax.
+
+ * With a configuration variable rebase.abbreviateCommands set,
+   "git rebase -i" produces the todo list with a single-letter
+   command names.
+
+ * "git worktree add" learned to run the post-checkout hook, just like
+   "git checkout" does, after the initial checkout.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -163,6 +183,24 @@ Performance, Internal Implementation, Development Support etc.
  * An internal function that was left for backward compatibility has
    been removed, as there is no remaining callers.
 
+ * Historically, the diff machinery for rename detection had a
+   hardcoded limit of 32k paths; this is being lifted to allow users
+   trade cycles with a (possibly) easier to read result.
+
+ * The tracing infrastructure has been optimized for cases where no
+   tracing is requested.
+
+ * In preparation for implementing narrow/partial clone, the object
+   walking machinery has been taught a way to tell it to "filter" some
+   objects from enumeration.
+
+ * A few structures and variables that are implementation details of
+   the decorate API have been renamed and then the API got documented
+   better.
+
+ * Assorted updates for TravisCI integration.
+   (merge 4f26366679 sg/travis-fixes later to maint).
+
 Also contains various documentation updates and code clean-ups.
 
 
@@ -304,10 +342,10 @@ Fixes since v2.15
  * "git branch --set-upstream" has been deprecated and (sort of)
    removed, as "--set-upstream-to" is the preferred one these days.
    The documentation still had "--set-upstream" listed on its
-   synopsys section, which has been corrected.
+   synopsis section, which has been corrected.
    (merge a060f3d3d8 tz/branch-doc-remove-set-upstream later to maint).
 
- * Internaly we use 0{40} as a placeholder object name to signal the
+ * Internally we use 0{40} as a placeholder object name to signal the
    codepath that there is no such object (e.g. the fast-forward check
    while "git fetch" stores a new remote-tracking ref says "we know
    there is no 'old' thing pointed at by the ref, as we are creating
@@ -329,6 +367,44 @@ Fixes since v2.15
  * A regression in the progress eye-candy was fixed.
    (merge 9c5951cacf jk/progress-delay-fix later to maint).
 
+ * The code internal to the recursive merge strategy was not fully
+   prepared to see a path that is renamed to try overwriting another
+   path that is only different in case on case insensitive systems.
+   This does not matter in the current code, but will start to matter
+   once the rename detection logic starts taking hints from nearby
+   paths moving to some directory and moves a new path along with them.
+   (merge 4cba2b0108 en/merge-recursive-icase-removal later to maint).
+
+ * An v2.12-era regression in pathspec match logic, which made it look
+   into submodule tree even when it is not desired, has been fixed.
+   (merge eef3df5a93 bw/pathspec-match-submodule-boundary later to maint).
+
+ * Amending commits in git-gui broke the author name that is non-ascii
+   due to incorrect enconding conversion.
+
+ * Recent update to the submodule configuration code broke "diff-tree"
+   by accidentally stopping to read from the index upfront.
+   (merge fd66bcc31f bw/submodule-config-cleanup later to maint).
+
+ * Git shows a message to tell the user that it is waiting for the
+   user to finish editing when spawning an editor, in case the editor
+   opens to a hidden window or somewhere obscure and the user gets
+   lost.
+   (merge abfb04d0c7 ls/editor-waiting-message later to maint).
+
+ * The "safe crlf" check incorrectly triggered for contents that does
+   not use CRLF as line endings, which has been corrected.
+   (merge 649f1f0948 tb/check-crlf-for-safe-crlf later to maint).
+
+ * "git clone --shared" to borrow from a (secondary) worktree did not
+   work, even though "git clone --local" did.  Both are now accepted.
+   (merge b3b05971c1 es/clone-shared-worktree later to maint).
+
+ * The build procedure now allows not just the repositories but also
+   the refs to be used to take pre-formatted manpages and html
+   documents to install.
+   (merge 65289e9dcd rb/quick-install-doc later to maint).
+
  * Other minor doc, test and build updates and code cleanups.
    (merge 1a1fc2d5b5 rd/man-prune-progress later to maint).
    (merge 0ba014035a rd/man-reflog-add-n later to maint).
@@ -338,3 +414,14 @@ Fixes since v2.15
    (merge 5a0526264b tg/t-readme-updates later to maint).
    (merge 5e83cca0b8 jk/no-optional-locks later to maint).
    (merge 826c778f7c js/hashmap-update-sample later to maint).
+   (merge 176b2d328c sg/setup-doc-update later to maint).
+   (merge 1b09073514 rs/am-builtin-leakfix later to maint).
+   (merge addcf6cfde rs/fmt-merge-msg-string-leak-fix later to maint).
+   (merge c3ff8f6c14 rs/strbuf-read-once-reset-length later to maint).
+   (merge 6b0eb884f9 db/doc-workflows-neuter-the-maintainer later to maint).
+   (merge 8c87bdfb21 jk/cvsimport-quoting later to maint).
+   (merge 176cb979fe rs/fmt-merge-msg-leakfix later to maint).
+   (merge 5a03360e73 tb/delimit-pretty-trailers-args-with-comma later to maint).
+   (merge d0e6326026 ot/pretty later to maint).
+   (merge 44103f4197 sb/test-helper-excludes later to maint).
+   (merge 170078693f jt/transport-no-more-rsync later to maint).
index c1598ee..7814fb9 100644 (file)
@@ -354,6 +354,9 @@ advice.*::
        ignoredHook::
                Advice shown if an hook is ignored because the hook is not
                set as executable.
+       waitingForEditor::
+               Print a message to the terminal whenever Git is waiting for
+               editor input from the user.
 --
 
 core.fileMode::
@@ -2733,36 +2736,7 @@ push.recurseSubmodules::
        is retained. You may override this configuration at time of push by
        specifying '--recurse-submodules=check|on-demand|no'.
 
-rebase.stat::
-       Whether to show a diffstat of what changed upstream since the last
-       rebase. False by default.
-
-rebase.autoSquash::
-       If set to true enable `--autosquash` option by default.
-
-rebase.autoStash::
-       When set to true, automatically create a temporary stash entry
-       before the operation begins, and apply it after the operation
-       ends.  This means that you can run rebase on a dirty worktree.
-       However, use with care: the final stash application after a
-       successful rebase might result in non-trivial conflicts.
-       Defaults to false.
-
-rebase.missingCommitsCheck::
-       If set to "warn", git rebase -i will print a warning if some
-       commits are removed (e.g. a line was deleted), however the
-       rebase will still proceed. If set to "error", it will print
-       the previous warning and stop the rebase, 'git rebase
-       --edit-todo' can then be used to correct the error. If set to
-       "ignore", no checking is done.
-       To drop a commit without warning or error, use the `drop`
-       command in the todo-list.
-       Defaults to "ignore".
-
-rebase.instructionFormat::
-       A format string, as specified in linkgit:git-log[1], to be used for
-       the instruction list during an interactive rebase.  The format will automatically
-       have the long commit hash prepended to the format.
+include::rebase-config.txt[]
 
 receive.advertiseAtomic::
        By default, git-receive-pack will advertise the atomic push
@@ -3468,3 +3442,13 @@ web.browser::
        Specify a web browser that may be used by some commands.
        Currently only linkgit:git-instaweb[1] and linkgit:git-help[1]
        may use it.
+
+worktree.guessRemote::
+       With `add`, if no branch argument, and neither of `-b` nor
+       `-B` nor `--detach` are given, the command defaults to
+       creating a new branch from HEAD.  If `worktree.guessRemote` is
+       set to true, `worktree add` tries to find a remote-tracking
+       branch whose name uniquely matches the new branch name.  If
+       such a branch exists, it is checked out and set as "upstream"
+       for the new branch.  If no such match can be found, it falls
+       back to creating a new branch from the current HEAD.
index 3c93c21..9d1586b 100644 (file)
@@ -80,6 +80,16 @@ endif::git-format-patch[]
 --histogram::
        Generate a diff using the "histogram diff" algorithm.
 
+--anchored=<text>::
+       Generate a diff using the "anchored diff" algorithm.
++
+This option may be specified more than once.
++
+If a line exists in both the source and destination, exists only once,
+and starts with this text, this algorithm attempts to prevent it from
+appearing as a deletion or addition in the output. It uses the "patience
+diff" algorithm internally.
+
 --diff-algorithm={patience|minimal|histogram|myers}::
        Choose a diff algorithm. The variants are as follows:
 +
index 83c8e9b..42ca7b5 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--dissociate] [--separate-git-dir <git dir>]
          [--depth <depth>] [--[no-]single-branch] [--no-tags]
-         [--recurse-submodules] [--[no-]shallow-submodules]
+         [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
          [--jobs <n>] [--] <repository> [<directory>]
 
 DESCRIPTION
@@ -231,14 +231,17 @@ branch of some repository for search indexing.
        After the clone is created, initialize and clone submodules
        within based on the provided pathspec.  If no pathspec is
        provided, all submodules are initialized and cloned.
-       Submodules are initialized and cloned using their default
-       settings.  The resulting clone has `submodule.active` set to
+       This option can be given multiple times for pathspecs consisting
+       of multiple entries.  The resulting clone has `submodule.active` set to
        the provided pathspec, or "." (meaning all submodules) if no
-       pathspec is provided.  This is equivalent to running
-       `git submodule update --init --recursive` immediately after
-       the clone is finished. This option is ignored if the cloned
-       repository does not have a worktree/checkout (i.e. if any of
-       `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
+       pathspec is provided.
++
+Submodules are initialized and cloned using their default settings. This is
+equivalent to running
+`git submodule update --init --recursive <pathspec>` immediately after
+the clone is finished. This option is ignored if the cloned repository does
+not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`,
+or `--mirror` is given)
 
 --[no-]shallow-submodules::
        All submodules which are cloned will be shallow with a depth of 1.
index 473a161..aa403d0 100644 (file)
@@ -12,7 +12,8 @@ SYNOPSIS
 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied]
        [--no-reuse-delta] [--delta-base-offset] [--non-empty]
        [--local] [--incremental] [--window=<n>] [--depth=<n>]
-       [--revs [--unpacked | --all]] [--stdout | base-name]
+       [--revs [--unpacked | --all]]
+       [--stdout [--filter=<filter-spec>] | base-name]
        [--shallow] [--keep-true-parents] < object-list
 
 
@@ -236,6 +237,25 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.
        With this option, parents that are hidden by grafts are packed
        nevertheless.
 
+--filter=<filter-spec>::
+       Requires `--stdout`.  Omits certain objects (usually blobs) from
+       the resulting packfile.  See linkgit:git-rev-list[1] for valid
+       `<filter-spec>` forms.
+
+--no-filter::
+       Turns off any previous `--filter=` argument.
+
+--missing=<missing-action>::
+       A debug option to help with future "partial clone" development.
+       This option specifies how missing objects are handled.
++
+The form '--missing=error' requests that pack-objects stop with an error if
+a missing object is encountered.  This is the default action.
++
+The form '--missing=allow-any' will allow object traversal to continue
+if a missing object is encountered.  Missing objects will silently be
+omitted from the results.
+
 SEE ALSO
 --------
 linkgit:git-rev-list[1]
index 3cedfb0..8a861c1 100644 (file)
@@ -203,24 +203,7 @@ Alternatively, you can undo the 'git rebase' with
 CONFIGURATION
 -------------
 
-rebase.stat::
-       Whether to show a diffstat of what changed upstream since the last
-       rebase. False by default.
-
-rebase.autoSquash::
-       If set to true enable `--autosquash` option by default.
-
-rebase.autoStash::
-       If set to true enable `--autostash` option by default.
-
-rebase.missingCommitsCheck::
-       If set to "warn", print warnings about removed commits in
-       interactive mode. If set to "error", print the warnings and
-       stop the rebase. If set to "ignore", no checking is
-       done. "ignore" by default.
-
-rebase.instructionFormat::
-       Custom commit list format to use during an `--interactive` rebase.
+include::rebase-config.txt[]
 
 OPTIONS
 -------
index ef22f17..88609ff 100644 (file)
@@ -47,7 +47,9 @@ SYNOPSIS
             [ --fixed-strings | -F ]
             [ --date=<format>]
             [ [ --objects | --objects-edge | --objects-edge-aggressive ]
-              [ --unpacked ] ]
+              [ --unpacked ]
+              [ --filter=<filter-spec> [ --filter-print-omitted ] ] ]
+            [ --missing=<missing-action> ]
             [ --pretty | --header ]
             [ --bisect ]
             [ --bisect-vars ]
index b472acc..f850e8f 100644 (file)
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
 SYNOPSIS
 --------
 [verse]
-'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<branch>]
+'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -45,14 +45,22 @@ specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
-add <path> [<branch>]::
+add <path> [<commit-ish>]::
 
-Create `<path>` and checkout `<branch>` into it. The new working directory
+Create `<path>` and checkout `<commit-ish>` into it. The new working directory
 is linked to the current repository, sharing everything except working
 directory specific files such as HEAD, index, etc. `-` may also be
-specified as `<branch>`; it is synonymous with `@{-1}`.
+specified as `<commit-ish>`; it is synonymous with `@{-1}`.
 +
-If `<branch>` is omitted and neither `-b` nor `-B` nor `--detach` used,
+If <commit-ish> is a branch name (call it `<branch>` and is not found,
+and neither `-b` nor `-B` nor `--detach` are used, but there does
+exist a tracking branch in exactly one remote (call it `<remote>`)
+with a matching name, treat as equivalent to
+------------
+$ git worktree add --track -b <branch> <path> <remote>/<branch>
+------------
++
+If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
 then, as a convenience, a new branch based at HEAD is created automatically,
 as if `-b $(basename <path>)` was specified.
 
@@ -84,29 +92,45 @@ OPTIONS
 
 -f::
 --force::
-       By default, `add` refuses to create a new working tree when `<branch>`
+       By default, `add` refuses to create a new working tree when `<commit-ish>` is a branch name and
        is already checked out by another working tree. This option overrides
        that safeguard.
 
 -b <new-branch>::
 -B <new-branch>::
        With `add`, create a new branch named `<new-branch>` starting at
-       `<branch>`, and check out `<new-branch>` into the new working tree.
-       If `<branch>` is omitted, it defaults to HEAD.
+       `<commit-ish>`, and check out `<new-branch>` into the new working tree.
+       If `<commit-ish>` is omitted, it defaults to HEAD.
        By default, `-b` refuses to create a new branch if it already
        exists. `-B` overrides this safeguard, resetting `<new-branch>` to
-       `<branch>`.
+       `<commit-ish>`.
 
 --detach::
        With `add`, detach HEAD in the new working tree. See "DETACHED HEAD"
        in linkgit:git-checkout[1].
 
 --[no-]checkout::
-       By default, `add` checks out `<branch>`, however, `--no-checkout` can
+       By default, `add` checks out `<commit-ish>`, however, `--no-checkout` can
        be used to suppress checkout in order to make customizations,
        such as configuring sparse-checkout. See "Sparse checkout"
        in linkgit:git-read-tree[1].
 
+--[no-]guess-remote::
+       With `worktree add <path>`, without `<commit-ish>`, instead
+       of creating a new branch from HEAD, if there exists a tracking
+       branch in exactly one remote matching the basename of `<path>,
+       base the new branch on the remote-tracking branch, and mark
+       the remote-tracking branch as "upstream" from the new branch.
++
+This can also be set up as the default behaviour by using the
+`worktree.guessRemote` config option.
+
+--[no-]track::
+       When creating a new branch, if `<commit-ish>` is a branch,
+       mark it as "upstream" from the new branch.  This is the
+       default if `<commit-ish>` is a remote-tracking branch.  See
+       "--track" in linkgit:git-branch[1] for details.
+
 --lock::
        Keep the working tree locked after creation. This is the
        equivalent of `git worktree lock` after `git worktree add`,
index e75db10..3f4161a 100644 (file)
@@ -736,6 +736,15 @@ corresponding standard handle, and if `GIT_REDIRECT_STDERR` is
 `2>&1`, standard error will be redirected to the same handle as
 standard output.
 
+`GIT_PRINT_SHA1_ELLIPSIS` (deprecated)::
+       If set to `yes`, print an ellipsis following an
+       (abbreviated) SHA-1 value.  This affects indications of
+       detached HEADs (linkgit:git-checkout[1]) and the raw
+       diff output (linkgit:git-diff[1]).  Printing an
+       ellipsis in the cases mentioned is no longer considered
+       adequate and support for it is likely to be removed in the
+       foreseeable future (along with the variable).
+
 Discussion[[Discussion]]
 ------------------------
 
index b63f2ea..f877f7b 100644 (file)
@@ -170,7 +170,8 @@ This hook cannot affect the outcome of 'git checkout'.
 
 It is also run after 'git clone', unless the --no-checkout (-n) option is
 used. The first parameter given to the hook is the null-ref, the second the
-ref of the new HEAD and the flag is always 1.
+ref of the new HEAD and the flag is always 1. Likewise for 'git worktree add'
+unless --no-checkout is used.
 
 This hook can be used to perform repository validity checks, auto-display
 differences from the previous HEAD if different, or set working dir metadata
index 02569d0..926e044 100644 (file)
@@ -407,8 +407,8 @@ follows.
 `git pull <url> <branch>`
 =====================================
 
-Occasionally, the maintainer may get merge conflicts when he tries to
-pull changes from downstream.  In this case, he can ask downstream to
+Occasionally, the maintainer may get merge conflicts when they try to
+pull changes from downstream.  In this case, they can ask downstream to
 do the merge and resolve the conflicts themselves (perhaps they will
 know better how to resolve them).  It is one of the rare cases where
 downstream 'should' merge from upstream.
index 327f69b..17231d8 100755 (executable)
@@ -3,11 +3,12 @@
 
 repository=${1?repository}
 destdir=${2?destination}
+GIT_MAN_REF=${3?master}
 
-head=master GIT_DIR=
+GIT_DIR=
 for d in "$repository/.git" "$repository"
 do
-       if GIT_DIR="$d" git rev-parse refs/heads/master >/dev/null 2>&1
+       if GIT_DIR="$d" git rev-parse "$GIT_MAN_REF" >/dev/null 2>&1
        then
                GIT_DIR="$d"
                export GIT_DIR
@@ -27,12 +28,12 @@ export GIT_INDEX_FILE GIT_WORK_TREE
 rm -f "$GIT_INDEX_FILE"
 trap 'rm -f "$GIT_INDEX_FILE"' 0
 
-git read-tree $head
+git read-tree "$GIT_MAN_REF"
 git checkout-index -a -f --prefix="$destdir"/
 
 if test -n "$GZ"
 then
-       git ls-tree -r --name-only $head |
+       git ls-tree -r --name-only "$GIT_MAN_REF" |
        xargs printf "$destdir/%s\n" |
        xargs gzip -f
 fi
index d433d50..e664c08 100644 (file)
@@ -204,11 +204,13 @@ endif::git-rev-list[]
   than given and there are spaces on its left, use those spaces
 - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)'
   respectively, but padding both sides (i.e. the text is centered)
-- %(trailers): display the trailers of the body as interpreted by
-  linkgit:git-interpret-trailers[1]. If the `:only` option is given,
-  omit non-trailer lines from the trailer block.  If the `:unfold`
-  option is given, behave as if interpret-trailer's `--unfold` option
-  was given. E.g., `%(trailers:only:unfold)` to do both.
+- %(trailers[:options]): display the trailers of the body as interpreted
+  by linkgit:git-interpret-trailers[1]. The `trailers` string may be
+  followed by a colon and zero or more comma-separated options. If the
+  `only` option is given, omit non-trailer lines from the trailer block.
+  If the `unfold` option is given, behave as if interpret-trailer's
+  `--unfold` option was given.  E.g., `%(trailers:only,unfold)` to do
+  both.
 
 NOTE: Some placeholders may depend on other options given to the
 revision traversal engine. For example, the `%g*` reflog options will
diff --git a/Documentation/rebase-config.txt b/Documentation/rebase-config.txt
new file mode 100644 (file)
index 0000000..42e1ba7
--- /dev/null
@@ -0,0 +1,52 @@
+rebase.stat::
+       Whether to show a diffstat of what changed upstream since the last
+       rebase. False by default.
+
+rebase.autoSquash::
+       If set to true enable `--autosquash` option by default.
+
+rebase.autoStash::
+       When set to true, automatically create a temporary stash entry
+       before the operation begins, and apply it after the operation
+       ends.  This means that you can run rebase on a dirty worktree.
+       However, use with care: the final stash application after a
+       successful rebase might result in non-trivial conflicts.
+       This option can be overridden by the `--no-autostash` and
+       `--autostash` options of linkgit:git-rebase[1].
+       Defaults to false.
+
+rebase.missingCommitsCheck::
+       If set to "warn", git rebase -i will print a warning if some
+       commits are removed (e.g. a line was deleted), however the
+       rebase will still proceed. If set to "error", it will print
+       the previous warning and stop the rebase, 'git rebase
+       --edit-todo' can then be used to correct the error. If set to
+       "ignore", no checking is done.
+       To drop a commit without warning or error, use the `drop`
+       command in the todo list.
+       Defaults to "ignore".
+
+rebase.instructionFormat::
+       A format string, as specified in linkgit:git-log[1], to be used for the
+       todo list during an interactive rebase.  The format will
+       automatically have the long commit hash prepended to the format.
+
+rebase.abbreviateCommands::
+       If set to true, `git rebase` will use abbreviated command names in the
+       todo list resulting in something like this:
++
+-------------------------------------------
+       p deadbee The oneline of the commit
+       p fa1afe1 The oneline of the next commit
+       ...
+-------------------------------------------
++
+instead of:
++
+-------------------------------------------
+       pick deadbee The oneline of the commit
+       pick fa1afe1 The oneline of the next commit
+       ...
+-------------------------------------------
++
+Defaults to false.
index 13501e1..8d8b7f4 100644 (file)
@@ -706,6 +706,47 @@ ifdef::git-rev-list[]
 --unpacked::
        Only useful with `--objects`; print the object IDs that are not
        in packs.
+
+--filter=<filter-spec>::
+       Only useful with one of the `--objects*`; omits objects (usually
+       blobs) from the list of printed objects.  The '<filter-spec>'
+       may be one of the following:
++
+The form '--filter=blob:none' omits all blobs.
++
+The form '--filter=blob:limit=<n>[kmg]' omits blobs larger than n bytes
+or units.  n may be zero.  The suffixes k, m, and g can be used to name
+units in KiB, MiB, or GiB.  For example, 'blob:limit=1k' is the same
+as 'blob:limit=1024'.
++
+The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout
+specification contained in the blob (or blob-expression) '<blob-ish>'
+to omit blobs that would not be not required for a sparse checkout on
+the requested refs.
++
+The form '--filter=sparse:path=<path>' similarly uses a sparse-checkout
+specification contained in <path>.
+
+--no-filter::
+       Turn off any previous `--filter=` argument.
+
+--filter-print-omitted::
+       Only useful with `--filter=`; prints a list of the objects omitted
+       by the filter.  Object IDs are prefixed with a ``~'' character.
+
+--missing=<missing-action>::
+       A debug option to help with future "partial clone" development.
+       This option specifies how missing objects are handled.
++
+The form '--missing=error' requests that rev-list stop with an error if
+a missing object is encountered.  This is the default action.
++
+The form '--missing=allow-any' will allow object traversal to continue
+if a missing object is encountered.  Missing objects will silently be
+omitted from the results.
++
+The form '--missing=print' is like 'allow-any', but will also print a
+list of the missing objects.  Object IDs are prefixed with a ``?'' character.
 endif::git-rev-list[]
 
 --no-walk[=(sorted|unsorted)]::
index 6127746..dfcc49c 100644 (file)
@@ -271,7 +271,7 @@ The '..' (two-dot) Range Notation::
  for commits that are reachable from r2 excluding those that are reachable
  from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'.
 
-The '...' (three dot) Symmetric Difference Notation::
+The '...' (three-dot) Symmetric Difference Notation::
  A similar notation 'r1\...r2' is called symmetric difference
  of 'r1' and 'r2' and is defined as
  'r1 r2 --not $(git merge-base --all r1 r2)'.
diff --git a/Documentation/technical/api-decorate.txt b/Documentation/technical/api-decorate.txt
deleted file mode 100644 (file)
index 1d52a6c..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-decorate API
-============
-
-Talk about <decorate.h>
-
-(Linus)
index 3a03e63..eff7890 100644 (file)
@@ -319,7 +319,7 @@ do so (now or later) by using -b with the checkout command again. Example:
 
   git checkout -b new_branch_name
 
-HEAD is now at 427abfa... Linux v2.6.17
+HEAD is now at 427abfa Linux v2.6.17
 ------------------------------------------------
 
 The HEAD then refers to the SHA-1 of the commit instead of to a branch,
@@ -508,7 +508,7 @@ Bisecting: 3537 revisions left to test after this
 
 If you run `git branch` at this point, you'll see that Git has
 temporarily moved you in "(no branch)". HEAD is now detached from any
-branch and points directly to a commit (with commit id 65934...) that
+branch and points directly to a commit (with commit id 65934) that
 is reachable from "master" but not from v2.6.18. Compile and test it,
 and see whether it crashes. Assume it does crash. Then:
 
@@ -549,14 +549,14 @@ says "bisect".  Choose a safe-looking commit nearby, note its commit
 id, and check it out with:
 
 -------------------------------------------------
-$ git reset --hard fb47ddb2db...
+$ git reset --hard fb47ddb2db
 -------------------------------------------------
 
 then test, run `bisect good` or `bisect bad` as appropriate, and
 continue.
 
 Instead of `git bisect visualize` and then `git reset --hard
-fb47ddb2db...`, you might just want to tell Git that you want to skip
+fb47ddb2db`, you might just want to tell Git that you want to skip
 the current commit:
 
 -------------------------------------------------
@@ -3416,7 +3416,7 @@ commit abc
 Author:
 Date:
 ...
-:100644 100644 4b9458b... newsha... M somedirectory/myfile
+:100644 100644 4b9458b newsha M somedirectory/myfile
 
 
 commit xyz
@@ -3424,7 +3424,7 @@ Author:
 Date:
 
 ...
-:100644 100644 oldsha... 4b9458b... M somedirectory/myfile
+:100644 100644 oldsha 4b9458b M somedirectory/myfile
 ------------------------------------------------
 
 This tells you that the immediately following version of the file was
@@ -3449,7 +3449,7 @@ and your repository is good again!
 $ git log --raw --all
 ------------------------------------------------
 
-and just looked for the sha of the missing object (4b9458b..) in that
+and just looked for the sha of the missing object (4b9458b) in that
 whole thing. It's up to you--Git does *have* a lot of information, it is
 just missing one particular blob version.
 
@@ -4114,9 +4114,9 @@ program, e.g.  `diff3`, `merge`, or Git's own merge-file, on
 the blob objects from these three stages yourself, like this:
 
 ------------------------------------------------
-$ git cat-file blob 263414f... >hello.c~1
-$ git cat-file blob 06fa6a2... >hello.c~2
-$ git cat-file blob cc44c73... >hello.c~3
+$ git cat-file blob 263414f >hello.c~1
+$ git cat-file blob 06fa6a2 >hello.c~2
+$ git cat-file blob cc44c73 >hello.c~3
 $ git merge-file hello.c~2 hello.c~1 hello.c~3
 ------------------------------------------------
 
@@ -4374,7 +4374,7 @@ $ git log --no-merges t/
 ------------------------
 
 In the pager (`less`), just search for "bundle", go a few lines back,
-and see that it is in commit 18449ab0...  Now just copy this object name,
+and see that it is in commit 18449ab0.  Now just copy this object name,
 and paste it into the command line
 
 -------------------
index fef9c8d..32c1706 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -651,6 +651,7 @@ TEST_PROGRAMS_NEED_X += test-dump-cache-tree
 TEST_PROGRAMS_NEED_X += test-dump-fsmonitor
 TEST_PROGRAMS_NEED_X += test-dump-split-index
 TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
+TEST_PROGRAMS_NEED_X += test-example-decorate
 TEST_PROGRAMS_NEED_X += test-fake-ssh
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-hashmap
@@ -759,6 +760,7 @@ LIB_OBJS += branch.o
 LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
+LIB_OBJS += checkout.o
 LIB_OBJS += color.o
 LIB_OBJS += column.o
 LIB_OBJS += combine-diff.o
@@ -810,6 +812,8 @@ LIB_OBJS += levenshtein.o
 LIB_OBJS += line-log.o
 LIB_OBJS += line-range.o
 LIB_OBJS += list-objects.o
+LIB_OBJS += list-objects-filter.o
+LIB_OBJS += list-objects-filter-options.o
 LIB_OBJS += ll-merge.o
 LIB_OBJS += lockfile.o
 LIB_OBJS += log-tree.o
index c6169bc..406efc1 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -18,6 +18,7 @@ int advice_object_name_warning = 1;
 int advice_rm_hints = 1;
 int advice_add_embedded_repo = 1;
 int advice_ignored_hook = 1;
+int advice_waiting_for_editor = 1;
 
 static struct {
        const char *name;
@@ -40,6 +41,7 @@ static struct {
        { "rmhints", &advice_rm_hints },
        { "addembeddedrepo", &advice_add_embedded_repo },
        { "ignoredhook", &advice_ignored_hook },
+       { "waitingforeditor", &advice_waiting_for_editor },
 
        /* make this an alias for backward compatibility */
        { "pushnonfastforward", &advice_push_update_rejected }
index f525d6f..70568fa 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -20,6 +20,7 @@ extern int advice_object_name_warning;
 extern int advice_rm_hints;
 extern int advice_add_embedded_repo;
 extern int advice_ignored_hook;
+extern int advice_waiting_for_editor;
 
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
index fe1e1c3..2672054 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -244,7 +244,7 @@ N_("\n"
 "\"git push -u\" to set the upstream config as you push.");
 
 void create_branch(const char *name, const char *start_name,
-                  int force, int reflog, int clobber_head,
+                  int force, int clobber_head_ok, int reflog,
                   int quiet, enum branch_track track)
 {
        struct commit *commit;
@@ -258,7 +258,7 @@ void create_branch(const char *name, const char *start_name,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if ((track == BRANCH_TRACK_OVERRIDE || clobber_head)
+       if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)
            ? validate_branchname(name, &ref)
            : validate_new_branchname(name, &ref, force)) {
                if (!force)
index be5e5d1..473d0a9 100644 (file)
--- a/branch.h
+++ b/branch.h
  *
  *   - force enables overwriting an existing (non-head) branch
  *
+ *   - clobber_head_ok allows the currently checked out (hence existing)
+ *     branch to be overwritten; without 'force', it has no effect.
+ *
  *   - reflog creates a reflog for the branch
  *
+ *   - quiet suppresses tracking information
+ *
  *   - track causes the new branch to be configured to merge the remote branch
  *     that start_name is a tracking branch for (if any).
+ *
  */
 void create_branch(const char *name, const char *start_name,
-                  int force, int reflog,
-                  int clobber_head, int quiet, enum branch_track track);
+                  int force, int clobber_head_ok,
+                  int reflog, int quiet, enum branch_track track);
 
 /*
  * Check if 'name' can be a valid name for a branch; die otherwise.
index 3d98e52..06b8245 100644 (file)
@@ -708,6 +708,7 @@ static int split_mail_mbox(struct am_state *state, const char **paths,
 {
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf last = STRBUF_INIT;
+       int ret;
 
        cp.git_cmd = 1;
        argv_array_push(&cp.args, "mailsplit");
@@ -721,13 +722,16 @@ static int split_mail_mbox(struct am_state *state, const char **paths,
        argv_array_push(&cp.args, "--");
        argv_array_pushv(&cp.args, paths);
 
-       if (capture_command(&cp, &last, 8))
-               return -1;
+       ret = capture_command(&cp, &last, 8);
+       if (ret)
+               goto exit;
 
        state->cur = 1;
        state->last = strtol(last.buf, NULL, 10);
 
-       return 0;
+exit:
+       strbuf_release(&last);
+       return ret ? -1 : 0;
 }
 
 /**
index af95ad2..8dcc2ed 100644 (file)
@@ -462,6 +462,8 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 {
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+       const char *interpreted_oldname = NULL;
+       const char *interpreted_newname = NULL;
        int recovery = 0;
 
        if (!oldname) {
@@ -493,6 +495,11 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 
        reject_rebase_or_bisect_branch(oldref.buf);
 
+       if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
+           !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
+               die("BUG: expected prefix missing for refs");
+       }
+
        if (copy)
                strbuf_addf(&logmsg, "Branch: copied %s to %s",
                            oldref.buf, newref.buf);
@@ -507,11 +514,11 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 
        if (recovery) {
                if (copy)
-                       warning(_("Copied a misnamed branch '%s' away"),
-                               oldref.buf + 11);
+                       warning(_("Created a copy of a misnamed branch '%s'"),
+                               interpreted_oldname);
                else
                        warning(_("Renamed a misnamed branch '%s' away"),
-                               oldref.buf + 11);
+                               interpreted_oldname);
        }
 
        if (!copy &&
@@ -520,9 +527,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 
        strbuf_release(&logmsg);
 
-       strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
+       strbuf_addf(&oldsection, "branch.%s", interpreted_oldname);
        strbuf_release(&oldref);
-       strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
+       strbuf_addf(&newsection, "branch.%s", interpreted_newname);
        strbuf_release(&newref);
        if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
                die(_("Branch is renamed, but update of config-file failed"));
@@ -806,7 +813,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
 
                create_branch(argv[0], (argc == 2) ? argv[1] : head,
-                             force, reflog, 0, quiet, track);
+                             force, 0, reflog, quiet, track);
 
        } else
                usage_with_options(builtin_branch_usage, options);
index e1e157d..8bdc927 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "config.h"
+#include "checkout.h"
 #include "lockfile.h"
 #include "parse-options.h"
 #include "refs.h"
@@ -400,10 +401,16 @@ static void show_local_changes(struct object *head,
 static void describe_detached_head(const char *msg, struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
+
        if (!parse_commit(commit))
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
-       fprintf(stderr, "%s %s... %s\n", msg,
-               find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
+       if (print_sha1_ellipsis()) {
+               fprintf(stderr, "%s %s... %s\n", msg,
+                       find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
+       } else {
+               fprintf(stderr, "%s %s %s\n", msg,
+                       find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
+       }
        strbuf_release(&sb);
 }
 
@@ -640,8 +647,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                else
                        create_branch(opts->new_branch, new->name,
                                      opts->new_branch_force ? 1 : 0,
-                                     opts->new_branch_log,
                                      opts->new_branch_force ? 1 : 0,
+                                     opts->new_branch_log,
                                      opts->quiet,
                                      opts->track);
                new->name = opts->new_branch;
@@ -872,46 +879,6 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
        return git_xmerge_config(var, value, NULL);
 }
 
-struct tracking_name_data {
-       /* const */ char *src_ref;
-       char *dst_ref;
-       struct object_id *dst_oid;
-       int unique;
-};
-
-static int check_tracking_name(struct remote *remote, void *cb_data)
-{
-       struct tracking_name_data *cb = cb_data;
-       struct refspec query;
-       memset(&query, 0, sizeof(struct refspec));
-       query.src = cb->src_ref;
-       if (remote_find_tracking(remote, &query) ||
-           get_oid(query.dst, cb->dst_oid)) {
-               free(query.dst);
-               return 0;
-       }
-       if (cb->dst_ref) {
-               free(query.dst);
-               cb->unique = 0;
-               return 0;
-       }
-       cb->dst_ref = query.dst;
-       return 0;
-}
-
-static const char *unique_tracking_name(const char *name, struct object_id *oid)
-{
-       struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
-       cb_data.src_ref = xstrfmt("refs/heads/%s", name);
-       cb_data.dst_oid = oid;
-       for_each_remote(check_tracking_name, &cb_data);
-       free(cb_data.src_ref);
-       if (cb_data.unique)
-               return cb_data.dst_ref;
-       free(cb_data.dst_ref);
-       return NULL;
-}
-
 static int parse_branchname_arg(int argc, const char **argv,
                                int dwim_new_local_branch_ok,
                                struct branch_info *new,
index b228457..6ad0ab3 100644 (file)
@@ -452,7 +452,8 @@ static void clone_local(const char *src_repo, const char *dest_repo)
 {
        if (option_shared) {
                struct strbuf alt = STRBUF_INIT;
-               strbuf_addf(&alt, "%s/objects", src_repo);
+               get_common_dir(&alt, src_repo);
+               strbuf_addstr(&alt, "/objects");
                add_to_alternates_file(alt.buf);
                strbuf_release(&alt);
        } else {
index d664999..b775a75 100644 (file)
@@ -110,6 +110,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
 
        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
        init_revisions(opt, prefix);
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
        opt->abbrev = 0;
        opt->diff = 1;
        opt->disable_stdin = 1;
index f8fe04c..796d0cd 100644 (file)
@@ -895,7 +895,7 @@ static void export_marks(char *file)
 {
        unsigned int i;
        uint32_t mark;
-       struct object_decoration *deco = idnums.hash;
+       struct decoration_entry *deco = idnums.entries;
        FILE *f;
        int e = 0;
 
index e705237..e656746 100644 (file)
@@ -3,6 +3,7 @@
  */
 #include "cache.h"
 #include "config.h"
+#include "repository.h"
 #include "refs.h"
 #include "commit.h"
 #include "builtin.h"
@@ -1397,7 +1398,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                struct argv_array options = ARGV_ARRAY_INIT;
 
                add_options_to_argv(&options);
-               result = fetch_populated_submodules(&options,
+               result = fetch_populated_submodules(the_repository,
+                                                   &options,
                                                    submodule_prefix,
                                                    recurse_submodules,
                                                    recurse_submodules_default,
index 22034f8..8e8a15e 100644 (file)
@@ -377,7 +377,8 @@ static void shortlog(const char *name,
                        string_list_append(&subjects,
                                           oid_to_hex(&commit->object.oid));
                else
-                       string_list_append(&subjects, strbuf_detach(&sb, NULL));
+                       string_list_append_nodup(&subjects,
+                                                strbuf_detach(&sb, NULL));
        }
 
        if (opts->credit_people)
index 5a6cfe6..3ca4ac8 100644 (file)
@@ -1015,6 +1015,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                       prefix, argv + i);
        pathspec.max_depth = opt.max_depth;
        pathspec.recursive = 1;
+       pathspec.recurse_submodules = !!recurse_submodules;
 
 #ifndef NO_PTHREADS
        if (list.nr || cached || show_in_pager)
index 8ec459f..4c51aec 100644 (file)
@@ -1660,10 +1660,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                from_stdin = 1;
                        } else if (!strcmp(arg, "--fix-thin")) {
                                fix_thin_pack = 1;
-                       } else if (!strcmp(arg, "--strict")) {
-                               strict = 1;
-                               do_fsck_object = 1;
-                       } else if (skip_prefix(arg, "--strict=", &arg)) {
+                       } else if (skip_to_optional_arg(arg, "--strict", &arg)) {
                                strict = 1;
                                do_fsck_object = 1;
                                fsck_set_msg_types(&fsck_options, arg);
@@ -1679,10 +1676,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                verify = 1;
                                show_stat = 1;
                                stat_only = 1;
-                       } else if (!strcmp(arg, "--keep")) {
-                               keep_msg = "";
-                       } else if (starts_with(arg, "--keep=")) {
-                               keep_msg = arg + 7;
+                       } else if (skip_to_optional_arg(arg, "--keep", &keep_msg)) {
+                               ; /* nothing to do */
                        } else if (starts_with(arg, "--threads=")) {
                                char *end;
                                nr_threads = strtoul(arg+10, &end, 0);
index ffdd5f0..cf3684d 100644 (file)
@@ -291,7 +291,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        }
 
        if (gitmodules_modified)
-               stage_updated_gitmodules();
+               stage_updated_gitmodules(&the_index);
 
        if (active_cache_changed &&
            write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
index 1a2c7d9..7c81761 100644 (file)
@@ -12,7 +12,7 @@
 #include "builtin.h"
 #include "notes.h"
 #include "blob.h"
-#include "commit.h"
+#include "pretty.h"
 #include "refs.h"
 #include "exec_cmd.h"
 #include "run-command.h"
index 631de28..6b9cfc2 100644 (file)
@@ -15,6 +15,8 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
 #include "pack-objects.h"
 #include "progress.h"
 #include "refs.h"
@@ -79,6 +81,15 @@ static unsigned long cache_max_small_delta_size = 1000;
 
 static unsigned long window_memory_limit = 0;
 
+static struct list_objects_filter_options filter_options;
+
+enum missing_action {
+       MA_ERROR = 0,    /* fail if any missing objects are encountered */
+       MA_ALLOW_ANY,    /* silently allow ALL missing objects */
+};
+static enum missing_action arg_missing_action;
+static show_object_fn fn_show_object;
+
 /*
  * stats
  */
@@ -2553,6 +2564,42 @@ static void show_object(struct object *obj, const char *name, void *data)
        obj->flags |= OBJECT_ADDED;
 }
 
+static void show_object__ma_allow_any(struct object *obj, const char *name, void *data)
+{
+       assert(arg_missing_action == MA_ALLOW_ANY);
+
+       /*
+        * Quietly ignore ALL missing objects.  This avoids problems with
+        * staging them now and getting an odd error later.
+        */
+       if (!has_object_file(&obj->oid))
+               return;
+
+       show_object(obj, name, data);
+}
+
+static int option_parse_missing_action(const struct option *opt,
+                                      const char *arg, int unset)
+{
+       assert(arg);
+       assert(!unset);
+
+       if (!strcmp(arg, "error")) {
+               arg_missing_action = MA_ERROR;
+               fn_show_object = show_object;
+               return 0;
+       }
+
+       if (!strcmp(arg, "allow-any")) {
+               arg_missing_action = MA_ALLOW_ANY;
+               fn_show_object = show_object__ma_allow_any;
+               return 0;
+       }
+
+       die(_("invalid value for --missing"));
+       return 0;
+}
+
 static void show_edge(struct commit *commit)
 {
        add_preferred_base(&commit->object.oid);
@@ -2817,7 +2864,12 @@ static void get_object_list(int ac, const char **av)
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        mark_edges_uninteresting(&revs, show_edge);
-       traverse_commit_list(&revs, show_commit, show_object, NULL);
+
+       if (!fn_show_object)
+               fn_show_object = show_object;
+       traverse_commit_list_filtered(&filter_options, &revs,
+                                     show_commit, fn_show_object, NULL,
+                                     NULL);
 
        if (unpack_unreachable_expiration) {
                revs.ignore_missing_links = 1;
@@ -2953,6 +3005,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                         N_("use a bitmap index if available to speed up counting objects")),
                OPT_BOOL(0, "write-bitmap-index", &write_bitmap_index,
                         N_("write a bitmap index together with the pack index")),
+               OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+               { OPTION_CALLBACK, 0, "missing", NULL, N_("action"),
+                 N_("handling for missing objects"), PARSE_OPT_NONEG,
+                 option_parse_missing_action },
                OPT_END(),
        };
 
@@ -3029,6 +3085,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (!rev_list_all || !rev_list_reflog || !rev_list_index)
                unpack_unreachable_expiration = 0;
 
+       if (filter_options.choice) {
+               if (!pack_to_stdout)
+                       die("cannot use --filter without --stdout.");
+               use_bitmap_index = 0;
+       }
+
        /*
         * "soft" reasons not to use bitmaps - for on-disk repack by default we want
         *
index f851936..7daee54 100644 (file)
@@ -12,10 +12,12 @@ static const char * const builtin_rebase_helper_usage[] = {
 int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 {
        struct replay_opts opts = REPLAY_OPTS_INIT;
-       int keep_empty = 0;
+       unsigned flags = 0, keep_empty = 0;
+       int abbreviate_commands = 0;
        enum {
-               CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
-               CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH
+               CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
+               CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
+               ADD_EXEC
        } command = 0;
        struct option options[] = {
                OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -27,19 +29,22 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE(0, "make-script", &command,
                        N_("make rebase script"), MAKE_SCRIPT),
                OPT_CMDMODE(0, "shorten-ids", &command,
-                       N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S),
+                       N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
                OPT_CMDMODE(0, "expand-ids", &command,
-                       N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
+                       N_("expand commit ids in the todo list"), EXPAND_OIDS),
                OPT_CMDMODE(0, "check-todo-list", &command,
                        N_("check the todo list"), CHECK_TODO_LIST),
                OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
                        N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
                OPT_CMDMODE(0, "rearrange-squash", &command,
                        N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
+               OPT_CMDMODE(0, "add-exec-commands", &command,
+                       N_("insert exec commands in todo list"), ADD_EXEC),
                OPT_END()
        };
 
        git_config(git_default_config, NULL);
+       git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
 
        opts.action = REPLAY_INTERACTIVE_REBASE;
        opts.allow_ff = 1;
@@ -48,21 +53,25 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, NULL, options,
                        builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
 
+       flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+       flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+       flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+
        if (command == CONTINUE && argc == 1)
                return !!sequencer_continue(&opts);
        if (command == ABORT && argc == 1)
                return !!sequencer_remove_state(&opts);
        if (command == MAKE_SCRIPT && argc > 1)
-               return !!sequencer_make_script(keep_empty, stdout, argc, argv);
-       if (command == SHORTEN_SHA1S && argc == 1)
-               return !!transform_todo_ids(1);
-       if (command == EXPAND_SHA1S && argc == 1)
-               return !!transform_todo_ids(0);
+               return !!sequencer_make_script(stdout, argc, argv, flags);
+       if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1)
+               return !!transform_todos(flags);
        if (command == CHECK_TODO_LIST && argc == 1)
                return !!check_todo_list();
        if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
                return !!skip_unnecessary_picks();
        if (command == REARRANGE_SQUASH && argc == 1)
                return !!rearrange_squash();
+       if (command == ADD_EXEC && argc == 2)
+               return !!sequencer_add_exec_commands(argv[1]);
        usage_with_options(builtin_rebase_helper_usage, options);
 }
index 906e541..e15f595 100644 (file)
@@ -12,7 +12,7 @@
 #include "lockfile.h"
 #include "tag.h"
 #include "object.h"
-#include "commit.h"
+#include "pretty.h"
 #include "run-command.h"
 #include "refs.h"
 #include "diff.h"
index 4032eb3..d5345b6 100644 (file)
@@ -4,6 +4,8 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
 #include "pack.h"
 #include "pack-bitmap.h"
 #include "builtin.h"
@@ -12,6 +14,7 @@
 #include "bisect.h"
 #include "progress.h"
 #include "reflog-walk.h"
+#include "oidset.h"
 
 static const char rev_list_usage[] =
 "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -55,6 +58,20 @@ static const char rev_list_usage[] =
 static struct progress *progress;
 static unsigned progress_counter;
 
+static struct list_objects_filter_options filter_options;
+static struct oidset omitted_objects;
+static int arg_print_omitted; /* print objects omitted by filter */
+
+static struct oidset missing_objects;
+enum missing_action {
+       MA_ERROR = 0,    /* fail if any missing objects are encountered */
+       MA_ALLOW_ANY,    /* silently allow ALL missing objects */
+       MA_PRINT,        /* print ALL missing objects in special section */
+};
+static enum missing_action arg_missing_action;
+
+#define DEFAULT_OIDSET_SIZE     (16*1024)
+
 static void finish_commit(struct commit *commit, void *data);
 static void show_commit(struct commit *commit, void *data)
 {
@@ -178,11 +195,31 @@ static void finish_commit(struct commit *commit, void *data)
        free_commit_buffer(commit);
 }
 
+static inline void finish_object__ma(struct object *obj)
+{
+       switch (arg_missing_action) {
+       case MA_ERROR:
+               die("missing blob object '%s'", oid_to_hex(&obj->oid));
+               return;
+
+       case MA_ALLOW_ANY:
+               return;
+
+       case MA_PRINT:
+               oidset_insert(&missing_objects, &obj->oid);
+               return;
+
+       default:
+               BUG("unhandled missing_action");
+               return;
+       }
+}
+
 static void finish_object(struct object *obj, const char *name, void *cb_data)
 {
        struct rev_list_info *info = cb_data;
        if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid))
-               die("missing blob object '%s'", oid_to_hex(&obj->oid));
+               finish_object__ma(obj);
        if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
                parse_object(&obj->oid);
 }
@@ -269,6 +306,26 @@ static int show_object_fast(
        return 1;
 }
 
+static inline int parse_missing_action_value(const char *value)
+{
+       if (!strcmp(value, "error")) {
+               arg_missing_action = MA_ERROR;
+               return 1;
+       }
+
+       if (!strcmp(value, "allow-any")) {
+               arg_missing_action = MA_ALLOW_ANY;
+               return 1;
+       }
+
+       if (!strcmp(value, "print")) {
+               arg_missing_action = MA_PRINT;
+               return 1;
+       }
+
+       return 0;
+}
+
 int cmd_rev_list(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -335,6 +392,30 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        show_progress = arg;
                        continue;
                }
+
+               if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) {
+                       parse_list_objects_filter(&filter_options, arg);
+                       if (filter_options.choice && !revs.blob_objects)
+                               die(_("object filtering requires --objects"));
+                       if (filter_options.choice == LOFC_SPARSE_OID &&
+                           !filter_options.sparse_oid_value)
+                               die(_("invalid sparse value '%s'"),
+                                   filter_options.filter_spec);
+                       continue;
+               }
+               if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
+                       list_objects_filter_release(&filter_options);
+                       continue;
+               }
+               if (!strcmp(arg, "--filter-print-omitted")) {
+                       arg_print_omitted = 1;
+                       continue;
+               }
+
+               if (skip_prefix(arg, "--missing=", &arg) &&
+                   parse_missing_action_value(arg))
+                       continue;
+
                usage(rev_list_usage);
 
        }
@@ -360,6 +441,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.show_notes)
                die(_("rev-list does not support display of notes"));
 
+       if (filter_options.choice && use_bitmap_index)
+               die(_("cannot combine --use-bitmap-index with object filtering"));
+
        save_commit_buffer = (revs.verbose_header ||
                              revs.grep_filter.pattern_list ||
                              revs.grep_filter.header_list);
@@ -403,7 +487,31 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        return show_bisect_vars(&info, reaches, all);
        }
 
-       traverse_commit_list(&revs, show_commit, show_object, &info);
+       if (arg_print_omitted)
+               oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
+       if (arg_missing_action == MA_PRINT)
+               oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE);
+
+       traverse_commit_list_filtered(
+               &filter_options, &revs, show_commit, show_object, &info,
+               (arg_print_omitted ? &omitted_objects : NULL));
+
+       if (arg_print_omitted) {
+               struct oidset_iter iter;
+               struct object_id *oid;
+               oidset_iter_init(&omitted_objects, &iter);
+               while ((oid = oidset_iter_next(&iter)))
+                       printf("~%s\n", oid_to_hex(oid));
+               oidset_clear(&omitted_objects);
+       }
+       if (arg_missing_action == MA_PRINT) {
+               struct oidset_iter iter;
+               struct object_id *oid;
+               oidset_iter_init(&missing_objects, &iter);
+               while ((oid = oidset_iter_next(&iter)))
+                       printf("?%s\n", oid_to_hex(oid));
+               oidset_clear(&missing_objects);
+       }
 
        stop_progress(&progress);
 
index d91451f..4a2fcca 100644 (file)
@@ -382,7 +382,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                }
                strbuf_release(&buf);
                if (gitmodules_modified)
-                       stage_updated_gitmodules();
+                       stage_updated_gitmodules(&the_index);
        }
 
        if (active_cache_changed) {
index 2e24b5c..e8a4aa4 100644 (file)
@@ -1,6 +1,6 @@
 #include "cache.h"
 #include "config.h"
-#include "commit.h"
+#include "pretty.h"
 #include "refs.h"
 #include "builtin.h"
 #include "color.h"
index ed043d5..7cef5b1 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "checkout.h"
 #include "config.h"
 #include "builtin.h"
 #include "dir.h"
@@ -32,8 +33,19 @@ struct add_opts {
 
 static int show_only;
 static int verbose;
+static int guess_remote;
 static timestamp_t expire;
 
+static int git_worktree_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "worktree.guessremote")) {
+               guess_remote = git_config_bool(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value, cb);
+}
+
 static int prune_worktree(const char *id, struct strbuf *reason)
 {
        struct stat st;
@@ -218,20 +230,21 @@ static int add_worktree(const char *path, const char *refname,
        int counter = 0, len, ret;
        struct strbuf symref = STRBUF_INIT;
        struct commit *commit = NULL;
+       int is_branch = 0;
 
        if (file_exists(path) && !is_empty_dir(path))
                die(_("'%s' already exists"), path);
 
        /* is 'refname' a branch or commit? */
        if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
-                ref_exists(symref.buf)) { /* it's a branch */
+           ref_exists(symref.buf)) {
+               is_branch = 1;
                if (!opts->force)
                        die_if_checked_out(symref.buf, 0);
-       } else { /* must be a commit */
-               commit = lookup_commit_reference_by_name(refname);
-               if (!commit)
-                       die(_("invalid reference: %s"), refname);
        }
+       commit = lookup_commit_reference_by_name(refname);
+       if (!commit)
+               die(_("invalid reference: %s"), refname);
 
        name = worktree_basename(path, &len);
        git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name);
@@ -296,7 +309,7 @@ static int add_worktree(const char *path, const char *refname,
        argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
        cp.git_cmd = 1;
 
-       if (commit)
+       if (!is_branch)
                argv_array_pushl(&cp.args, "update-ref", "HEAD",
                                 oid_to_hex(&commit->object.oid), NULL);
        else
@@ -327,6 +340,15 @@ done:
                strbuf_addf(&sb, "%s/locked", sb_repo.buf);
                unlink_or_warn(sb.buf);
        }
+
+       /*
+        * Hook failure does not warrant worktree deletion, so run hook after
+        * is_junk is cleared, but do return appropriate code when hook fails.
+        */
+       if (!ret && opts->checkout)
+               ret = run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid),
+                                 oid_to_hex(&commit->object.oid), "1", NULL);
+
        argv_array_clear(&child_env);
        strbuf_release(&sb);
        strbuf_release(&symref);
@@ -341,6 +363,7 @@ static int add(int ac, const char **av, const char *prefix)
        const char *new_branch_force = NULL;
        char *path;
        const char *branch;
+       const char *opt_track = NULL;
        struct option options[] = {
                OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
                OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -350,6 +373,11 @@ static int add(int ac, const char **av, const char *prefix)
                OPT_BOOL(0, "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_PASSTHRU(0, "track", &opt_track, NULL,
+                            N_("set up tracking mode (see git-branch(1))"),
+                            PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
+               OPT_BOOL(0, "guess-remote", &guess_remote,
+                        N_("try to match the new branch name with a remote-tracking branch")),
                OPT_END()
        };
 
@@ -384,6 +412,28 @@ static int add(int ac, const char **av, const char *prefix)
                int n;
                const char *s = worktree_basename(path, &n);
                opts.new_branch = xstrndup(s, n);
+               if (guess_remote) {
+                       struct object_id oid;
+                       const char *remote =
+                               unique_tracking_name(opts.new_branch, &oid);
+                       if (remote)
+                               branch = remote;
+               }
+       }
+
+       if (ac == 2 && !opts.new_branch && !opts.detach) {
+               struct object_id oid;
+               struct commit *commit;
+               const char *remote;
+
+               commit = lookup_commit_reference_by_name(branch);
+               if (!commit) {
+                       remote = unique_tracking_name(branch, &oid);
+                       if (remote) {
+                               opts.new_branch = branch;
+                               branch = remote;
+                       }
+               }
        }
 
        if (opts.new_branch) {
@@ -394,9 +444,13 @@ static int add(int ac, const char **av, const char *prefix)
                        argv_array_push(&cp.args, "--force");
                argv_array_push(&cp.args, opts.new_branch);
                argv_array_push(&cp.args, branch);
+               if (opt_track)
+                       argv_array_push(&cp.args, opt_track);
                if (run_command(&cp))
                        return -1;
                branch = opts.new_branch;
+       } else if (opt_track) {
+               die(_("--[no-]track can only be used if a new branch is created"));
        }
 
        UNLEAK(path);
@@ -557,7 +611,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_default_config, NULL);
+       git_config(git_worktree_config, NULL);
 
        if (ac < 2)
                usage_with_options(worktree_usage, options);
diff --git a/cache.h b/cache.h
index d3e2402..a2ec8c0 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1491,6 +1491,7 @@ extern const char *ident_default_name(void);
 extern const char *ident_default_email(void);
 extern const char *git_editor(void);
 extern const char *git_pager(int stdout_is_tty);
+extern int is_terminal_dumb(void);
 extern int git_ident_config(const char *, const char *, void *);
 extern void reset_ident_date(void);
 
@@ -1972,4 +1973,10 @@ void sleep_millisec(int millisec);
  */
 void safe_create_dir(const char *dir, int share);
 
+/*
+ * Should we print an ellipsis after an abbreviated SHA-1 value
+ * when doing diff-raw output or indicating a detached HEAD?
+ */
+extern int print_sha1_ellipsis(void);
+
 #endif /* CACHE_H */
diff --git a/checkout.c b/checkout.c
new file mode 100644 (file)
index 0000000..ac42630
--- /dev/null
@@ -0,0 +1,43 @@
+#include "cache.h"
+#include "remote.h"
+#include "checkout.h"
+
+struct tracking_name_data {
+       /* const */ char *src_ref;
+       char *dst_ref;
+       struct object_id *dst_oid;
+       int unique;
+};
+
+static int check_tracking_name(struct remote *remote, void *cb_data)
+{
+       struct tracking_name_data *cb = cb_data;
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.src = cb->src_ref;
+       if (remote_find_tracking(remote, &query) ||
+           get_oid(query.dst, cb->dst_oid)) {
+               free(query.dst);
+               return 0;
+       }
+       if (cb->dst_ref) {
+               free(query.dst);
+               cb->unique = 0;
+               return 0;
+       }
+       cb->dst_ref = query.dst;
+       return 0;
+}
+
+const char *unique_tracking_name(const char *name, struct object_id *oid)
+{
+       struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+       cb_data.src_ref = xstrfmt("refs/heads/%s", name);
+       cb_data.dst_oid = oid;
+       for_each_remote(check_tracking_name, &cb_data);
+       free(cb_data.src_ref);
+       if (cb_data.unique)
+               return cb_data.dst_ref;
+       free(cb_data.dst_ref);
+       return NULL;
+}
diff --git a/checkout.h b/checkout.h
new file mode 100644 (file)
index 0000000..9980711
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef CHECKOUT_H
+#define CHECKOUT_H
+
+#include "cache.h"
+
+/*
+ * Check if the branch name uniquely matches a branch name on a remote
+ * tracking branch.  Return the name of the remote if such a branch
+ * exists, NULL otherwise.
+ */
+extern const char *unique_tracking_name(const char *name, struct object_id *oid);
+
+#endif /* CHECKOUT_H */
index 5bd06fe..75a9fd2 100755 (executable)
@@ -8,10 +8,8 @@
 P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION
 LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION
 
-case "${TRAVIS_OS_NAME:-linux}" in
-linux)
-       export GIT_TEST_HTTPD=YesPlease
-
+case "$jobname" in
+linux-clang|linux-gcc)
        mkdir --parents "$P4_PATH"
        pushd "$P4_PATH"
                wget --quiet "$P4WHENCE/bin.linux26x86_64/p4d"
@@ -26,7 +24,7 @@ linux)
                cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs .
        popd
        ;;
-osx)
+osx-clang|osx-gcc)
        brew update --quiet
        # Uncomment this if you want to run perf tests:
        # brew install gnu-time
index ac05f1f..331d3eb 100755 (executable)
@@ -23,14 +23,42 @@ skip_branch_tip_with_tag () {
 
 # Set 'exit on error' for all CI scripts to let the caller know that
 # something went wrong
-set -e
+set -ex
 
 skip_branch_tip_with_tag
 
-case "${TRAVIS_OS_NAME:-linux}" in
-linux)
+if test -z "$jobname"
+then
+       jobname="$TRAVIS_OS_NAME-$CC"
+fi
+
+export DEVELOPER=1
+export DEFAULT_TEST_TARGET=prove
+export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
+export GIT_TEST_OPTS="--verbose-log"
+export GIT_TEST_CLONE_2GB=YesPlease
+
+case "$jobname" in
+linux-clang|linux-gcc)
+       export GIT_TEST_HTTPD=YesPlease
+
+       # The Linux build installs the defined dependency versions below.
+       # The OS X build installs the latest available versions. Keep that
+       # in mind when you encounter a broken OS X build!
+       export LINUX_P4_VERSION="16.2"
+       export LINUX_GIT_LFS_VERSION="1.5.2"
+
        P4_PATH="$(pwd)/custom/p4"
        GIT_LFS_PATH="$(pwd)/custom/git-lfs"
        export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH"
        ;;
+osx-clang|osx-gcc)
+       # t9810 occasionally fails on Travis CI OS X
+       # t9816 occasionally fails with "TAP out of sequence errors" on
+       # Travis CI OS X
+       export GIT_SKIP_TESTS="t9810 t9816"
+       ;;
+GETTEXT_POISON)
+       export GETTEXT_POISON=YesPlease
+       ;;
 esac
diff --git a/color.c b/color.c
index 9a9261a..d48dd94 100644 (file)
--- a/color.c
+++ b/color.c
@@ -329,8 +329,7 @@ static int check_auto_color(void)
        if (color_stdout_is_tty < 0)
                color_stdout_is_tty = isatty(1);
        if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) {
-               char *term = getenv("TERM");
-               if (term && strcmp(term, "dumb"))
+               if (!is_terminal_dumb())
                        return 1;
        }
        return 0;
index 99a3fea..8c68ca1 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -7,6 +7,7 @@
 #include "decorate.h"
 #include "gpg-interface.h"
 #include "string-list.h"
+#include "pretty.h"
 
 struct commit_list {
        struct commit *item;
@@ -121,93 +122,13 @@ struct commit_list *copy_commit_list(struct commit_list *list);
 
 void free_commit_list(struct commit_list *list);
 
-/* Commit formats */
-enum cmit_fmt {
-       CMIT_FMT_RAW,
-       CMIT_FMT_MEDIUM,
-       CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
-       CMIT_FMT_SHORT,
-       CMIT_FMT_FULL,
-       CMIT_FMT_FULLER,
-       CMIT_FMT_ONELINE,
-       CMIT_FMT_EMAIL,
-       CMIT_FMT_MBOXRD,
-       CMIT_FMT_USERFORMAT,
-
-       CMIT_FMT_UNSPECIFIED
-};
-
-static inline int cmit_fmt_is_mail(enum cmit_fmt fmt)
-{
-       return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD);
-}
-
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 
-struct pretty_print_context {
-       /*
-        * Callers should tweak these to change the behavior of pp_* functions.
-        */
-       enum cmit_fmt fmt;
-       int abbrev;
-       const char *after_subject;
-       int preserve_subject;
-       struct date_mode date_mode;
-       unsigned date_mode_explicit:1;
-       int print_email_subject;
-       int expand_tabs_in_log;
-       int need_8bit_cte;
-       char *notes_message;
-       struct reflog_walk_info *reflog_info;
-       struct rev_info *rev;
-       const char *output_encoding;
-       struct string_list *mailmap;
-       int color;
-       struct ident_split *from_ident;
-
-       /*
-        * Fields below here are manipulated internally by pp_* functions and
-        * should not be counted on by callers.
-        */
-       struct string_list in_body_headers;
-       int graph_width;
-};
-
-struct userformat_want {
-       unsigned notes:1;
-};
-
 extern int has_non_ascii(const char *text);
 extern const char *logmsg_reencode(const struct commit *commit,
                                   char **commit_encoding,
                                   const char *output_encoding);
-extern void get_commit_format(const char *arg, struct rev_info *);
-extern const char *format_subject(struct strbuf *sb, const char *msg,
-                                 const char *line_separator);
-extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);
-extern int commit_format_is_empty(enum cmit_fmt);
 extern const char *skip_blank_lines(const char *msg);
-extern void format_commit_message(const struct commit *commit,
-                                 const char *format, struct strbuf *sb,
-                                 const struct pretty_print_context *context);
-extern void pretty_print_commit(struct pretty_print_context *pp,
-                               const struct commit *commit,
-                               struct strbuf *sb);
-extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
-                          struct strbuf *sb);
-void pp_user_info(struct pretty_print_context *pp,
-                 const char *what, struct strbuf *sb,
-                 const char *line, const char *encoding);
-void pp_title_line(struct pretty_print_context *pp,
-                  const char **msg_p,
-                  struct strbuf *sb,
-                  const char *encoding,
-                  int need_8bit_cte);
-void pp_remainder(struct pretty_print_context *pp,
-                 const char **msg_p,
-                 struct strbuf *sb,
-                 int indent);
-
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
index c6cbef3..983e419 100644 (file)
@@ -278,11 +278,12 @@ __git_ps1_colorize_gitstring ()
        r="$c_clear$r"
 }
 
+# Helper function to read the first line of a file into a variable.
+# __git_eread requires 2 arguments, the file path and the name of the
+# variable, in that order.
 __git_eread ()
 {
-       local f="$1"
-       shift
-       test -r "$f" && read "$@" <"$f"
+       test -r "$1" && IFS=$'\r\n' read "$2" <"$1"
 }
 
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
index 20d7ab6..1a41a48 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -220,18 +220,27 @@ static void check_safe_crlf(const char *path, enum crlf_action crlf_action,
        }
 }
 
-static int has_cr_in_index(const struct index_state *istate, const char *path)
+static int has_crlf_in_index(const struct index_state *istate, const char *path)
 {
        unsigned long sz;
        void *data;
-       int has_cr;
+       const char *crp;
+       int has_crlf = 0;
 
        data = read_blob_data_from_index(istate, path, &sz);
        if (!data)
                return 0;
-       has_cr = memchr(data, '\r', sz) != NULL;
+
+       crp = memchr(data, '\r', sz);
+       if (crp) {
+               unsigned int ret_stats;
+               ret_stats = gather_convert_stats(data, sz);
+               if (!(ret_stats & CONVERT_STAT_BITS_BIN) &&
+                   (ret_stats & CONVERT_STAT_BITS_TXT_CRLF))
+                       has_crlf = 1;
+       }
        free(data);
-       return has_cr;
+       return has_crlf;
 }
 
 static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats,
@@ -290,7 +299,7 @@ static int crlf_to_git(const struct index_state *istate,
                 * cherry-pick.
                 */
                if ((checksafe != SAFE_CRLF_RENORMALIZE) &&
-                   has_cr_in_index(istate, path))
+                   has_crlf_in_index(istate, path))
                        convert_crlf_into_lf = 0;
        }
        if ((checksafe == SAFE_CRLF_WARN ||
index 270eb25..de31331 100644 (file)
@@ -14,20 +14,20 @@ static unsigned int hash_obj(const struct object *obj, unsigned int n)
 static void *insert_decoration(struct decoration *n, const struct object *base, void *decoration)
 {
        int size = n->size;
-       struct object_decoration *hash = n->hash;
+       struct decoration_entry *entries = n->entries;
        unsigned int j = hash_obj(base, size);
 
-       while (hash[j].base) {
-               if (hash[j].base == base) {
-                       void *old = hash[j].decoration;
-                       hash[j].decoration = decoration;
+       while (entries[j].base) {
+               if (entries[j].base == base) {
+                       void *old = entries[j].decoration;
+                       entries[j].decoration = decoration;
                        return old;
                }
                if (++j >= size)
                        j = 0;
        }
-       hash[j].base = base;
-       hash[j].decoration = decoration;
+       entries[j].base = base;
+       entries[j].decoration = decoration;
        n->nr++;
        return NULL;
 }
@@ -36,24 +36,23 @@ static void grow_decoration(struct decoration *n)
 {
        int i;
        int old_size = n->size;
-       struct object_decoration *old_hash = n->hash;
+       struct decoration_entry *old_entries = n->entries;
 
        n->size = (old_size + 1000) * 3 / 2;
-       n->hash = xcalloc(n->size, sizeof(struct object_decoration));
+       n->entries = xcalloc(n->size, sizeof(struct decoration_entry));
        n->nr = 0;
 
        for (i = 0; i < old_size; i++) {
-               const struct object *base = old_hash[i].base;
-               void *decoration = old_hash[i].decoration;
+               const struct object *base = old_entries[i].base;
+               void *decoration = old_entries[i].decoration;
 
                if (!decoration)
                        continue;
                insert_decoration(n, base, decoration);
        }
-       free(old_hash);
+       free(old_entries);
 }
 
-/* Add a decoration pointer, return any old one */
 void *add_decoration(struct decoration *n, const struct object *obj,
                void *decoration)
 {
@@ -64,7 +63,6 @@ void *add_decoration(struct decoration *n, const struct object *obj,
        return insert_decoration(n, obj, decoration);
 }
 
-/* Lookup a decoration pointer */
 void *lookup_decoration(struct decoration *n, const struct object *obj)
 {
        unsigned int j;
@@ -74,7 +72,7 @@ void *lookup_decoration(struct decoration *n, const struct object *obj)
                return NULL;
        j = hash_obj(obj, n->size);
        for (;;) {
-               struct object_decoration *ref = n->hash + j;
+               struct decoration_entry *ref = n->entries + j;
                if (ref->base == obj)
                        return ref->decoration;
                if (!ref->base)
index e732804..9014c1e 100644 (file)
@@ -1,18 +1,61 @@
 #ifndef DECORATE_H
 #define DECORATE_H
 
-struct object_decoration {
+/*
+ * A data structure that associates Git objects to void pointers. See
+ * t/helper/test-example-decorate.c for a demonstration of how to use these
+ * functions.
+ */
+
+/*
+ * An entry in the data structure.
+ */
+struct decoration_entry {
        const struct object *base;
        void *decoration;
 };
 
+/*
+ * The data structure.
+ *
+ * This data structure must be zero-initialized.
+ */
 struct decoration {
+       /*
+        * Not used by the decoration mechanism. Clients may use this for
+        * whatever they want.
+        */
        const char *name;
-       unsigned int size, nr;
-       struct object_decoration *hash;
+
+       /*
+        * The capacity of "entries".
+        */
+       unsigned int size;
+
+       /*
+        * The number of real Git objects (that is, entries with non-NULL
+        * "base").
+        */
+       unsigned int nr;
+
+       /*
+        * The entries. This is an array of size "size", containing nr entries
+        * with non-NULL "base" and (size - nr) entries with NULL "base".
+        */
+       struct decoration_entry *entries;
 };
 
+/*
+ * Add an association from the given object to the given pointer (which may be
+ * NULL), returning the previously associated pointer. If there is no previous
+ * association, this function returns NULL.
+ */
 extern void *add_decoration(struct decoration *n, const struct object *obj, void *decoration);
+
+/*
+ * Return the pointer associated to the given object. If there is no
+ * association, this function returns NULL.
+ */
 extern void *lookup_decoration(struct decoration *n, const struct object *obj);
 
 #endif
diff --git a/diff.c b/diff.c
index 2ebe222..fb22b19 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3210,6 +3210,8 @@ static void builtin_diff(const char *name_a,
                ecbdata.opt = o;
                ecbdata.header = header.len ? &header : NULL;
                xpp.flags = o->xdl_opts;
+               xpp.anchors = o->anchors;
+               xpp.anchors_nr = o->anchors_nr;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -3302,6 +3304,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
+               xpp.anchors = o->anchors;
+               xpp.anchors_nr = o->anchors_nr;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
@@ -4508,17 +4512,12 @@ int diff_opt_parse(struct diff_options *options,
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        else if (!strcmp(arg, "--shortstat"))
                options->output_format |= DIFF_FORMAT_SHORTSTAT;
-       else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
-               return parse_dirstat_opt(options, "");
-       else if (skip_prefix(arg, "-X", &arg))
-               return parse_dirstat_opt(options, arg);
-       else if (skip_prefix(arg, "--dirstat=", &arg))
+       else if (skip_prefix(arg, "-X", &arg) ||
+                skip_to_optional_arg(arg, "--dirstat", &arg))
                return parse_dirstat_opt(options, arg);
        else if (!strcmp(arg, "--cumulative"))
                return parse_dirstat_opt(options, "cumulative");
-       else if (!strcmp(arg, "--dirstat-by-file"))
-               return parse_dirstat_opt(options, "files");
-       else if (skip_prefix(arg, "--dirstat-by-file=", &arg)) {
+       else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) {
                parse_dirstat_opt(options, "files");
                return parse_dirstat_opt(options, arg);
        }
@@ -4540,13 +4539,13 @@ int diff_opt_parse(struct diff_options *options,
                return stat_opt(options, av);
 
        /* renames options */
-       else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") ||
-                !strcmp(arg, "--break-rewrites")) {
+       else if (starts_with(arg, "-B") ||
+                skip_to_optional_arg(arg, "--break-rewrites", NULL)) {
                if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -B: %s", arg+2);
        }
-       else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") ||
-                !strcmp(arg, "--find-renames")) {
+       else if (starts_with(arg, "-M") ||
+                skip_to_optional_arg(arg, "--find-renames", NULL)) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -M: %s", arg+2);
                options->detect_rename = DIFF_DETECT_RENAME;
@@ -4554,8 +4553,8 @@ int diff_opt_parse(struct diff_options *options,
        else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
                options->irreversible_delete = 1;
        }
-       else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") ||
-                !strcmp(arg, "--find-copies")) {
+       else if (starts_with(arg, "-C") ||
+                skip_to_optional_arg(arg, "--find-copies", NULL)) {
                if (options->detect_rename == DIFF_DETECT_COPY)
                        options->flags.find_copies_harder = 1;
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
@@ -4568,11 +4567,10 @@ int diff_opt_parse(struct diff_options *options,
                options->flags.rename_empty = 1;
        else if (!strcmp(arg, "--no-rename-empty"))
                options->flags.rename_empty = 0;
-       else if (!strcmp(arg, "--relative"))
-               options->flags.relative_name = 1;
-       else if (skip_prefix(arg, "--relative=", &arg)) {
+       else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) {
                options->flags.relative_name = 1;
-               options->prefix = arg;
+               if (arg)
+                       options->prefix = arg;
        }
 
        /* xdiff options */
@@ -4594,9 +4592,18 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_XDL_SET(options, INDENT_HEURISTIC);
        else if (!strcmp(arg, "--no-indent-heuristic"))
                DIFF_XDL_CLR(options, INDENT_HEURISTIC);
-       else if (!strcmp(arg, "--patience"))
+       else if (!strcmp(arg, "--patience")) {
+               int i;
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
-       else if (!strcmp(arg, "--histogram"))
+               /*
+                * Both --patience and --anchored use PATIENCE_DIFF
+                * internally, so remove any anchors previously
+                * specified.
+                */
+               for (i = 0; i < options->anchors_nr; i++)
+                       free(options->anchors[i]);
+               options->anchors_nr = 0;
+       } else if (!strcmp(arg, "--histogram"))
                options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
        else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) {
                long value = parse_algorithm_value(optarg);
@@ -4608,6 +4615,11 @@ int diff_opt_parse(struct diff_options *options,
                options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
                options->xdl_opts |= value;
                return argcount;
+       } else if (skip_prefix(arg, "--anchored=", &arg)) {
+               options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
+               ALLOC_GROW(options->anchors, options->anchors_nr + 1,
+                          options->anchors_alloc);
+               options->anchors[options->anchors_nr++] = xstrdup(arg);
        }
 
        /* flags options */
@@ -4628,9 +4640,7 @@ int diff_opt_parse(struct diff_options *options,
        else if (!strcmp(arg, "--no-follow")) {
                options->flags.follow_renames = 0;
                options->flags.default_follow_renames = 0;
-       } else if (!strcmp(arg, "--color"))
-               options->use_color = 1;
-       else if (skip_prefix(arg, "--color=", &arg)) {
+       } else if (skip_to_optional_arg_default(arg, "--color", &arg, "always")) {
                int value = git_config_colorbool(NULL, arg);
                if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
@@ -4650,15 +4660,10 @@ int diff_opt_parse(struct diff_options *options,
                if (cm < 0)
                        die("bad --color-moved argument: %s", arg);
                options->color_moved = cm;
-       } else if (!strcmp(arg, "--color-words")) {
+       } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) {
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
-       else if (skip_prefix(arg, "--color-words=", &arg)) {
-               options->use_color = 1;
-               options->word_diff = DIFF_WORDS_COLOR;
-               options->word_regex = arg;
-       }
        else if (!strcmp(arg, "--word-diff")) {
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
@@ -4696,15 +4701,10 @@ int diff_opt_parse(struct diff_options *options,
                options->flags.textconv_set_via_cmdline = 1;
        } else if (!strcmp(arg, "--no-textconv"))
                options->flags.allow_textconv = 0;
-       else if (!strcmp(arg, "--ignore-submodules")) {
-               options->flags.override_submodule_config = 1;
-               handle_ignore_submodules_arg(options, "all");
-       } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) {
+       else if (skip_to_optional_arg_default(arg, "--ignore-submodules", &arg, "all")) {
                options->flags.override_submodule_config = 1;
                handle_ignore_submodules_arg(options, arg);
-       } else if (!strcmp(arg, "--submodule"))
-               options->submodule_format = DIFF_SUBMODULE_LOG;
-       else if (skip_prefix(arg, "--submodule=", &arg))
+       } else if (skip_to_optional_arg_default(arg, "--submodule", &arg, "log"))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
                return parse_ws_error_highlight_opt(options, arg);
@@ -4902,14 +4902,20 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len)
        int abblen;
        const char *abbrev;
 
+       /* Do we want all 40 hex characters? */
        if (len == GIT_SHA1_HEXSZ)
                return oid_to_hex(oid);
 
+       /* An abbreviated value is fine, possibly followed by an ellipsis. */
        abbrev = diff_abbrev_oid(oid, len);
+
+       if (!print_sha1_ellipsis())
+               return abbrev;
+
        abblen = strlen(abbrev);
 
        /*
-        * In well-behaved cases, where the abbbreviated result is the
+        * In well-behaved cases, where the abbreviated result is the
         * same as the requested length, append three dots after the
         * abbreviation (hence the whole logic is limited to the case
         * where abblen < 37); when the actual abbreviated result is a
@@ -5454,7 +5460,7 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
                warning(_(rename_limit_warning));
        else
                return;
-       if (0 < needed && needed < 32767)
+       if (0 < needed)
                warning(_(rename_limit_advice), varname, needed);
 }
 
diff --git a/diff.h b/diff.h
index 0fb18dd..7cf276f 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -166,6 +166,10 @@ struct diff_options {
        const char *stat_sep;
        long xdl_opts;
 
+       /* see Documentation/diff-options.txt */
+       char **anchors;
+       size_t anchors_nr, anchors_alloc;
+
        int stat_width;
        int stat_name_width;
        int stat_graph_width;
index 12dc2a0..245e999 100644 (file)
@@ -391,14 +391,12 @@ static int too_many_rename_candidates(int num_create,
         * growing larger than a "rename_limit" square matrix, ie:
         *
         *    num_create * num_src > rename_limit * rename_limit
-        *
-        * but handles the potential overflow case specially (and we
-        * assume at least 32-bit integers)
         */
-       if (rename_limit <= 0 || rename_limit > 32767)
+       if (rename_limit <= 0)
                rename_limit = 32767;
        if ((num_create <= rename_limit || num_src <= rename_limit) &&
-           (num_create * num_src <= rename_limit * rename_limit))
+           ((uint64_t)num_create * (uint64_t)num_src
+            <= (uint64_t)rename_limit * (uint64_t)rename_limit))
                return 0;
 
        options->needed_rename_limit =
@@ -415,7 +413,8 @@ static int too_many_rename_candidates(int num_create,
                num_src++;
        }
        if ((num_create <= rename_limit || num_src <= rename_limit) &&
-           (num_create * num_src <= rename_limit * rename_limit))
+           ((uint64_t)num_create * (uint64_t)num_src
+            <= (uint64_t)rename_limit * (uint64_t)rename_limit))
                return 2;
        return 1;
 }
@@ -534,7 +533,7 @@ void diffcore_rename(struct diff_options *options)
        if (options->show_rename_progress) {
                progress = start_delayed_progress(
                                _("Performing inexact rename detection"),
-                               rename_dst_nr * rename_src_nr);
+                               (uint64_t)rename_dst_nr * (uint64_t)rename_src_nr);
        }
 
        mx = xcalloc(st_mult(NUM_CANDIDATE_PER_DST, num_create), sizeof(*mx));
@@ -571,7 +570,7 @@ void diffcore_rename(struct diff_options *options)
                        diff_free_filespec_blob(two);
                }
                dst_cnt++;
-               display_progress(progress, (i+1)*rename_src_nr);
+               display_progress(progress, (uint64_t)(i+1)*(uint64_t)rename_src_nr);
        }
        stop_progress(&progress);
 
diff --git a/dir.c b/dir.c
index 3c54366..7c4b45e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -221,6 +221,57 @@ int within_depth(const char *name, int namelen,
        return 1;
 }
 
+/*
+ * Read the contents of the blob with the given OID into a buffer.
+ * Append a trailing LF to the end if the last line doesn't have one.
+ *
+ * Returns:
+ *    -1 when the OID is invalid or unknown or does not refer to a blob.
+ *     0 when the blob is empty.
+ *     1 along with { data, size } of the (possibly augmented) buffer
+ *       when successful.
+ *
+ * Optionally updates the given sha1_stat with the given OID (when valid).
+ */
+static int do_read_blob(const struct object_id *oid,
+                       struct sha1_stat *sha1_stat,
+                       size_t *size_out,
+                       char **data_out)
+{
+       enum object_type type;
+       unsigned long sz;
+       char *data;
+
+       *size_out = 0;
+       *data_out = NULL;
+
+       data = read_sha1_file(oid->hash, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return -1;
+       }
+
+       if (sha1_stat) {
+               memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
+               hashcpy(sha1_stat->sha1, oid->hash);
+       }
+
+       if (sz == 0) {
+               free(data);
+               return 0;
+       }
+
+       if (data[sz - 1] != '\n') {
+               data = xrealloc(data, st_add(sz, 1));
+               data[sz++] = '\n';
+       }
+
+       *size_out = xsize_t(sz);
+       *data_out = data;
+
+       return 1;
+}
+
 #define DO_MATCH_EXCLUDE   (1<<0)
 #define DO_MATCH_DIRECTORY (1<<1)
 #define DO_MATCH_SUBMODULE (1<<2)
@@ -601,32 +652,22 @@ void add_exclude(const char *string, const char *base,
        x->el = el;
 }
 
-static void *read_skip_worktree_file_from_index(const struct index_state *istate,
-                                               const char *path, size_t *size,
-                                               struct sha1_stat *sha1_stat)
+static int read_skip_worktree_file_from_index(const struct index_state *istate,
+                                             const char *path,
+                                             size_t *size_out,
+                                             char **data_out,
+                                             struct sha1_stat *sha1_stat)
 {
        int pos, len;
-       unsigned long sz;
-       enum object_type type;
-       void *data;
 
        len = strlen(path);
        pos = index_name_pos(istate, path, len);
        if (pos < 0)
-               return NULL;
+               return -1;
        if (!ce_skip_worktree(istate->cache[pos]))
-               return NULL;
-       data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz);
-       if (!data || type != OBJ_BLOB) {
-               free(data);
-               return NULL;
-       }
-       *size = xsize_t(sz);
-       if (sha1_stat) {
-               memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
-               hashcpy(sha1_stat->sha1, istate->cache[pos]->oid.hash);
-       }
-       return data;
+               return -1;
+
+       return do_read_blob(&istate->cache[pos]->oid, sha1_stat, size_out, data_out);
 }
 
 /*
@@ -740,6 +781,10 @@ static void invalidate_directory(struct untracked_cache *uc,
                dir->dirs[i]->recurse = 0;
 }
 
+static int add_excludes_from_buffer(char *buf, size_t size,
+                                   const char *base, int baselen,
+                                   struct exclude_list *el);
+
 /*
  * Given a file with name "fname", read it (either from disk, or from
  * an index if 'istate' is non-null), parse it and store the
@@ -755,9 +800,10 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                        struct sha1_stat *sha1_stat)
 {
        struct stat st;
-       int fd, i, lineno = 1;
+       int r;
+       int fd;
        size_t size = 0;
-       char *buf, *entry;
+       char *buf;
 
        fd = open(fname, O_RDONLY);
        if (fd < 0 || fstat(fd, &st) < 0) {
@@ -765,17 +811,13 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                        warn_on_fopen_errors(fname);
                else
                        close(fd);
-               if (!istate ||
-                   (buf = read_skip_worktree_file_from_index(istate, fname, &size, sha1_stat)) == NULL)
+               if (!istate)
                        return -1;
-               if (size == 0) {
-                       free(buf);
-                       return 0;
-               }
-               if (buf[size-1] != '\n') {
-                       buf = xrealloc(buf, st_add(size, 1));
-                       buf[size++] = '\n';
-               }
+               r = read_skip_worktree_file_from_index(istate, fname,
+                                                      &size, &buf,
+                                                      sha1_stat);
+               if (r != 1)
+                       return r;
        } else {
                size = xsize_t(st.st_size);
                if (size == 0) {
@@ -814,6 +856,17 @@ static int add_excludes(const char *fname, const char *base, int baselen,
                }
        }
 
+       add_excludes_from_buffer(buf, size, base, baselen, el);
+       return 0;
+}
+
+static int add_excludes_from_buffer(char *buf, size_t size,
+                                   const char *base, int baselen,
+                                   struct exclude_list *el)
+{
+       int i, lineno = 1;
+       char *entry;
+
        el->filebuf = buf;
 
        if (skip_utf8_bom(&buf, size))
@@ -842,6 +895,23 @@ int add_excludes_from_file_to_list(const char *fname, const char *base,
        return add_excludes(fname, base, baselen, el, istate, NULL);
 }
 
+int add_excludes_from_blob_to_list(
+       struct object_id *oid,
+       const char *base, int baselen,
+       struct exclude_list *el)
+{
+       char *buf;
+       size_t size;
+       int r;
+
+       r = do_read_blob(oid, NULL, &size, &buf);
+       if (r != 1)
+               return r;
+
+       add_excludes_from_buffer(buf, size, base, baselen, el);
+       return 0;
+}
+
 struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                      int group_type, const char *src)
 {
diff --git a/dir.h b/dir.h
index 233a2eb..11a047b 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -259,6 +259,9 @@ extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
                                          struct exclude_list *el, struct  index_state *istate);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
+extern int add_excludes_from_blob_to_list(struct object_id *oid,
+                                         const char *base, int baselen,
+                                         struct exclude_list *el);
 extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *el, int srcpos);
index 7519ede..9a9b4e1 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -7,11 +7,16 @@
 #define DEFAULT_EDITOR "vi"
 #endif
 
+int is_terminal_dumb(void)
+{
+       const char *terminal = getenv("TERM");
+       return !terminal || !strcmp(terminal, "dumb");
+}
+
 const char *git_editor(void)
 {
        const char *editor = getenv("GIT_EDITOR");
-       const char *terminal = getenv("TERM");
-       int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb");
+       int terminal_is_dumb = is_terminal_dumb();
 
        if (!editor && editor_program)
                editor = editor_program;
@@ -40,6 +45,23 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
                const char *args[] = { editor, real_path(path), NULL };
                struct child_process p = CHILD_PROCESS_INIT;
                int ret, sig;
+               int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
+
+               if (print_waiting_for_editor) {
+                       /*
+                        * A dumb terminal cannot erase the line later on. Add a
+                        * newline to separate the hint from subsequent output.
+                        *
+                        * Make sure that our message is separated with a whitespace
+                        * from further cruft that may be written by the editor.
+                        */
+                       const char term = is_terminal_dumb() ? '\n' : ' ';
+
+                       fprintf(stderr,
+                               _("hint: Waiting for your editor to close the file...%c"),
+                               term);
+                       fflush(stderr);
+               }
 
                p.argv = args;
                p.env = env;
@@ -58,6 +80,13 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
                if (ret)
                        return error("There was a problem with the editor '%s'.",
                                        editor);
+
+               if (print_waiting_for_editor && !is_terminal_dumb())
+                       /*
+                        * Go back to the beginning and erase the entire line to
+                        * avoid wasting the vertical space.
+                        */
+                       fputs("\r\033[K", stderr);
        }
 
        if (!buffer)
index 8fa032f..63ac38a 100644 (file)
@@ -344,3 +344,18 @@ int use_optional_locks(void)
 {
        return git_env_bool(GIT_OPTIONAL_LOCKS_ENVIRONMENT, 1);
 }
+
+int print_sha1_ellipsis(void)
+{
+       /*
+        * Determine if the calling environment contains the variable
+        * GIT_PRINT_SHA1_ELLIPSIS set to "yes".
+        */
+       static int cached_result = -1; /* unknown */
+
+       if (cached_result < 0) {
+               const char *v = getenv("GIT_PRINT_SHA1_ELLIPSIS");
+               cached_result = (v && !strcasecmp(v, "yes"));
+       }
+       return cached_result;
+}
index cedad4d..68b2ad5 100644 (file)
@@ -484,6 +484,29 @@ static inline int skip_prefix(const char *str, const char *prefix,
        return 0;
 }
 
+/*
+ * If the string "str" is the same as the string in "prefix", then the "arg"
+ * parameter is set to the "def" parameter and 1 is returned.
+ * If the string "str" begins with the string found in "prefix" and then a
+ * "=" sign, then the "arg" parameter is set to "str + strlen(prefix) + 1"
+ * (i.e., to the point in the string right after the prefix and the "=" sign),
+ * and 1 is returned.
+ *
+ * Otherwise, return 0 and leave "arg" untouched.
+ *
+ * When we accept both a "--key" and a "--key=<val>" option, this function
+ * can be used instead of !strcmp(arg, "--key") and then
+ * skip_prefix(arg, "--key=", &arg) to parse such an option.
+ */
+int skip_to_optional_arg_default(const char *str, const char *prefix,
+                                const char **arg, const char *def);
+
+static inline int skip_to_optional_arg(const char *str, const char *prefix,
+                                      const char **arg)
+{
+       return skip_to_optional_arg_default(str, prefix, arg, "");
+}
+
 /*
  * Like skip_prefix, but promises never to read past "len" bytes of the input
  * buffer, and returns the remaining number of bytes in "out" via "outlen".
index 3692992..2d8df83 100755 (executable)
@@ -642,7 +642,7 @@ sub is_sha1 {
 
 sub get_headref ($) {
        my $name = shift;
-       $name =~ s/'/'\\''/;
+       $name =~ s/'/'\\''/g;
        my $r = `git rev-parse --verify '$name' 2>/dev/null`;
        return undef unless $? == 0;
        chomp $r;
index 83620b7..75ea965 100644 (file)
@@ -25,6 +25,8 @@ You are currently in the middle of a merge that has not been fully completed.  Y
        set msg {}
        set parents [list]
        if {[catch {
+                       set name ""
+                       set email ""
                        set fd [git_read cat-file commit $curHEAD]
                        fconfigure $fd -encoding binary -translation lf
                        # By default commits are assumed to be in utf-8
@@ -34,9 +36,7 @@ You are currently in the middle of a merge that has not been fully completed.  Y
                                        lappend parents [string range $line 7 end]
                                } elseif {[string match {encoding *} $line]} {
                                        set enc [string tolower [string range $line 9 end]]
-                               } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} {
-                                       set commit_author [list name $name email $email date $time]
-                               }
+                               } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} { }
                        }
                        set msg [read $fd]
                        close $fd
@@ -44,7 +44,13 @@ You are currently in the middle of a merge that has not been fully completed.  Y
                        set enc [tcl_encoding $enc]
                        if {$enc ne {}} {
                                set msg [encoding convertfrom $enc $msg]
+                               set name [encoding convertfrom $enc $name]
+                               set email [encoding convertfrom $enc $email]
                        }
+                       if {$name ne {} && $email ne {}} {
+                               set commit_author [list name $name email $email date $time]
+                       }
+
                        set msg [string trim $msg]
                } err]} {
                error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"]
index 4378156..e3f5a0a 100644 (file)
@@ -722,27 +722,6 @@ collapse_todo_ids() {
        git rebase--helper --shorten-ids
 }
 
-# Add commands after a pick or after a squash/fixup series
-# in the todo list.
-add_exec_commands () {
-       {
-               first=t
-               while read -r insn rest
-               do
-                       case $insn in
-                       pick)
-                               test -n "$first" ||
-                               printf "%s" "$cmd"
-                               ;;
-                       esac
-                       printf "%s %s\n" "$insn" "$rest"
-                       first=
-               done
-               printf "%s" "$cmd"
-       } <"$1" >"$1.new" &&
-       mv "$1.new" "$1"
-}
-
 # Switch to the branch in $into and notify it in the reflog
 checkout_onto () {
        GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
@@ -982,7 +961,7 @@ fi
 
 test -s "$todo" || echo noop >> "$todo"
 test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
-test -n "$cmd" && add_exec_commands "$todo"
+test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
 
 todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
 todocount=${todocount##* }
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c
new file mode 100644 (file)
index 0000000..4c5b34e
--- /dev/null
@@ -0,0 +1,92 @@
+#include "cache.h"
+#include "commit.h"
+#include "config.h"
+#include "revision.h"
+#include "argv-array.h"
+#include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
+
+/*
+ * Parse value of the argument to the "filter" keyword.
+ * On the command line this looks like:
+ *       --filter=<arg>
+ * and in the pack protocol as:
+ *       "filter" SP <arg>
+ *
+ * The filter keyword will be used by many commands.
+ * See Documentation/rev-list-options.txt for allowed values for <arg>.
+ *
+ * Capture the given arg as the "filter_spec".  This can be forwarded to
+ * subordinate commands when necessary.  We also "intern" the arg for
+ * the convenience of the current command.
+ */
+int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
+                             const char *arg)
+{
+       const char *v0;
+
+       if (filter_options->choice)
+               die(_("multiple object filter types cannot be combined"));
+
+       filter_options->filter_spec = strdup(arg);
+
+       if (!strcmp(arg, "blob:none")) {
+               filter_options->choice = LOFC_BLOB_NONE;
+               return 0;
+       }
+
+       if (skip_prefix(arg, "blob:limit=", &v0)) {
+               if (!git_parse_ulong(v0, &filter_options->blob_limit_value))
+                       die(_("invalid filter-spec expression '%s'"), arg);
+               filter_options->choice = LOFC_BLOB_LIMIT;
+               return 0;
+       }
+
+       if (skip_prefix(arg, "sparse:oid=", &v0)) {
+               struct object_context oc;
+               struct object_id sparse_oid;
+
+               /*
+                * Try to parse <oid-expression> into an OID for the current
+                * command, but DO NOT complain if we don't have the blob or
+                * ref locally.
+                */
+               if (!get_oid_with_context(v0, GET_OID_BLOB,
+                                         &sparse_oid, &oc))
+                       filter_options->sparse_oid_value = oiddup(&sparse_oid);
+               filter_options->choice = LOFC_SPARSE_OID;
+               return 0;
+       }
+
+       if (skip_prefix(arg, "sparse:path=", &v0)) {
+               filter_options->choice = LOFC_SPARSE_PATH;
+               filter_options->sparse_path_value = strdup(v0);
+               return 0;
+       }
+
+       die(_("invalid filter-spec expression '%s'"), arg);
+       return 0;
+}
+
+int opt_parse_list_objects_filter(const struct option *opt,
+                                 const char *arg, int unset)
+{
+       struct list_objects_filter_options *filter_options = opt->value;
+
+       if (unset || !arg) {
+               list_objects_filter_release(filter_options);
+               return 0;
+       }
+
+       return parse_list_objects_filter(filter_options, arg);
+}
+
+void list_objects_filter_release(
+       struct list_objects_filter_options *filter_options)
+{
+       free(filter_options->filter_spec);
+       free(filter_options->sparse_oid_value);
+       free(filter_options->sparse_path_value);
+       memset(filter_options, 0, sizeof(*filter_options));
+}
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h
new file mode 100644 (file)
index 0000000..eea44a1
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef LIST_OBJECTS_FILTER_OPTIONS_H
+#define LIST_OBJECTS_FILTER_OPTIONS_H
+
+#include "parse-options.h"
+
+/*
+ * The list of defined filters for list-objects.
+ */
+enum list_objects_filter_choice {
+       LOFC_DISABLED = 0,
+       LOFC_BLOB_NONE,
+       LOFC_BLOB_LIMIT,
+       LOFC_SPARSE_OID,
+       LOFC_SPARSE_PATH,
+       LOFC__COUNT /* must be last */
+};
+
+struct list_objects_filter_options {
+       /*
+        * 'filter_spec' is the raw argument value given on the command line
+        * or protocol request.  (The part after the "--keyword=".)  For
+        * commands that launch filtering sub-processes, this value should be
+        * passed to them as received by the current process.
+        */
+       char *filter_spec;
+
+       /*
+        * 'choice' is determined by parsing the filter-spec.  This indicates
+        * the filtering algorithm to use.
+        */
+       enum list_objects_filter_choice choice;
+
+       /*
+        * Parsed values (fields) from within the filter-spec.  These are
+        * choice-specific; not all values will be defined for any given
+        * choice.
+        */
+       struct object_id *sparse_oid_value;
+       char *sparse_path_value;
+       unsigned long blob_limit_value;
+};
+
+/* Normalized command line arguments */
+#define CL_ARG__FILTER "filter"
+
+int parse_list_objects_filter(
+       struct list_objects_filter_options *filter_options,
+       const char *arg);
+
+int opt_parse_list_objects_filter(const struct option *opt,
+                                 const char *arg, int unset);
+
+#define OPT_PARSE_LIST_OBJECTS_FILTER(fo) \
+       { OPTION_CALLBACK, 0, CL_ARG__FILTER, fo, N_("args"), \
+         N_("object filtering"), 0, \
+         opt_parse_list_objects_filter }
+
+void list_objects_filter_release(
+       struct list_objects_filter_options *filter_options);
+
+#endif /* LIST_OBJECTS_FILTER_OPTIONS_H */
diff --git a/list-objects-filter.c b/list-objects-filter.c
new file mode 100644 (file)
index 0000000..4356c45
--- /dev/null
@@ -0,0 +1,401 @@
+#include "cache.h"
+#include "dir.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+#include "tree-walk.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
+#include "oidset.h"
+
+/* Remember to update object flag allocation in object.h */
+/*
+ * FILTER_SHOWN_BUT_REVISIT -- we set this bit on tree objects
+ * that have been shown, but should be revisited if they appear
+ * in the traversal (until we mark it SEEN).  This is a way to
+ * let us silently de-dup calls to show() in the caller.  This
+ * is subtly different from the "revision.h:SHOWN" and the
+ * "sha1_name.c:ONELINE_SEEN" bits.  And also different from
+ * the non-de-dup usage in pack-bitmap.c
+ */
+#define FILTER_SHOWN_BUT_REVISIT (1<<21)
+
+/*
+ * A filter for list-objects to omit ALL blobs from the traversal.
+ * And to OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_blobs_none_data {
+       struct oidset *omits;
+};
+
+static enum list_objects_filter_result filter_blobs_none(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data_)
+{
+       struct filter_blobs_none_data *filter_data = filter_data_;
+
+       switch (filter_situation) {
+       default:
+               die("unknown filter_situation");
+               return LOFR_ZERO;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+               /* always include all tree objects */
+               return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+       case LOFS_END_TREE:
+               assert(obj->type == OBJ_TREE);
+               return LOFR_ZERO;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+               assert((obj->flags & SEEN) == 0);
+
+               if (filter_data->omits)
+                       oidset_insert(filter_data->omits, &obj->oid);
+               return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
+       }
+}
+
+static void *filter_blobs_none__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+
+       *filter_fn = filter_blobs_none;
+       *filter_free_fn = free;
+       return d;
+}
+
+/*
+ * A filter for list-objects to omit large blobs.
+ * And to OPTIONALLY collect a list of the omitted OIDs.
+ */
+struct filter_blobs_limit_data {
+       struct oidset *omits;
+       unsigned long max_bytes;
+};
+
+static enum list_objects_filter_result filter_blobs_limit(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data_)
+{
+       struct filter_blobs_limit_data *filter_data = filter_data_;
+       unsigned long object_length;
+       enum object_type t;
+
+       switch (filter_situation) {
+       default:
+               die("unknown filter_situation");
+               return LOFR_ZERO;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+               /* always include all tree objects */
+               return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+
+       case LOFS_END_TREE:
+               assert(obj->type == OBJ_TREE);
+               return LOFR_ZERO;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+               assert((obj->flags & SEEN) == 0);
+
+               t = sha1_object_info(obj->oid.hash, &object_length);
+               if (t != OBJ_BLOB) { /* probably OBJ_NONE */
+                       /*
+                        * We DO NOT have the blob locally, so we cannot
+                        * apply the size filter criteria.  Be conservative
+                        * and force show it (and let the caller deal with
+                        * the ambiguity).
+                        */
+                       goto include_it;
+               }
+
+               if (object_length < filter_data->max_bytes)
+                       goto include_it;
+
+               if (filter_data->omits)
+                       oidset_insert(filter_data->omits, &obj->oid);
+               return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
+       }
+
+include_it:
+       if (filter_data->omits)
+               oidset_remove(filter_data->omits, &obj->oid);
+       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+}
+
+static void *filter_blobs_limit__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+       d->max_bytes = filter_options->blob_limit_value;
+
+       *filter_fn = filter_blobs_limit;
+       *filter_free_fn = free;
+       return d;
+}
+
+/*
+ * A filter driven by a sparse-checkout specification to only
+ * include blobs that a sparse checkout would populate.
+ *
+ * The sparse-checkout spec can be loaded from a blob with the
+ * given OID or from a local pathname.  We allow an OID because
+ * the repo may be bare or we may be doing the filtering on the
+ * server.
+ */
+struct frame {
+       /*
+        * defval is the usual default include/exclude value that
+        * should be inherited as we recurse into directories based
+        * upon pattern matching of the directory itself or of a
+        * containing directory.
+        */
+       int defval;
+
+       /*
+        * 1 if the directory (recursively) contains any provisionally
+        * omitted objects.
+        *
+        * 0 if everything (recursively) contained in this directory
+        * has been explicitly included (SHOWN) in the result and
+        * the directory may be short-cut later in the traversal.
+        */
+       unsigned child_prov_omit : 1;
+};
+
+struct filter_sparse_data {
+       struct oidset *omits;
+       struct exclude_list el;
+
+       size_t nr, alloc;
+       struct frame *array_frame;
+};
+
+static enum list_objects_filter_result filter_sparse(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data_)
+{
+       struct filter_sparse_data *filter_data = filter_data_;
+       int val, dtype;
+       struct frame *frame;
+
+       switch (filter_situation) {
+       default:
+               die("unknown filter_situation");
+               return LOFR_ZERO;
+
+       case LOFS_BEGIN_TREE:
+               assert(obj->type == OBJ_TREE);
+               dtype = DT_DIR;
+               val = is_excluded_from_list(pathname, strlen(pathname),
+                                           filename, &dtype, &filter_data->el,
+                                           &the_index);
+               if (val < 0)
+                       val = filter_data->array_frame[filter_data->nr].defval;
+
+               ALLOC_GROW(filter_data->array_frame, filter_data->nr + 1,
+                          filter_data->alloc);
+               filter_data->nr++;
+               filter_data->array_frame[filter_data->nr].defval = val;
+               filter_data->array_frame[filter_data->nr].child_prov_omit = 0;
+
+               /*
+                * A directory with this tree OID may appear in multiple
+                * places in the tree. (Think of a directory move or copy,
+                * with no other changes, so the OID is the same, but the
+                * full pathnames of objects within this directory are new
+                * and may match is_excluded() patterns differently.)
+                * So we cannot mark this directory as SEEN (yet), since
+                * that will prevent process_tree() from revisiting this
+                * tree object with other pathname prefixes.
+                *
+                * Only _DO_SHOW the tree object the first time we visit
+                * this tree object.
+                *
+                * We always show all tree objects.  A future optimization
+                * may want to attempt to narrow this.
+                */
+               if (obj->flags & FILTER_SHOWN_BUT_REVISIT)
+                       return LOFR_ZERO;
+               obj->flags |= FILTER_SHOWN_BUT_REVISIT;
+               return LOFR_DO_SHOW;
+
+       case LOFS_END_TREE:
+               assert(obj->type == OBJ_TREE);
+               assert(filter_data->nr > 0);
+
+               frame = &filter_data->array_frame[filter_data->nr];
+               filter_data->nr--;
+
+               /*
+                * Tell our parent directory if any of our children were
+                * provisionally omitted.
+                */
+               filter_data->array_frame[filter_data->nr].child_prov_omit |=
+                       frame->child_prov_omit;
+
+               /*
+                * If there are NO provisionally omitted child objects (ALL child
+                * objects in this folder were INCLUDED), then we can mark the
+                * folder as SEEN (so we will not have to revisit it again).
+                */
+               if (!frame->child_prov_omit)
+                       return LOFR_MARK_SEEN;
+               return LOFR_ZERO;
+
+       case LOFS_BLOB:
+               assert(obj->type == OBJ_BLOB);
+               assert((obj->flags & SEEN) == 0);
+
+               frame = &filter_data->array_frame[filter_data->nr];
+
+               dtype = DT_REG;
+               val = is_excluded_from_list(pathname, strlen(pathname),
+                                           filename, &dtype, &filter_data->el,
+                                           &the_index);
+               if (val < 0)
+                       val = frame->defval;
+               if (val > 0) {
+                       if (filter_data->omits)
+                               oidset_remove(filter_data->omits, &obj->oid);
+                       return LOFR_MARK_SEEN | LOFR_DO_SHOW;
+               }
+
+               /*
+                * Provisionally omit it.  We've already established that
+                * this pathname is not in the sparse-checkout specification
+                * with the CURRENT pathname, so we *WANT* to omit this blob.
+                *
+                * However, a pathname elsewhere in the tree may also
+                * reference this same blob, so we cannot reject it yet.
+                * Leave the LOFR_ bits unset so that if the blob appears
+                * again in the traversal, we will be asked again.
+                */
+               if (filter_data->omits)
+                       oidset_insert(filter_data->omits, &obj->oid);
+
+               /*
+                * Remember that at least 1 blob in this tree was
+                * provisionally omitted.  This prevents us from short
+                * cutting the tree in future iterations.
+                */
+               frame->child_prov_omit = 1;
+               return LOFR_ZERO;
+       }
+}
+
+
+static void filter_sparse_free(void *filter_data)
+{
+       struct filter_sparse_data *d = filter_data;
+       /* TODO free contents of 'd' */
+       free(d);
+}
+
+static void *filter_sparse_oid__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+       if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value,
+                                          NULL, 0, &d->el) < 0)
+               die("could not load filter specification");
+
+       ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
+       d->array_frame[d->nr].defval = 0; /* default to include */
+       d->array_frame[d->nr].child_prov_omit = 0;
+
+       *filter_fn = filter_sparse;
+       *filter_free_fn = filter_sparse_free;
+       return d;
+}
+
+static void *filter_sparse_path__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       struct filter_sparse_data *d = xcalloc(1, sizeof(*d));
+       d->omits = omitted;
+       if (add_excludes_from_file_to_list(filter_options->sparse_path_value,
+                                          NULL, 0, &d->el, NULL) < 0)
+               die("could not load filter specification");
+
+       ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
+       d->array_frame[d->nr].defval = 0; /* default to include */
+       d->array_frame[d->nr].child_prov_omit = 0;
+
+       *filter_fn = filter_sparse;
+       *filter_free_fn = filter_sparse_free;
+       return d;
+}
+
+typedef void *(*filter_init_fn)(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn);
+
+/*
+ * Must match "enum list_objects_filter_choice".
+ */
+static filter_init_fn s_filters[] = {
+       NULL,
+       filter_blobs_none__init,
+       filter_blobs_limit__init,
+       filter_sparse_oid__init,
+       filter_sparse_path__init,
+};
+
+void *list_objects_filter__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn)
+{
+       filter_init_fn init_fn;
+
+       assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
+
+       if (filter_options->choice >= LOFC__COUNT)
+               die("invalid list-objects filter choice: %d",
+                   filter_options->choice);
+
+       init_fn = s_filters[filter_options->choice];
+       if (init_fn)
+               return init_fn(omitted, filter_options,
+                              filter_fn, filter_free_fn);
+       *filter_fn = NULL;
+       *filter_free_fn = NULL;
+       return NULL;
+}
diff --git a/list-objects-filter.h b/list-objects-filter.h
new file mode 100644 (file)
index 0000000..a963d02
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef LIST_OBJECTS_FILTER_H
+#define LIST_OBJECTS_FILTER_H
+
+/*
+ * During list-object traversal we allow certain objects to be
+ * filtered (omitted) from the result.  The active filter uses
+ * these result values to guide list-objects.
+ *
+ * _ZERO      : Do nothing with the object at this time.  It may
+ *              be revisited if it appears in another place in
+ *              the tree or in another commit during the overall
+ *              traversal.
+ *
+ * _MARK_SEEN : Mark this object as "SEEN" in the object flags.
+ *              This will prevent it from being revisited during
+ *              the remainder of the traversal.  This DOES NOT
+ *              imply that it will be included in the results.
+ *
+ * _DO_SHOW   : Show this object in the results (call show() on it).
+ *              In general, objects should only be shown once, but
+ *              this result DOES NOT imply that we mark it SEEN.
+ *
+ * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW)
+ * but they can be used independently, such as when sparse-checkout
+ * pattern matching is being applied.
+ *
+ * A _MARK_SEEN without _DO_SHOW can be called a hard-omit -- the
+ * object is not shown and will never be reconsidered (unless a
+ * previous iteration has already shown it).
+ *
+ * A _DO_SHOW without _MARK_SEEN can be used, for example, to
+ * include a directory, but then revisit it to selectively include
+ * or omit objects within it.
+ *
+ * A _ZERO can be called a provisional-omit -- the object is NOT shown,
+ * but *may* be revisited (if the object appears again in the traversal).
+ * Therefore, it will be omitted from the results *unless* a later
+ * iteration causes it to be shown.
+ */
+enum list_objects_filter_result {
+       LOFR_ZERO      = 0,
+       LOFR_MARK_SEEN = 1<<0,
+       LOFR_DO_SHOW   = 1<<1,
+};
+
+enum list_objects_filter_situation {
+       LOFS_BEGIN_TREE,
+       LOFS_END_TREE,
+       LOFS_BLOB
+};
+
+typedef enum list_objects_filter_result (*filter_object_fn)(
+       enum list_objects_filter_situation filter_situation,
+       struct object *obj,
+       const char *pathname,
+       const char *filename,
+       void *filter_data);
+
+typedef void (*filter_free_fn)(void *filter_data);
+
+/*
+ * Constructor for the set of defined list-objects filters.
+ * Returns a generic "void *filter_data".
+ *
+ * The returned "filter_fn" will be used by traverse_commit_list()
+ * to filter the results.
+ *
+ * The returned "filter_free_fn" is a destructor for the
+ * filter_data.
+ */
+void *list_objects_filter__init(
+       struct oidset *omitted,
+       struct list_objects_filter_options *filter_options,
+       filter_object_fn *filter_fn,
+       filter_free_fn *filter_free_fn);
+
+#endif /* LIST_OBJECTS_FILTER_H */
index b3931fa..d9e83d0 100644 (file)
@@ -7,16 +7,21 @@
 #include "tree-walk.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "list-objects-filter.h"
+#include "list-objects-filter-options.h"
 
 static void process_blob(struct rev_info *revs,
                         struct blob *blob,
                         show_object_fn show,
                         struct strbuf *path,
                         const char *name,
-                        void *cb_data)
+                        void *cb_data,
+                        filter_object_fn filter_fn,
+                        void *filter_data)
 {
        struct object *obj = &blob->object;
        size_t pathlen;
+       enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
 
        if (!revs->blob_objects)
                return;
@@ -24,11 +29,17 @@ static void process_blob(struct rev_info *revs,
                die("bad blob object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
-       obj->flags |= SEEN;
 
        pathlen = path->len;
        strbuf_addstr(path, name);
-       show(obj, path->buf, cb_data);
+       if (filter_fn)
+               r = filter_fn(LOFS_BLOB, obj,
+                             path->buf, &path->buf[pathlen],
+                             filter_data);
+       if (r & LOFR_MARK_SEEN)
+               obj->flags |= SEEN;
+       if (r & LOFR_DO_SHOW)
+               show(obj, path->buf, cb_data);
        strbuf_setlen(path, pathlen);
 }
 
@@ -69,7 +80,9 @@ static void process_tree(struct rev_info *revs,
                         show_object_fn show,
                         struct strbuf *base,
                         const char *name,
-                        void *cb_data)
+                        void *cb_data,
+                        filter_object_fn filter_fn,
+                        void *filter_data)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
@@ -77,6 +90,7 @@ static void process_tree(struct rev_info *revs,
        enum interesting match = revs->diffopt.pathspec.nr == 0 ?
                all_entries_interesting: entry_not_interesting;
        int baselen = base->len;
+       enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW;
 
        if (!revs->tree_objects)
                return;
@@ -90,9 +104,15 @@ static void process_tree(struct rev_info *revs,
                die("bad tree object %s", oid_to_hex(&obj->oid));
        }
 
-       obj->flags |= SEEN;
        strbuf_addstr(base, name);
-       show(obj, base->buf, cb_data);
+       if (filter_fn)
+               r = filter_fn(LOFS_BEGIN_TREE, obj,
+                             base->buf, &base->buf[baselen],
+                             filter_data);
+       if (r & LOFR_MARK_SEEN)
+               obj->flags |= SEEN;
+       if (r & LOFR_DO_SHOW)
+               show(obj, base->buf, cb_data);
        if (base->len)
                strbuf_addch(base, '/');
 
@@ -112,7 +132,7 @@ static void process_tree(struct rev_info *revs,
                        process_tree(revs,
                                     lookup_tree(entry.oid),
                                     show, base, entry.path,
-                                    cb_data);
+                                    cb_data, filter_fn, filter_data);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.oid->hash,
                                        show, base, entry.path,
@@ -121,8 +141,19 @@ static void process_tree(struct rev_info *revs,
                        process_blob(revs,
                                     lookup_blob(entry.oid),
                                     show, base, entry.path,
-                                    cb_data);
+                                    cb_data, filter_fn, filter_data);
        }
+
+       if (filter_fn) {
+               r = filter_fn(LOFS_END_TREE, obj,
+                             base->buf, &base->buf[baselen],
+                             filter_data);
+               if (r & LOFR_MARK_SEEN)
+                       obj->flags |= SEEN;
+               if (r & LOFR_DO_SHOW)
+                       show(obj, base->buf, cb_data);
+       }
+
        strbuf_setlen(base, baselen);
        free_tree_buffer(tree);
 }
@@ -183,10 +214,12 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
        add_pending_object(revs, &tree->object, "");
 }
 
-void traverse_commit_list(struct rev_info *revs,
-                         show_commit_fn show_commit,
-                         show_object_fn show_object,
-                         void *data)
+static void do_traverse(struct rev_info *revs,
+                       show_commit_fn show_commit,
+                       show_object_fn show_object,
+                       void *show_data,
+                       filter_object_fn filter_fn,
+                       void *filter_data)
 {
        int i;
        struct commit *commit;
@@ -200,7 +233,7 @@ void traverse_commit_list(struct rev_info *revs,
                 */
                if (commit->tree)
                        add_pending_tree(revs, commit->tree);
-               show_commit(commit, data);
+               show_commit(commit, show_data);
        }
        for (i = 0; i < revs->pending.nr; i++) {
                struct object_array_entry *pending = revs->pending.objects + i;
@@ -211,19 +244,21 @@ void traverse_commit_list(struct rev_info *revs,
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       show_object(obj, name, data);
+                       show_object(obj, name, show_data);
                        continue;
                }
                if (!path)
                        path = "";
                if (obj->type == OBJ_TREE) {
                        process_tree(revs, (struct tree *)obj, show_object,
-                                    &base, path, data);
+                                    &base, path, show_data,
+                                    filter_fn, filter_data);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
                        process_blob(revs, (struct blob *)obj, show_object,
-                                    &base, path, data);
+                                    &base, path, show_data,
+                                    filter_fn, filter_data);
                        continue;
                }
                die("unknown pending object %s (%s)",
@@ -232,3 +267,31 @@ void traverse_commit_list(struct rev_info *revs,
        object_array_clear(&revs->pending);
        strbuf_release(&base);
 }
+
+void traverse_commit_list(struct rev_info *revs,
+                         show_commit_fn show_commit,
+                         show_object_fn show_object,
+                         void *show_data)
+{
+       do_traverse(revs, show_commit, show_object, show_data, NULL, NULL);
+}
+
+void traverse_commit_list_filtered(
+       struct list_objects_filter_options *filter_options,
+       struct rev_info *revs,
+       show_commit_fn show_commit,
+       show_object_fn show_object,
+       void *show_data,
+       struct oidset *omitted)
+{
+       filter_object_fn filter_fn = NULL;
+       filter_free_fn filter_free_fn = NULL;
+       void *filter_data = NULL;
+
+       filter_data = list_objects_filter__init(omitted, filter_options,
+                                               &filter_fn, &filter_free_fn);
+       do_traverse(revs, show_commit, show_object, show_data,
+                   filter_fn, filter_data);
+       if (filter_data && filter_free_fn)
+               filter_free_fn(filter_data);
+}
index 0cebf85..aa618d7 100644 (file)
@@ -8,4 +8,15 @@ void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, voi
 typedef void (*show_edge_fn)(struct commit *);
 void mark_edges_uninteresting(struct rev_info *, show_edge_fn);
 
-#endif
+struct oidset;
+struct list_objects_filter_options;
+
+void traverse_commit_list_filtered(
+       struct list_objects_filter_options *filter_options,
+       struct rev_info *revs,
+       show_commit_fn show_commit,
+       show_object_fn show_object,
+       void *show_data,
+       struct oidset *omitted);
+
+#endif /* LIST_OBJECTS_H */
index a4c280d..2ecf495 100644 (file)
@@ -646,7 +646,7 @@ static int remove_file(struct merge_options *o, int clean,
                if (ignore_case) {
                        struct cache_entry *ce;
                        ce = cache_file_exists(path, strlen(path), ignore_case);
-                       if (ce && ce_stage(ce) == 0)
+                       if (ce && ce_stage(ce) == 0 && strcmp(path, ce->name))
                                return 0;
                }
                if (remove_path(path))
index df8abe9..f34461d 100644 (file)
--- a/object.h
+++ b/object.h
@@ -38,6 +38,7 @@ struct object_array {
  * http-push.c:                            16-----19
  * commit.c:                               16-----19
  * sha1_name.c:                                     20
+ * list-objects-filter.c:                             21
  * builtin/fsck.c:  0--3
  */
 #define FLAG_BITS  27
index 18f54cd..d3cd2bb 100644 (file)
--- a/oidmap.h
+++ b/oidmap.h
@@ -65,4 +65,26 @@ extern void *oidmap_put(struct oidmap *map, void *entry);
  */
 extern void *oidmap_remove(struct oidmap *map, const struct object_id *key);
 
+
+struct oidmap_iter {
+       struct hashmap_iter h_iter;
+};
+
+static inline void oidmap_iter_init(struct oidmap *map, struct oidmap_iter *iter)
+{
+       hashmap_iter_init(&map->map, &iter->h_iter);
+}
+
+static inline void *oidmap_iter_next(struct oidmap_iter *iter)
+{
+       return hashmap_iter_next(&iter->h_iter);
+}
+
+static inline void *oidmap_iter_first(struct oidmap *map,
+                                     struct oidmap_iter *iter)
+{
+       oidmap_iter_init(map, iter);
+       return oidmap_iter_next(iter);
+}
+
 #endif
index f1f874a..454c54f 100644 (file)
--- a/oidset.c
+++ b/oidset.c
@@ -24,6 +24,16 @@ int oidset_insert(struct oidset *set, const struct object_id *oid)
        return 0;
 }
 
+int oidset_remove(struct oidset *set, const struct object_id *oid)
+{
+       struct oidmap_entry *entry;
+
+       entry = oidmap_remove(&set->map, oid);
+       free(entry);
+
+       return (entry != NULL);
+}
+
 void oidset_clear(struct oidset *set)
 {
        oidmap_free(&set->map, 1);
index f4c9e0f..783abce 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -24,6 +24,12 @@ struct oidset {
 
 #define OIDSET_INIT { OIDMAP_INIT }
 
+
+static inline void oidset_init(struct oidset *set, size_t initial_size)
+{
+       return oidmap_init(&set->map, initial_size);
+}
+
 /**
  * Returns true iff `set` contains `oid`.
  */
@@ -38,10 +44,40 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid);
  */
 int oidset_insert(struct oidset *set, const struct object_id *oid);
 
+/**
+ * Remove the oid from the set.
+ *
+ * Returns 1 if the oid was present in the set, 0 otherwise.
+ */
+int oidset_remove(struct oidset *set, const struct object_id *oid);
+
 /**
  * Remove all entries from the oidset, freeing any resources associated with
  * it.
  */
 void oidset_clear(struct oidset *set);
 
+struct oidset_iter {
+       struct oidmap_iter m_iter;
+};
+
+static inline void oidset_iter_init(struct oidset *set,
+                                   struct oidset_iter *iter)
+{
+       oidmap_iter_init(&set->map, &iter->m_iter);
+}
+
+static inline struct object_id *oidset_iter_next(struct oidset_iter *iter)
+{
+       struct oidmap_entry *e = oidmap_iter_next(&iter->m_iter);
+       return e ? &e->oid : NULL;
+}
+
+static inline struct object_id *oidset_iter_first(struct oidset *set,
+                                                 struct oidset_iter *iter)
+{
+       oidset_iter_init(set, iter);
+       return oidset_iter_next(iter);
+}
+
 #endif /* OIDSET_H */
diff --git a/path.h b/path.h
index 9541620..1ccd037 100644 (file)
--- a/path.h
+++ b/path.h
 struct repository;
 
 /*
- * Return a statically allocated filename, either generically (mkpath), in
- * the repository directory (git_path), or in a submodule's repository
- * directory (git_path_submodule). In all cases, note that the result
- * may be overwritten by another call to _any_ of the functions. Consider
- * using the safer "dup" or "strbuf" formats below (in some cases, the
- * unsafe versions have already been removed).
+ * The result to all functions which return statically allocated memory may be
+ * overwritten by another call to _any_ one of these functions. Consider using
+ * the safer variants which operate on strbufs or return allocated memory.
  */
-extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
-extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 
+/*
+ * Return a statically allocated path.
+ */
+extern const char *mkpath(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Return a path.
+ */
+extern char *mkpathdup(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Construct a path and place the result in the provided buffer `buf`.
+ */
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
-extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
-       __attribute__((format (printf, 2, 3)));
+
+/*
+ * The `git_common_path` family of functions will construct a path into a
+ * repository's common git directory, which is shared by all worktrees.
+ */
+
+/*
+ * Constructs a path into the common git directory of repository `repo` and
+ * append it in the provided buffer `sb`.
+ */
 extern void strbuf_git_common_path(struct strbuf *sb,
                                   const struct repository *repo,
                                   const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
-extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
-       __attribute__((format (printf, 2, 3)));
-extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path,
-                                    const char *fmt, ...)
-       __attribute__((format (printf, 3, 4)));
-extern char *git_pathdup(const char *fmt, ...)
-       __attribute__((format (printf, 1, 2)));
-extern char *mkpathdup(const char *fmt, ...)
+
+/*
+ * Return a statically allocated path into the main repository's
+ * (the_repository) common git directory.
+ */
+extern const char *git_common_path(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
-extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
-       __attribute__((format (printf, 2, 3)));
 
+
+/*
+ * The `git_path` family of functions will construct a path into a repository's
+ * git directory.
+ *
+ * These functions will perform adjustments to the resultant path to account
+ * for special paths which are either considered common among worktrees (e.g.
+ * paths into the object directory) or have been explicitly set via an
+ * environment variable or config (e.g. path to the index file).
+ *
+ * For an exhaustive list of the adjustments made look at `common_list` and
+ * `adjust_git_path` in path.c.
+ */
+
+/*
+ * Return a path into the git directory of repository `repo`.
+ */
 extern char *repo_git_path(const struct repository *repo,
                           const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into the git directory of repository `repo` and append it
+ * to the provided buffer `sb`.
+ */
 extern void strbuf_repo_git_path(struct strbuf *sb,
                                 const struct repository *repo,
                                 const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 
+/*
+ * Return a statically allocated path into the main repository's
+ * (the_repository) git directory.
+ */
+extern const char *git_path(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Return a path into the main repository's (the_repository) git directory.
+ */
+extern char *git_pathdup(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
+
+/*
+ * Construct a path into the main repository's (the_repository) git directory
+ * and place it in the provided buffer `buf`, the contents of the buffer will
+ * be overridden.
+ */
+extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into the main repository's (the_repository) git directory
+ * and append it to the provided buffer `sb`.
+ */
+extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
+/*
+ * Return a path into the worktree of repository `repo`.
+ *
+ * If the repository doesn't have a worktree NULL is returned.
+ */
 extern char *repo_worktree_path(const struct repository *repo,
                                const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into the worktree of repository `repo` and append it
+ * to the provided buffer `sb`.
+ *
+ * If the repository doesn't have a worktree nothing will be appended to `sb`.
+ */
 extern void strbuf_repo_worktree_path(struct strbuf *sb,
                                      const struct repository *repo,
                                      const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 
+/*
+ * Return a path into a submodule's git directory located at `path`.  `path`
+ * must only reference a submodule of the main repository (the_repository).
+ */
+extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
+
+/*
+ * Construct a path into a submodule's git directory located at `path` and
+ * append it to the provided buffer `sb`.  `path` must only reference a
+ * submodule of the main repository (the_repository).
+ */
+extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path,
+                                    const char *fmt, ...)
+       __attribute__((format (printf, 3, 4)));
+
 extern void report_linked_checkout_garbage(void);
 
 /*
index 6420d10..099a170 100644 (file)
@@ -24,6 +24,7 @@ struct pathspec {
        int nr;
        unsigned int has_wildcard:1;
        unsigned int recursive:1;
+       unsigned int recurse_submodules:1;
        unsigned magic;
        int max_depth;
        struct pathspec_item {
diff --git a/pretty.h b/pretty.h
new file mode 100644 (file)
index 0000000..5c85d94
--- /dev/null
+++ b/pretty.h
@@ -0,0 +1,131 @@
+#ifndef PRETTY_H
+#define PRETTY_H
+
+struct commit;
+
+/* Commit formats */
+enum cmit_fmt {
+       CMIT_FMT_RAW,
+       CMIT_FMT_MEDIUM,
+       CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
+       CMIT_FMT_SHORT,
+       CMIT_FMT_FULL,
+       CMIT_FMT_FULLER,
+       CMIT_FMT_ONELINE,
+       CMIT_FMT_EMAIL,
+       CMIT_FMT_MBOXRD,
+       CMIT_FMT_USERFORMAT,
+
+       CMIT_FMT_UNSPECIFIED
+};
+
+struct pretty_print_context {
+       /*
+        * Callers should tweak these to change the behavior of pp_* functions.
+        */
+       enum cmit_fmt fmt;
+       int abbrev;
+       const char *after_subject;
+       int preserve_subject;
+       struct date_mode date_mode;
+       unsigned date_mode_explicit:1;
+       int print_email_subject;
+       int expand_tabs_in_log;
+       int need_8bit_cte;
+       char *notes_message;
+       struct reflog_walk_info *reflog_info;
+       struct rev_info *rev;
+       const char *output_encoding;
+       struct string_list *mailmap;
+       int color;
+       struct ident_split *from_ident;
+
+       /*
+        * Fields below here are manipulated internally by pp_* functions and
+        * should not be counted on by callers.
+        */
+       struct string_list in_body_headers;
+       int graph_width;
+};
+
+/* Check whether commit format is mail. */
+static inline int cmit_fmt_is_mail(enum cmit_fmt fmt)
+{
+       return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD);
+}
+
+struct userformat_want {
+       unsigned notes:1;
+};
+
+/* Set the flag "w->notes" if there is placeholder %N in "fmt". */
+void userformat_find_requirements(const char *fmt, struct userformat_want *w);
+
+/*
+ * Shortcut for invoking pretty_print_commit if we do not have any context.
+ * Context would be set empty except "fmt".
+ */
+void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
+                       struct strbuf *sb);
+
+/*
+ * Get information about user and date from "line", format it and
+ * put it into "sb".
+ * Format of "line" must be readable for split_ident_line function.
+ * The resulting format is "what: name <email> date".
+ */
+void pp_user_info(struct pretty_print_context *pp, const char *what,
+                       struct strbuf *sb, const char *line,
+                       const char *encoding);
+
+/*
+ * Format title line of commit message taken from "msg_p" and
+ * put it into "sb".
+ * First line of "msg_p" is also affected.
+ */
+void pp_title_line(struct pretty_print_context *pp, const char **msg_p,
+                       struct strbuf *sb, const char *encoding,
+                       int need_8bit_cte);
+
+/*
+ * Get current state of commit message from "msg_p" and continue formatting
+ * by adding indentation and '>' signs. Put result into "sb".
+ */
+void pp_remainder(struct pretty_print_context *pp, const char **msg_p,
+                       struct strbuf *sb, int indent);
+
+/*
+ * Create a text message about commit using given "format" and "context".
+ * Put the result to "sb".
+ * Please use this function for custom formats.
+ */
+void format_commit_message(const struct commit *commit,
+                       const char *format, struct strbuf *sb,
+                       const struct pretty_print_context *context);
+
+/*
+ * Parse given arguments from "arg", check it for correctness and
+ * fill struct rev_info.
+ */
+void get_commit_format(const char *arg, struct rev_info *);
+
+/*
+ * Make a commit message with all rules from given "pp"
+ * and put it into "sb".
+ * Please use this function if you have a context (candidate for "pp").
+ */
+void pretty_print_commit(struct pretty_print_context *pp,
+                       const struct commit *commit,
+                       struct strbuf *sb);
+
+/*
+ * Change line breaks in "msg" to "line_separator" and put it into "sb".
+ * Return "msg" itself.
+ */
+const char *format_subject(struct strbuf *sb, const char *msg,
+                       const char *line_separator);
+
+/* Check if "cmit_fmt" will produce an empty output. */
+int commit_format_is_empty(enum cmit_fmt);
+
+#endif /* PRETTY_H */
index 5f87f45..5a99c9f 100644 (file)
@@ -30,8 +30,8 @@ struct throughput {
 
 struct progress {
        const char *title;
-       int last_value;
-       unsigned total;
+       uint64_t last_value;
+       uint64_t total;
        unsigned last_percent;
        unsigned delay;
        struct throughput *throughput;
@@ -78,7 +78,7 @@ static int is_foreground_fd(int fd)
        return tpgrp < 0 || tpgrp == getpgid(0);
 }
 
-static int display(struct progress *progress, unsigned n, const char *done)
+static int display(struct progress *progress, uint64_t n, const char *done)
 {
        const char *eol, *tp;
 
@@ -93,9 +93,10 @@ static int display(struct progress *progress, unsigned n, const char *done)
                if (percent != progress->last_percent || progress_update) {
                        progress->last_percent = percent;
                        if (is_foreground_fd(fileno(stderr)) || done) {
-                               fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
-                                       progress->title, percent, n,
-                                       progress->total, tp, eol);
+                               fprintf(stderr, "%s: %3u%% (%"PRIuMAX"/%"PRIuMAX")%s%s",
+                                       progress->title, percent,
+                                       (uintmax_t)n, (uintmax_t)progress->total,
+                                       tp, eol);
                                fflush(stderr);
                        }
                        progress_update = 0;
@@ -103,8 +104,8 @@ static int display(struct progress *progress, unsigned n, const char *done)
                }
        } else if (progress_update) {
                if (is_foreground_fd(fileno(stderr)) || done) {
-                       fprintf(stderr, "%s: %u%s%s",
-                               progress->title, n, tp, eol);
+                       fprintf(stderr, "%s: %"PRIuMAX"%s%s",
+                               progress->title, (uintmax_t)n, tp, eol);
                        fflush(stderr);
                }
                progress_update = 0;
@@ -114,7 +115,7 @@ static int display(struct progress *progress, unsigned n, const char *done)
        return 0;
 }
 
-static void throughput_string(struct strbuf *buf, off_t total,
+static void throughput_string(struct strbuf *buf, uint64_t total,
                              unsigned int rate)
 {
        strbuf_reset(buf);
@@ -125,7 +126,7 @@ static void throughput_string(struct strbuf *buf, off_t total,
        strbuf_addstr(buf, "/s");
 }
 
-void display_throughput(struct progress *progress, off_t total)
+void display_throughput(struct progress *progress, uint64_t total)
 {
        struct throughput *tp;
        uint64_t now_ns;
@@ -187,12 +188,12 @@ void display_throughput(struct progress *progress, off_t total)
                display(progress, progress->last_value, NULL);
 }
 
-int display_progress(struct progress *progress, unsigned n)
+int display_progress(struct progress *progress, uint64_t n)
 {
        return progress ? display(progress, n, NULL) : 0;
 }
 
-static struct progress *start_progress_delay(const char *title, unsigned total,
+static struct progress *start_progress_delay(const char *title, uint64_t total,
                                             unsigned delay)
 {
        struct progress *progress = malloc(sizeof(*progress));
@@ -213,12 +214,12 @@ static struct progress *start_progress_delay(const char *title, unsigned total,
        return progress;
 }
 
-struct progress *start_delayed_progress(const char *title, unsigned total)
+struct progress *start_delayed_progress(const char *title, uint64_t total)
 {
        return start_progress_delay(title, total, 2);
 }
 
-struct progress *start_progress(const char *title, unsigned total)
+struct progress *start_progress(const char *title, uint64_t total)
 {
        return start_progress_delay(title, total, 0);
 }
index 6392b63..70a4d4a 100644 (file)
@@ -3,10 +3,10 @@
 
 struct progress;
 
-void display_throughput(struct progress *progress, off_t total);
-int display_progress(struct progress *progress, unsigned n);
-struct progress *start_progress(const char *title, unsigned total);
-struct progress *start_delayed_progress(const char *title, unsigned total);
+void display_throughput(struct progress *progress, uint64_t total);
+int display_progress(struct progress *progress, uint64_t n);
+struct progress *start_progress(const char *title, uint64_t total);
+struct progress *start_delayed_progress(const char *title, uint64_t total);
 void stop_progress(struct progress **progress);
 void stop_progress_msg(struct progress **progress, const char *msg);
 
index 5476120..747bce8 100644 (file)
@@ -4,7 +4,7 @@
 #include "parse-options.h"
 #include "grep.h"
 #include "notes.h"
-#include "commit.h"
+#include "pretty.h"
 #include "diff.h"
 
 /* Remember to update object flag allocation in object.h */
index e90bc31..894d12a 100644 (file)
@@ -449,6 +449,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        o.branch2 = next ? next_label : "(empty tree)";
        if (is_rebase_i(opts))
                o.buffer_output = 2;
+       o.show_rename_progress = 1;
 
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
@@ -463,6 +464,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        if (is_rebase_i(opts) && clean <= 0)
                fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
+       diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
        if (clean < 0)
                return clean;
 
@@ -795,6 +797,13 @@ static const char *command_to_string(const enum todo_command command)
        die("Unknown command: %d", command);
 }
 
+static char command_to_char(const enum todo_command command)
+{
+       if (command < TODO_COMMENT && todo_command_info[command].c)
+               return todo_command_info[command].c;
+       return comment_line_char;
+}
+
 static int is_noop(const enum todo_command command)
 {
        return TODO_NOOP <= command;
@@ -1268,6 +1277,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        bol += padding;
 
        if (item->command == TODO_EXEC) {
+               item->commit = NULL;
                item->arg = bol;
                item->arg_len = (int)(eol - bol);
                return 0;
@@ -2443,14 +2453,16 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
        strbuf_release(&sob);
 }
 
-int sequencer_make_script(int keep_empty, FILE *out,
-               int argc, const char **argv)
+int sequencer_make_script(FILE *out, int argc, const char **argv,
+                         unsigned flags)
 {
        char *format = NULL;
        struct pretty_print_context pp = {0};
        struct strbuf buf = STRBUF_INIT;
        struct rev_info revs;
        struct commit *commit;
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
+       const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
 
        init_revisions(&revs, NULL);
        revs.verbose_header = 1;
@@ -2483,7 +2495,8 @@ int sequencer_make_script(int keep_empty, FILE *out,
                strbuf_reset(&buf);
                if (!keep_empty && is_original_commit_empty(commit))
                        strbuf_addf(&buf, "%c ", comment_line_char);
-               strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
+               strbuf_addf(&buf, "%s %s ", insn,
+                           oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, &buf);
                strbuf_addch(&buf, '\n');
                fputs(buf.buf, out);
@@ -2492,61 +2505,90 @@ int sequencer_make_script(int keep_empty, FILE *out,
        return 0;
 }
 
-
-int transform_todo_ids(int shorten_ids)
+/*
+ * Add commands after pick and (series of) squash/fixup commands
+ * in the todo list.
+ */
+int sequencer_add_exec_commands(const char *commands)
 {
        const char *todo_file = rebase_path_todo();
        struct todo_list todo_list = TODO_LIST_INIT;
-       int fd, res, i;
-       FILE *out;
+       struct todo_item *item;
+       struct strbuf *buf = &todo_list.buf;
+       size_t offset = 0, commands_len = strlen(commands);
+       int i, first;
 
-       strbuf_reset(&todo_list.buf);
-       fd = open(todo_file, O_RDONLY);
-       if (fd < 0)
-               return error_errno(_("could not open '%s'"), todo_file);
-       if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
-               close(fd);
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
                return error(_("could not read '%s'."), todo_file);
-       }
-       close(fd);
 
-       res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
-       if (res) {
+       if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
                return error(_("unusable todo list: '%s'"), todo_file);
        }
 
-       out = fopen(todo_file, "w");
-       if (!out) {
+       first = 1;
+       /* insert <commands> before every pick except the first one */
+       for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
+               if (item->command == TODO_PICK && !first) {
+                       strbuf_insert(buf, item->offset_in_buf + offset,
+                                     commands, commands_len);
+                       offset += commands_len;
+               }
+               first = 0;
+       }
+
+       /* append final <commands> */
+       strbuf_add(buf, commands, commands_len);
+
+       i = write_message(buf->buf, buf->len, todo_file, 0);
+       todo_list_release(&todo_list);
+       return i;
+}
+
+int transform_todos(unsigned flags)
+{
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       struct strbuf buf = STRBUF_INIT;
+       struct todo_item *item;
+       int i;
+
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+               return error(_("could not read '%s'."), todo_file);
+
+       if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) {
                todo_list_release(&todo_list);
-               return error(_("unable to open '%s' for writing"), todo_file);
+               return error(_("unusable todo list: '%s'"), todo_file);
        }
-       for (i = 0; i < todo_list.nr; i++) {
-               struct todo_item *item = todo_list.items + i;
-               int bol = item->offset_in_buf;
-               const char *p = todo_list.buf.buf + bol;
-               int eol = i + 1 < todo_list.nr ?
-                       todo_list.items[i + 1].offset_in_buf :
-                       todo_list.buf.len;
-
-               if (item->command >= TODO_EXEC && item->command != TODO_DROP)
-                       fwrite(p, eol - bol, 1, out);
-               else {
-                       const char *id = shorten_ids ?
-                               short_commit_name(item->commit) :
-                               oid_to_hex(&item->commit->object.oid);
-                       int len;
-
-                       p += strspn(p, " \t"); /* left-trim command */
-                       len = strcspn(p, " \t"); /* length of command */
-
-                       fprintf(out, "%.*s %s %.*s\n",
-                               len, p, id, item->arg_len, item->arg);
+
+       for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
+               /* if the item is not a command write it and continue */
+               if (item->command >= TODO_COMMENT) {
+                       strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg);
+                       continue;
                }
+
+               /* add command to the buffer */
+               if (flags & TODO_LIST_ABBREVIATE_CMDS)
+                       strbuf_addch(&buf, command_to_char(item->command));
+               else
+                       strbuf_addstr(&buf, command_to_string(item->command));
+
+               /* add commit id */
+               if (item->commit) {
+                       const char *oid = flags & TODO_LIST_SHORTEN_IDS ?
+                                         short_commit_name(item->commit) :
+                                         oid_to_hex(&item->commit->object.oid);
+
+                       strbuf_addf(&buf, " %s", oid);
+               }
+               /* add all the rest */
+               strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg);
        }
-       fclose(out);
+
+       i = write_message(buf.buf, buf.len, todo_file, 0);
        todo_list_release(&todo_list);
-       return 0;
+       return i;
 }
 
 enum check_level {
index 6f3d3df..81f6d7d 100644 (file)
@@ -45,10 +45,14 @@ int sequencer_continue(struct replay_opts *opts);
 int sequencer_rollback(struct replay_opts *opts);
 int sequencer_remove_state(struct replay_opts *opts);
 
-int sequencer_make_script(int keep_empty, FILE *out,
-               int argc, const char **argv);
+#define TODO_LIST_KEEP_EMPTY (1U << 0)
+#define TODO_LIST_SHORTEN_IDS (1U << 1)
+#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
+int sequencer_make_script(FILE *out, int argc, const char **argv,
+                         unsigned flags);
 
-int transform_todo_ids(int shorten_ids);
+int sequencer_add_exec_commands(const char *command);
+int transform_todos(unsigned flags);
 int check_todo_list(void);
 int skip_unnecessary_picks(void);
 int rearrange_squash(void);
diff --git a/setup.c b/setup.c
index 50c6b2a..8cc3418 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -926,7 +926,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
         * - ../.git
         * - ../.git/
         * - ../ (bare)
-        * - ../../.git/
+        * - ../../.git
         *   etc.
         */
        one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
index 1e4d684..6d7f943 100644 (file)
 
 int recv_sideband(const char *me, int in_stream, int out)
 {
-       const char *term, *suffix;
+       const char *suffix;
        char buf[LARGE_PACKET_MAX + 1];
        struct strbuf outbuf = STRBUF_INIT;
        int retval = 0;
 
-       term = getenv("TERM");
-       if (isatty(2) && term && strcmp(term, "dumb"))
+       if (isatty(2) && !is_terminal_dumb())
                suffix = ANSI_SUFFIX;
        else
                suffix = DUMB_SUFFIX;
index 323c49c..8007be8 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -11,6 +11,28 @@ int starts_with(const char *str, const char *prefix)
                        return 0;
 }
 
+int skip_to_optional_arg_default(const char *str, const char *prefix,
+                                const char **arg, const char *def)
+{
+       const char *p;
+
+       if (!skip_prefix(str, prefix, &p))
+               return 0;
+
+       if (!*p) {
+               if (arg)
+                       *arg = def;
+               return 1;
+       }
+
+       if (*p != '=')
+               return 0;
+
+       if (arg)
+               *arg = p + 1;
+       return 1;
+}
+
 /*
  * Used as the default ->buf value, so that people can always assume
  * buf is non NULL and ->buf is NUL terminated even for a freshly
@@ -386,12 +408,15 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
 
 ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint)
 {
+       size_t oldalloc = sb->alloc;
        ssize_t cnt;
 
        strbuf_grow(sb, hint ? hint : 8192);
        cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
        if (cnt > 0)
                strbuf_setlen(sb, sb->len + cnt);
+       else if (oldalloc == 0)
+               strbuf_release(sb);
        return cnt;
 }
 
index fa25888..2967704 100644 (file)
@@ -1,3 +1,5 @@
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
+
 #include "cache.h"
 #include "repository.h"
 #include "config.h"
@@ -55,14 +57,15 @@ int is_gitmodules_unmerged(const struct index_state *istate)
  * future version when we learn to stage the changes we do ourselves without
  * staging any previous modifications.
  */
-int is_staging_gitmodules_ok(const struct index_state *istate)
+int is_staging_gitmodules_ok(struct index_state *istate)
 {
        int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 
        if ((pos >= 0) && (pos < istate->cache_nr)) {
                struct stat st;
                if (lstat(GITMODULES_FILE, &st) == 0 &&
-                   ce_match_stat(istate->cache[pos], &st, CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED)
+                   ie_match_stat(istate, istate->cache[pos], &st,
+                                 CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED)
                        return 0;
        }
 
@@ -143,9 +146,9 @@ int remove_path_from_gitmodules(const char *path)
        return 0;
 }
 
-void stage_updated_gitmodules(void)
+void stage_updated_gitmodules(struct index_state *istate)
 {
-       if (add_file_to_cache(GITMODULES_FILE, 0))
+       if (add_file_to_index(istate, GITMODULES_FILE, 0))
                die(_("staging updated .gitmodules failed"));
 }
 
@@ -1178,7 +1181,7 @@ int submodule_touches_in_range(struct object_id *excl_oid,
 struct submodule_parallel_fetch {
        int count;
        struct argv_array args;
-       const char *work_tree;
+       struct repository *r;
        const char *prefix;
        int command_line_option;
        int default_option;
@@ -1199,7 +1202,7 @@ static int get_fetch_recurse_config(const struct submodule *submodule,
 
                int fetch_recurse = submodule->fetch_recurse;
                key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
-               if (!repo_config_get_string_const(the_repository, key, &value)) {
+               if (!repo_config_get_string_const(spf->r, key, &value)) {
                        fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
                }
                free(key);
@@ -1218,11 +1221,11 @@ static int get_next_submodule(struct child_process *cp,
        int ret = 0;
        struct submodule_parallel_fetch *spf = data;
 
-       for (; spf->count < active_nr; spf->count++) {
+       for (; spf->count < spf->r->index->cache_nr; spf->count++) {
                struct strbuf submodule_path = STRBUF_INIT;
                struct strbuf submodule_git_dir = STRBUF_INIT;
                struct strbuf submodule_prefix = STRBUF_INIT;
-               const struct cache_entry *ce = active_cache[spf->count];
+               const struct cache_entry *ce = spf->r->index->cache[spf->count];
                const char *git_dir, *default_argv;
                const struct submodule *submodule;
                struct submodule default_submodule = SUBMODULE_INIT;
@@ -1230,7 +1233,7 @@ static int get_next_submodule(struct child_process *cp,
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
 
-               submodule = submodule_from_path(&null_oid, ce->name);
+               submodule = submodule_from_cache(spf->r, &null_oid, ce->name);
                if (!submodule) {
                        const char *name = default_name_or_path(ce->name);
                        if (name) {
@@ -1256,7 +1259,7 @@ static int get_next_submodule(struct child_process *cp,
                        continue;
                }
 
-               strbuf_addf(&submodule_path, "%s/%s", spf->work_tree, ce->name);
+               strbuf_repo_worktree_path(&submodule_path, spf->r, "%s", ce->name);
                strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
                strbuf_addf(&submodule_prefix, "%s%s/", spf->prefix, ce->name);
                git_dir = read_gitfile(submodule_git_dir.buf);
@@ -1309,7 +1312,8 @@ static int fetch_finish(int retvalue, struct strbuf *err,
        return 0;
 }
 
-int fetch_populated_submodules(const struct argv_array *options,
+int fetch_populated_submodules(struct repository *r,
+                              const struct argv_array *options,
                               const char *prefix, int command_line_option,
                               int default_option,
                               int quiet, int max_parallel_jobs)
@@ -1317,16 +1321,16 @@ int fetch_populated_submodules(const struct argv_array *options,
        int i;
        struct submodule_parallel_fetch spf = SPF_INIT;
 
-       spf.work_tree = get_git_work_tree();
+       spf.r = r;
        spf.command_line_option = command_line_option;
        spf.default_option = default_option;
        spf.quiet = quiet;
        spf.prefix = prefix;
 
-       if (!spf.work_tree)
+       if (!r->worktree)
                goto out;
 
-       if (read_cache() < 0)
+       if (repo_read_index(r) < 0)
                die("index file corrupt");
 
        argv_array_push(&spf.args, "fetch");
index f0da027..b9b7ef0 100644 (file)
@@ -34,10 +34,10 @@ struct submodule_update_strategy {
 #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
 
 extern int is_gitmodules_unmerged(const struct index_state *istate);
-extern int is_staging_gitmodules_ok(const struct index_state *istate);
+extern int is_staging_gitmodules_ok(struct index_state *istate);
 extern int update_path_in_gitmodules(const char *oldpath, const char *newpath);
 extern int remove_path_from_gitmodules(const char *path);
-extern void stage_updated_gitmodules(void);
+extern void stage_updated_gitmodules(struct index_state *istate);
 extern void set_diffopt_flags_from_submodule_config(struct diff_options *,
                const char *path);
 extern int git_default_submodule_config(const char *var, const char *value, void *cb);
@@ -76,10 +76,12 @@ extern int should_update_submodules(void);
  */
 extern const struct submodule *submodule_from_ce(const struct cache_entry *ce);
 extern void check_for_new_submodule_commits(struct object_id *oid);
-extern int fetch_populated_submodules(const struct argv_array *options,
-                              const char *prefix, int command_line_option,
-                              int default_option,
-                              int quiet, int max_parallel_jobs);
+extern int fetch_populated_submodules(struct repository *r,
+                                     const struct argv_array *options,
+                                     const char *prefix,
+                                     int command_line_option,
+                                     int default_option,
+                                     int quiet, int max_parallel_jobs);
 extern unsigned is_submodule_modified(const char *path, int ignore_untracked);
 extern int submodule_uses_gitfile(const char *path);
 
index d02f9b3..2bad28a 100644 (file)
@@ -1,40 +1,5 @@
-/test-chmtime
-/test-ctype
-/test-config
-/test-date
-/test-delta
-/test-drop-caches
-/test-dump-cache-tree
-/test-dump-fsmonitor
-/test-dump-split-index
-/test-dump-untracked-cache
-/test-fake-ssh
-/test-scrap-cache-tree
-/test-genrandom
-/test-hashmap
-/test-index-version
-/test-lazy-init-name-hash
-/test-line-buffer
-/test-match-trees
-/test-mergesort
-/test-mktemp
-/test-online-cpus
-/test-parse-options
-/test-path-utils
-/test-prio-queue
-/test-read-cache
-/test-ref-store
-/test-regex
-/test-revision-walking
-/test-run-command
-/test-sha1
-/test-sha1-array
-/test-sigchain
-/test-strcmp-offset
-/test-string-list
-/test-submodule-config
-/test-subprocess
-/test-svn-fe
-/test-urlmatch-normalization
-/test-wildmatch
-/test-write-cache
+*
+!*.sh
+!*.[ch]
+!*.gitignore
+
diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c
new file mode 100644 (file)
index 0000000..90dc97a
--- /dev/null
@@ -0,0 +1,74 @@
+#include "cache.h"
+#include "object.h"
+#include "decorate.h"
+
+int cmd_main(int argc, const char **argv)
+{
+       struct decoration n;
+       struct object_id one_oid = { {1} };
+       struct object_id two_oid = { {2} };
+       struct object_id three_oid = { {3} };
+       struct object *one, *two, *three;
+
+       int decoration_a, decoration_b;
+
+       void *ret;
+
+       int i, objects_noticed = 0;
+
+       /*
+        * The struct must be zero-initialized.
+        */
+       memset(&n, 0, sizeof(n));
+
+       /*
+        * Add 2 objects, one with a non-NULL decoration and one with a NULL
+        * decoration.
+        */
+       one = lookup_unknown_object(one_oid.hash);
+       two = lookup_unknown_object(two_oid.hash);
+       ret = add_decoration(&n, one, &decoration_a);
+       if (ret)
+               die("BUG: when adding a brand-new object, NULL should be returned");
+       ret = add_decoration(&n, two, NULL);
+       if (ret)
+               die("BUG: when adding a brand-new object, NULL should be returned");
+
+       /*
+        * When re-adding an already existing object, the old decoration is
+        * returned.
+        */
+       ret = add_decoration(&n, one, NULL);
+       if (ret != &decoration_a)
+               die("BUG: when readding an already existing object, existing decoration should be returned");
+       ret = add_decoration(&n, two, &decoration_b);
+       if (ret)
+               die("BUG: when readding an already existing object, existing decoration should be returned");
+
+       /*
+        * Lookup returns the added declarations, or NULL if the object was
+        * never added.
+        */
+       ret = lookup_decoration(&n, one);
+       if (ret)
+               die("BUG: lookup should return added declaration");
+       ret = lookup_decoration(&n, two);
+       if (ret != &decoration_b)
+               die("BUG: lookup should return added declaration");
+       three = lookup_unknown_object(three_oid.hash);
+       ret = lookup_decoration(&n, three);
+       if (ret)
+               die("BUG: lookup for unknown object should return NULL");
+
+       /*
+        * The user can also loop through all entries.
+        */
+       for (i = 0; i < n.size; i++) {
+               if (n.entries[i].base)
+                       objects_noticed++;
+       }
+       if (objects_noticed != 2)
+               die("BUG: should have 2 objects");
+
+       return 0;
+}
index 68108d9..beb5927 100755 (executable)
@@ -43,19 +43,31 @@ create_gitattributes () {
        } >.gitattributes
 }
 
-create_NNO_files () {
+# Create 2 sets of files:
+# The NNO files are "Not NOrmalized in the repo. We use CRLF_mix_LF and store
+#   it under different names for the different test cases, see ${pfx}
+#   Depending on .gitattributes they are normalized at the next commit (or not)
+# The MIX files have different contents in the repo.
+#   Depending on its contents, the "new safer autocrlf" may kick in.
+create_NNO_MIX_files () {
        for crlf in false true input
        do
                for attr in "" auto text -text
                do
                        for aeol in "" lf crlf
                        do
-                               pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf}
+                               pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} &&
                                cp CRLF_mix_LF ${pfx}_LF.txt &&
                                cp CRLF_mix_LF ${pfx}_CRLF.txt &&
                                cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt &&
                                cp CRLF_mix_LF ${pfx}_LF_mix_CR.txt &&
-                               cp CRLF_mix_LF ${pfx}_CRLF_nul.txt
+                               cp CRLF_mix_LF ${pfx}_CRLF_nul.txt &&
+                               pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} &&
+                               cp LF          ${pfx}_LF.txt &&
+                               cp CRLF        ${pfx}_CRLF.txt &&
+                               cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt &&
+                               cp LF_mix_CR   ${pfx}_LF_mix_CR.txt &&
+                               cp CRLF_nul    ${pfx}_CRLF_nul.txt
                        done
                done
        done
@@ -136,6 +148,49 @@ commit_chk_wrnNNO () {
        '
 }
 
+# Commit a file with mixed line endings on top of different files
+# in the index. Check for warnings
+commit_MIX_chkwrn () {
+       attr=$1 ; shift
+       aeol=$1 ; shift
+       crlf=$1 ; shift
+       lfwarn=$1 ; shift
+       crlfwarn=$1 ; shift
+       lfmixcrlf=$1 ; shift
+       lfmixcr=$1 ; shift
+       crlfnul=$1 ; shift
+       pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf}
+       #Commit file with CLRF_mix_LF on top of existing file
+       create_gitattributes "$attr" $aeol &&
+       for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+       do
+               fname=${pfx}_$f.txt &&
+               cp CRLF_mix_LF $fname &&
+               printf Z >>"$fname" &&
+               git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err"
+       done
+
+       test_expect_success "commit file with mixed EOL onto LF crlf=$crlf attr=$attr" '
+               check_warning "$lfwarn" ${pfx}_LF.err
+       '
+       test_expect_success "commit file with mixed EOL onto CLRF attr=$attr aeol=$aeol crlf=$crlf" '
+               check_warning "$crlfwarn" ${pfx}_CRLF.err
+       '
+
+       test_expect_success "commit file with mixed EOL onto CRLF_mix_LF attr=$attr aeol=$aeol crlf=$crlf" '
+               check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err
+       '
+
+       test_expect_success "commit file with mixed EOL onto LF_mix_cr attr=$attr aeol=$aeol crlf=$crlf " '
+               check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err
+       '
+
+       test_expect_success "commit file with mixed EOL onto CRLF_nul attr=$attr aeol=$aeol crlf=$crlf" '
+               check_warning "$crlfnul" ${pfx}_CRLF_nul.err
+       '
+}
+
+
 stats_ascii () {
        case "$1" in
        LF)
@@ -323,8 +378,8 @@ test_expect_success 'setup master' '
        printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\rLINETHREE"   >CRLF_mix_CR &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONEQ\r\nLINETWO\r\nLINETHREE" | q_to_nul >CRLF_nul &&
        printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONEQ\nLINETWO\nLINETHREE" | q_to_nul >LF_nul &&
-       create_NNO_files CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF &&
-       git -c core.autocrlf=false add NNO_*.txt &&
+       create_NNO_MIX_files &&
+       git -c core.autocrlf=false add NNO_*.txt MIX_*.txt &&
        git commit -m "mixed line endings" &&
        test_tick
 '
@@ -385,6 +440,18 @@ test_expect_success 'commit files attr=crlf' '
        commit_check_warn input "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" ""
 '
 
+# Commit "CRLFmixLF" on top of these files already in the repo:
+#                                         mixed     mixed     mixed       mixed       mixed
+#                                         onto      onto      onto        onto        onto
+#                 attr                    LF        CRLF      CRLFmixLF   LF_mix_CR   CRLFNUL
+commit_MIX_chkwrn ""      ""      false   ""        ""        ""          ""          ""
+commit_MIX_chkwrn ""      ""      true    "LF_CRLF" ""        ""          "LF_CRLF"   "LF_CRLF"
+commit_MIX_chkwrn ""      ""      input   "CRLF_LF" ""        ""          "CRLF_LF"   "CRLF_LF"
+
+commit_MIX_chkwrn "auto"  ""      false   "$WAMIX"  ""        ""          "$WAMIX"    "$WAMIX"
+commit_MIX_chkwrn "auto"  ""      true    "LF_CRLF" ""        ""          "LF_CRLF"   "LF_CRLF"
+commit_MIX_chkwrn "auto"  ""      input   "CRLF_LF" ""        ""          "CRLF_LF"   "CRLF_LF"
+
 #                 attr                    LF        CRLF      CRLFmixLF   LF_mix_CR   CRLFNUL
 commit_chk_wrnNNO ""      ""      false   ""        ""        ""          ""          ""
 commit_chk_wrnNNO ""      ""      true    LF_CRLF   ""        ""          ""          ""
index fbb4ee9..bb4f2e0 100755 (executable)
@@ -186,4 +186,127 @@ test_expect_success 'no advice given for explicit detached head state' '
        test_cmp expect.no-advice actual
 '
 
+# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (new format)
+test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not asked to' "
+
+       # The first detach operation is more chatty than the following ones.
+       cat >1st_detach <<-'EOF' &&
+       Note: checking out 'HEAD^'.
+
+       You are in 'detached HEAD' state. You can look around, make experimental
+       changes and commit them, and you can discard any commits you make in this
+       state without impacting any branches by performing another checkout.
+
+       If you want to create a new branch to retain commits you create, you may
+       do so (now or later) by using -b with the checkout command again. Example:
+
+         git checkout -b <new-branch-name>
+
+       HEAD is now at 7c7cd714e262 three
+       EOF
+
+       # The remaining ones just show info about previous and current HEADs.
+       cat >2nd_detach <<-'EOF' &&
+       Previous HEAD position was 7c7cd714e262 three
+       HEAD is now at 139b20d8e6c5 two
+       EOF
+
+       cat >3rd_detach <<-'EOF' &&
+       Previous HEAD position was 139b20d8e6c5 two
+       HEAD is now at d79ce1670bdc one
+       EOF
+
+       reset &&
+       check_not_detached &&
+
+       # Various ways of *not* asking for ellipses
+
+       sane_unset GIT_PRINT_SHA1_ELLIPSIS &&
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 1st_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS="no" git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 2nd_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS= git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 3rd_detach actual &&
+
+       sane_unset GIT_PRINT_SHA1_ELLIPSIS &&
+
+       # We only have four commits, but we can re-use them
+       reset &&
+       check_not_detached &&
+
+       # Make no mention of the env var at all
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 1st_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS='nope' &&
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 2nd_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS=nein &&
+       git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 3rd_detach actual &&
+
+       true
+"
+
+# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (old format)
+test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked to' "
+
+       # The first detach operation is more chatty than the following ones.
+       cat >1st_detach <<-'EOF' &&
+       Note: checking out 'HEAD^'.
+
+       You are in 'detached HEAD' state. You can look around, make experimental
+       changes and commit them, and you can discard any commits you make in this
+       state without impacting any branches by performing another checkout.
+
+       If you want to create a new branch to retain commits you create, you may
+       do so (now or later) by using -b with the checkout command again. Example:
+
+         git checkout -b <new-branch-name>
+
+       HEAD is now at 7c7cd714e262... three
+       EOF
+
+       # The remaining ones just show info about previous and current HEADs.
+       cat >2nd_detach <<-'EOF' &&
+       Previous HEAD position was 7c7cd714e262... three
+       HEAD is now at 139b20d8e6c5... two
+       EOF
+
+       cat >3rd_detach <<-'EOF' &&
+       Previous HEAD position was 139b20d8e6c5... two
+       HEAD is now at d79ce1670bdc... one
+       EOF
+
+       reset &&
+       check_not_detached &&
+
+       # Various ways of asking for ellipses...
+       # The user can just use any kind of quoting (including none).
+
+       GIT_PRINT_SHA1_ELLIPSIS=yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 1st_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS=Yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 2nd_detach actual &&
+
+       GIT_PRINT_SHA1_ELLIPSIS=YES git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
+       check_detached &&
+       test_i18ncmp 3rd_detach actual &&
+
+       true
+"
+
 test_done
index b5c47ac..2b95944 100755 (executable)
@@ -245,6 +245,12 @@ test_expect_success 'local clone from linked checkout' '
        ( cd here-clone && git fsck )
 '
 
+test_expect_success 'local clone --shared from linked checkout' '
+       git -C bare worktree add --detach ../baretree &&
+       git clone --local --shared baretree bare-clone &&
+       grep /bare/ bare-clone/.git/objects/info/alternates
+'
+
 test_expect_success '"add" worktree with --no-checkout' '
        git worktree add --no-checkout -b swamp swamp &&
        ! test -e swamp/init.t &&
@@ -313,5 +319,164 @@ test_expect_success 'checkout a branch under bisect' '
 test_expect_success 'rename a branch under bisect not allowed' '
        test_must_fail git branch -M under-bisect bisect-with-new-name
 '
+# Is branch "refs/heads/$1" set to pull from "$2/$3"?
+test_branch_upstream () {
+       printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
+       {
+               git config "branch.$1.remote" &&
+               git config "branch.$1.merge"
+       } >actual.upstream &&
+       test_cmp expect.upstream actual.upstream
+}
+
+test_expect_success '--track sets up tracking' '
+       test_when_finished rm -rf track &&
+       git worktree add --track -b track track master &&
+       test_branch_upstream track . master
+'
+
+# setup remote repository $1 and repository $2 with $1 set up as
+# remote.  The remote has two branches, master and foo.
+setup_remote_repo () {
+       git init $1 &&
+       (
+               cd $1 &&
+               test_commit $1_master &&
+               git checkout -b foo &&
+               test_commit upstream_foo
+       ) &&
+       git init $2 &&
+       (
+               cd $2 &&
+               test_commit $2_master &&
+               git remote add $1 ../$1 &&
+               git config remote.$1.fetch \
+                       "refs/heads/*:refs/remotes/$1/*" &&
+               git fetch --all
+       )
+}
+
+test_expect_success '--no-track avoids setting up tracking' '
+       test_when_finished rm -rf repo_upstream repo_local foo &&
+       setup_remote_repo repo_upstream repo_local &&
+       (
+               cd repo_local &&
+               git worktree add --no-track -b foo ../foo repo_upstream/foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success '"add" <path> <non-existent-branch> fails' '
+       test_must_fail git worktree add foo non-existent
+'
+
+test_expect_success '"add" <path> <branch> dwims' '
+       test_when_finished rm -rf repo_upstream repo_dwim foo &&
+       setup_remote_repo repo_upstream repo_dwim &&
+       git init repo_dwim &&
+       (
+               cd repo_dwim &&
+               git worktree add ../foo foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_upstream foo &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add does not match remote' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git worktree add ../foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add --guess-remote sets up tracking' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git worktree add --guess-remote ../foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_a foo &&
+               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git config worktree.guessRemote true &&
+               git worktree add ../foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_a foo &&
+               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree --no-guess-remote option overrides config' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git config worktree.guessRemote true &&
+               git worktree add --no-guess-remote ../foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+post_checkout_hook () {
+       test_when_finished "rm -f .git/hooks/post-checkout" &&
+       mkdir -p .git/hooks &&
+       write_script .git/hooks/post-checkout <<-\EOF
+       echo $* >hook.actual
+       EOF
+}
+
+test_expect_success '"add" invokes post-checkout hook (branch)' '
+       post_checkout_hook &&
+       printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
+       git worktree add gumby &&
+       test_cmp hook.expect hook.actual
+'
+
+test_expect_success '"add" invokes post-checkout hook (detached)' '
+       post_checkout_hook &&
+       printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
+       git worktree add --detach grumpy &&
+       test_cmp hook.expect hook.actual
+'
+
+test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
+       post_checkout_hook &&
+       rm -f hook.actual &&
+       git worktree add --no-checkout gloopy &&
+       test_path_is_missing hook.actual
+'
 
 test_done
index 0a4ff6d..b81eb5f 100755 (executable)
@@ -19,7 +19,7 @@ test_expect_success 'setup: create subprojects' '
        git update-index --add sub1 &&
        git add sub2 &&
        git commit -q -m "subprojects added" &&
-       git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
+       GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
        git branch save HEAD &&
        cat >expected <<-\EOF &&
        :000000 160000 00000... A       sub1
index 6a82d1e..481a350 100755 (executable)
@@ -1260,6 +1260,28 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
        test B = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
+test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '
+       rebase_setup_and_clean abbrevcmd &&
+       test_commit "first" file1.txt "first line" first &&
+       test_commit "second" file1.txt "another line" second &&
+       test_commit "fixup! first" file2.txt "first line again" first_fixup &&
+       test_commit "squash! second" file1.txt "another line here" second_squash &&
+       cat >expected <<-EOF &&
+       p $(git rev-list --abbrev-commit -1 first) first
+       f $(git rev-list --abbrev-commit -1 first_fixup) fixup! first
+       x git show HEAD
+       p $(git rev-list --abbrev-commit -1 second) second
+       s $(git rev-list --abbrev-commit -1 second_squash) squash! second
+       x git show HEAD
+       EOF
+       git checkout abbrevcmd &&
+       set_cat_todo_editor &&
+       test_config rebase.abbreviateCommands true &&
+       test_must_fail git rebase -i --exec "git show HEAD" \
+               --autosquash master >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'static check of bad command' '
        rebase_setup_and_clean bad-cmd &&
        set_fake_editor &&
index 0d1fa45..eadf4f6 100755 (executable)
@@ -230,4 +230,19 @@ test_expect_success 'rename pretty print common prefix and suffix overlap' '
        test_i18ngrep " d/f/{ => f}/e " output
 '
 
+test_expect_success 'diff-tree -l0 defaults to a big rename limit, not zero' '
+       test_write_lines line1 line2 line3 >myfile &&
+       git add myfile &&
+       git commit -m x &&
+
+       test_write_lines line1 line2 line4 >myotherfile &&
+       git rm myfile &&
+       git add myotherfile &&
+       git commit -m x &&
+
+       git diff-tree -M -l0 HEAD HEAD^ >actual &&
+       # Verify that a rename from myotherfile to myfile was detected
+       grep "myotherfile.*myfile" actual
+'
+
 test_done
index c515e3e..f10798b 100755 (executable)
@@ -118,20 +118,37 @@ test_expect_success setup '
 EOF
 
 V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g')
-while read cmd
+while read magic cmd
 do
-       case "$cmd" in
-       '' | '#'*) continue ;;
+       case "$magic" in
+       '' | '#'*)
+               continue ;;
+       :*)
+               magic=${magic#:}
+               label="$magic-$cmd"
+               case "$magic" in
+               noellipses) ;;
+               *)
+                       die "bug in t4103: unknown magic $magic" ;;
+               esac ;;
+       *)
+               cmd="$magic $cmd" magic=
+               label="$cmd" ;;
        esac
-       test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g')
+       test=$(echo "$label" | sed -e 's|[/ ][/ ]*|_|g')
        pfx=$(printf "%04d" $test_count)
        expect="$TEST_DIRECTORY/t4013/diff.$test"
        actual="$pfx-diff.$test"
 
-       test_expect_success "git $cmd" '
+       test_expect_success "git $cmd # magic is ${magic:-"(not used)"}" '
                {
-                       echo "\$ git $cmd"
-                       git $cmd |
+                       echo "$ git $cmd"
+                       case "$magic" in
+                       "")
+                               GIT_PRINT_SHA1_ELLIPSIS=yes git $cmd ;;
+                       noellipses)
+                               git $cmd ;;
+                       esac |
                        sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \
                            -e "s/^\\(.*mixed; boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/"
                        echo "\$"
@@ -158,9 +175,12 @@ diff-tree -r --abbrev initial
 diff-tree -r --abbrev=4 initial
 diff-tree --root initial
 diff-tree --root --abbrev initial
+:noellipses diff-tree --root --abbrev initial
 diff-tree --root -r initial
 diff-tree --root -r --abbrev initial
+:noellipses diff-tree --root -r --abbrev initial
 diff-tree --root -r --abbrev=4 initial
+:noellipses diff-tree --root -r --abbrev=4 initial
 diff-tree -p initial
 diff-tree --root -p initial
 diff-tree --patch-with-stat initial
@@ -209,6 +229,7 @@ diff-tree -p master
 diff-tree -p -m master
 diff-tree -c master
 diff-tree -c --abbrev master
+:noellipses diff-tree -c --abbrev master
 diff-tree --cc master
 # stat only should show the diffstat with the first parent
 diff-tree -c --stat master
@@ -255,8 +276,10 @@ rev-list --parents HEAD
 rev-list --children HEAD
 
 whatchanged master
+:noellipses whatchanged master
 whatchanged -p master
 whatchanged --root master
+:noellipses whatchanged --root master
 whatchanged --root -p master
 whatchanged --patch-with-stat master
 whatchanged --root --patch-with-stat master
@@ -266,6 +289,7 @@ whatchanged --root -c --patch-with-stat --summary master
 # improved by Timo's patch
 whatchanged --root --cc --patch-with-stat --summary master
 whatchanged -SF master
+:noellipses whatchanged -SF master
 whatchanged -SF -p master
 
 log --patch-with-stat master -- dir/
@@ -284,6 +308,7 @@ show --stat side
 show --stat --summary side
 show --patch-with-stat side
 show --patch-with-raw side
+:noellipses show --patch-with-raw side
 show --patch-with-stat --summary side
 
 format-patch --stdout initial..side
@@ -311,8 +336,10 @@ diff -r --stat initial..side
 diff initial..side
 diff --patch-with-stat initial..side
 diff --patch-with-raw initial..side
+:noellipses diff --patch-with-raw initial..side
 diff --patch-with-stat -r initial..side
 diff --patch-with-raw -r initial..side
+:noellipses diff --patch-with-raw -r initial..side
 diff --name-status dir2 dir
 diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
@@ -325,10 +352,14 @@ diff --dirstat initial rearrange
 diff --dirstat-by-file initial rearrange
 # No-index --abbrev and --no-abbrev
 diff --raw initial
+:noellipses diff --raw initial
 diff --raw --abbrev=4 initial
+:noellipses diff --raw --abbrev=4 initial
 diff --raw --no-abbrev initial
 diff --no-index --raw dir2 dir
+:noellipses diff --no-index --raw dir2 dir
 diff --no-index --raw --abbrev=4 dir2 dir
+:noellipses diff --no-index --raw --abbrev=4 dir2 dir
 diff --no-index --raw --no-abbrev dir2 dir
 EOF
 
diff --git a/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial
new file mode 100644 (file)
index 0000000..4bdad40
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root --abbrev initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 040000 0000000 da7a33f A       dir
+:000000 100644 0000000 01e79c3 A       file0
+:000000 100644 0000000 01e79c3 A       file2
+$
diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial
new file mode 100644 (file)
index 0000000..26fbfeb
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r --abbrev=4 initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000 35d2 A     dir/sub
+:000000 100644 0000 01e7 A     file0
+:000000 100644 0000 01e7 A     file2
+$
diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial
new file mode 100644 (file)
index 0000000..2ac8561
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r --abbrev initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000000 35d242b A       dir/sub
+:000000 100644 0000000 01e79c3 A       file0
+:000000 100644 0000000 01e79c3 A       file2
+$
diff --git a/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master
new file mode 100644 (file)
index 0000000..bb80f01
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c --abbrev master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+::100644 100644 100644 cead32e 7289e35 992913c MM      dir/sub
+::100644 100644 100644 b414108 f4615da 10a8a9f MM      file0
+$
diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir
new file mode 100644 (file)
index 0000000..41b7baf
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --no-index --raw --abbrev=4 dir2 dir
+:000000 100644 0000 0000 A     dir/sub
+$
diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir
new file mode 100644 (file)
index 0000000..0cf3a3e
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --no-index --raw dir2 dir
+:000000 100644 0000000 0000000 A       dir/sub
+$
diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side
new file mode 100644 (file)
index 0000000..8d1f1e3
--- /dev/null
@@ -0,0 +1,36 @@
+$ git diff --patch-with-raw -r initial..side
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side
new file mode 100644 (file)
index 0000000..50d8aee
--- /dev/null
@@ -0,0 +1,36 @@
+$ git diff --patch-with-raw initial..side
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial b/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial
new file mode 100644 (file)
index 0000000..8ae44d6
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff --raw --abbrev=4 initial
+:100644 100644 35d2 9929 M     dir/sub
+:100644 100644 01e7 10a8 M     file0
+:000000 100644 0000 b1e6 A     file1
+:100644 000000 01e7 0000 D     file2
+$
diff --git a/t/t4013/diff.noellipses-diff_--raw_initial b/t/t4013/diff.noellipses-diff_--raw_initial
new file mode 100644 (file)
index 0000000..0175bfb
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff --raw initial
+:100644 100644 35d242b 992913c M       dir/sub
+:100644 100644 01e79c3 10a8a9f M       file0
+:000000 100644 0000000 b1e6722 A       file1
+:100644 000000 01e79c3 0000000 D       file2
+$
diff --git a/t/t4013/diff.noellipses-show_--patch-with-raw_side b/t/t4013/diff.noellipses-show_--patch-with-raw_side
new file mode 100644 (file)
index 0000000..32fed3d
--- /dev/null
@@ -0,0 +1,42 @@
+$ git show --patch-with-raw side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.noellipses-whatchanged_--root_master b/t/t4013/diff.noellipses-whatchanged_--root_master
new file mode 100644 (file)
index 0000000..c2cfd4e
--- /dev/null
@@ -0,0 +1,42 @@
+$ git whatchanged --root master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40 cead32e M       dir/sub
+:000000 100644 0000000 b1e6722 A       file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+:100644 100644 35d242b 8422d40 M       dir/sub
+:100644 100644 01e79c3 b414108 M       file0
+:100644 000000 01e79c3 0000000 D       file2
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+:000000 100644 0000000 35d242b A       dir/sub
+:000000 100644 0000000 01e79c3 A       file0
+:000000 100644 0000000 01e79c3 A       file2
+$
diff --git a/t/t4013/diff.noellipses-whatchanged_-SF_master b/t/t4013/diff.noellipses-whatchanged_-SF_master
new file mode 100644 (file)
index 0000000..b36ce58
--- /dev/null
@@ -0,0 +1,9 @@
+$ git whatchanged -SF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40 cead32e M       dir/sub
+$
diff --git a/t/t4013/diff.noellipses-whatchanged_master b/t/t4013/diff.noellipses-whatchanged_master
new file mode 100644 (file)
index 0000000..55e500f
--- /dev/null
@@ -0,0 +1,32 @@
+$ git whatchanged master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b 7289e35 M       dir/sub
+:100644 100644 01e79c3 f4615da M       file0
+:000000 100644 0000000 7289e35 A       file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40 cead32e M       dir/sub
+:000000 100644 0000000 b1e6722 A       file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+:100644 100644 35d242b 8422d40 M       dir/sub
+:100644 100644 01e79c3 b414108 M       file0
+:100644 000000 01e79c3 0000000 D       file2
+$
index 559a754..17df491 100755 (executable)
@@ -636,6 +636,23 @@ test_expect_success 'check with space before tab in indent (diff-tree)' '
        test_must_fail git diff-tree --check HEAD^ HEAD
 '
 
+test_expect_success 'check with ignored trailing whitespace attr (diff-tree)' '
+       test_when_finished "git reset --hard HEAD^" &&
+
+       # create a whitespace error that should be ignored
+       echo "* -whitespace" >.gitattributes &&
+       git add .gitattributes &&
+       echo "foo(); " >x &&
+       git add x &&
+       git commit -m "add trailing space" &&
+
+       # with a worktree diff-tree ignores the whitespace error
+       git diff-tree --root --check HEAD &&
+
+       # without a worktree diff-tree still ignores the whitespace error
+       git -C .git diff-tree --root --check HEAD
+'
+
 test_expect_success 'check trailing whitespace (trailing-space: off)' '
        git config core.whitespace "-trailing-space" &&
        echo "foo ();   " >x &&
index 3950f50..6471a68 100755 (executable)
@@ -12,62 +12,76 @@ test_expect_success 'setup' '
        git commit -m one
 '
 
-check_diff() {
-expect=$1; shift
-cat >expected <<EOF
-diff --git a/$expect b/$expect
-new file mode 100644
-index 0000000..25c05ef
---- /dev/null
-+++ b/$expect
-@@ -0,0 +1 @@
-+other content
-EOF
-test_expect_success "-p $*" "
-       git diff -p $* HEAD^ >actual &&
-       test_cmp expected actual
-"
+check_diff () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+       diff --git a/$expect b/$expect
+       new file mode 100644
+       index 0000000..25c05ef
+       --- /dev/null
+       +++ b/$expect
+       @@ -0,0 +1 @@
+       +other content
+       EOF
+       test_expect_success "-p $*" "
+               git -C '$dir' diff -p $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
 }
 
-check_numstat() {
-expect=$1; shift
-cat >expected <<EOF
-1      0       $expect
-EOF
-test_expect_success "--numstat $*" "
-       echo '1 0       $expect' >expected &&
-       git diff --numstat $* HEAD^ >actual &&
-       test_cmp expected actual
-"
+check_numstat () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+       1       0       $expect
+       EOF
+       test_expect_success "--numstat $*" "
+               echo '1 0       $expect' >expected &&
+               git -C '$dir' diff --numstat $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
 }
 
-check_stat() {
-expect=$1; shift
-cat >expected <<EOF
- $expect | 1 +
- 1 file changed, 1 insertion(+)
-EOF
-test_expect_success "--stat $*" "
-       git diff --stat $* HEAD^ >actual &&
-       test_i18ncmp expected actual
-"
+check_stat () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+        $expect | 1 +
+        1 file changed, 1 insertion(+)
+       EOF
+       test_expect_success "--stat $*" "
+               git -C '$dir' diff --stat $* HEAD^ >actual &&
+               test_i18ncmp expected actual
+       "
 }
 
-check_raw() {
-expect=$1; shift
-cat >expected <<EOF
-:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A     $expect
-EOF
-test_expect_success "--raw $*" "
-       git diff --no-abbrev --raw $* HEAD^ >actual &&
-       test_cmp expected actual
-"
+check_raw () {
+       dir=$1
+       shift
+       expect=$1
+       shift
+       cat >expected <<-EOF
+       :000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A      $expect
+       EOF
+       test_expect_success "--raw $*" "
+               git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual &&
+               test_cmp expected actual
+       "
 }
 
-for type in diff numstat stat raw; do
-       check_$type file2 --relative=subdir/
-       check_$type file2 --relative=subdir
-       check_$type dir/file2 --relative=sub
+for type in diff numstat stat raw
+do
+       check_$type . file2 --relative=subdir/
+       check_$type . file2 --relative=subdir
+       check_$type subdir file2 --relative
+       check_$type . dir/file2 --relative=sub
 done
 
 test_done
diff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh
new file mode 100755 (executable)
index 0000000..b3f510f
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+test_description='anchored diff algorithm'
+
+. ./test-lib.sh
+
+test_expect_success '--anchored' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       # normally, c is moved to produce the smallest diff
+       test_expect_code 1 git diff --no-index pre post >diff &&
+       grep "^+c" diff &&
+
+       # with anchor, a is moved
+       test_expect_code 1 git diff --no-index --anchored=c pre post >diff &&
+       grep "^+a" diff
+'
+
+test_expect_success '--anchored multiple' '
+       printf "a\nb\nc\nd\ne\nf\n" >pre &&
+       printf "c\na\nb\nf\nd\ne\n" >post &&
+
+       # with 1 anchor, c is not moved, but f is moved
+       test_expect_code 1 git diff --no-index --anchored=c pre post >diff &&
+       grep "^+a" diff && # a is moved instead of c
+       grep "^+f" diff &&
+
+       # with 2 anchors, c and f are not moved
+       test_expect_code 1 git diff --no-index --anchored=c --anchored=f pre post >diff &&
+       grep "^+a" diff &&
+       grep "^+d" diff # d is moved instead of f
+'
+
+test_expect_success '--anchored with nonexistent line has no effect' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       test_expect_code 1 git diff --no-index --anchored=x pre post >diff &&
+       grep "^+c" diff
+'
+
+test_expect_success '--anchored with non-unique line has no effect' '
+       printf "a\nb\nc\nd\ne\nc\n" >pre &&
+       printf "c\na\nb\nc\nd\ne\n" >post &&
+
+       test_expect_code 1 git diff --no-index --anchored=c pre post >diff &&
+       grep "^+c" diff
+'
+
+test_expect_success 'diff still produced with impossible multiple --anchored' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       test_expect_code 1 git diff --no-index --anchored=a --anchored=c pre post >diff &&
+       mv post expected_post &&
+
+       # Ensure that the diff is correct by applying it and then
+       # comparing the result with the original
+       git apply diff &&
+       diff expected_post post
+'
+
+test_expect_success 'later algorithm arguments override earlier ones' '
+       printf "a\nb\nc\n" >pre &&
+       printf "c\na\nb\n" >post &&
+
+       test_expect_code 1 git diff --no-index --patience --anchored=c pre post >diff &&
+       grep "^+a" diff &&
+
+       test_expect_code 1 git diff --no-index --anchored=c --patience pre post >diff &&
+       grep "^+c" diff &&
+
+       test_expect_code 1 git diff --no-index --histogram --anchored=c pre post >diff &&
+       grep "^+a" diff &&
+
+       test_expect_code 1 git diff --no-index --anchored=c --histogram pre post >diff &&
+       grep "^+c" diff
+'
+
+test_expect_success '--anchored works with other commands like "git show"' '
+       printf "a\nb\nc\n" >file &&
+       git add file &&
+       git commit -m foo &&
+       printf "c\na\nb\n" >file &&
+       git add file &&
+       git commit -m foo &&
+
+       # with anchor, a is moved
+       git show --patience --anchored=c >diff &&
+       grep "^+a" diff
+'
+
+test_done
index 935df6a..a1705f7 100755 (executable)
@@ -93,4 +93,23 @@ test_expect_success 'command line pathspec parsing for "git log"' '
        git log --merge -- a
 '
 
+test_expect_success 'tree_entry_interesting does not match past submodule boundaries' '
+       test_when_finished "rm -rf repo submodule" &&
+       git init submodule &&
+       test_commit -C submodule initial &&
+       git init repo &&
+       >"repo/[bracket]" &&
+       git -C repo add "[bracket]" &&
+       test_tick &&
+       git -C repo commit -m bracket &&
+       git -C repo rev-list HEAD -- "[bracket]" >expect &&
+
+       git -C repo submodule add ../submodule &&
+       test_tick &&
+       git -C repo commit -m submodule &&
+
+       git -C repo rev-list HEAD -- "[bracket]" >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh
new file mode 100755 (executable)
index 0000000..1b0acc3
--- /dev/null
@@ -0,0 +1,375 @@
+#!/bin/sh
+
+test_description='git pack-objects using object filtering'
+
+. ./test-lib.sh
+
+# Test blob:none filter.
+
+test_expect_success 'setup r1' '
+       echo "{print \$1}" >print_1.awk &&
+       echo "{print \$2}" >print_2.awk &&
+
+       git init r1 &&
+       for n in 1 2 3 4 5
+       do
+               echo "This is file: $n" > r1/file.$n
+               git -C r1 add file.$n
+               git -C r1 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r1 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r1 index-pack ../all.pack &&
+       git -C r1 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:none packfile has no blobs' '
+       git -C r1 pack-objects --rev --stdout --filter=blob:none >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r1 index-pack ../filter.pack &&
+       git -C r1 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       nr=$(wc -l <observed) &&
+       test 0 -eq $nr
+'
+
+test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
+       git -C r1 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r1 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test blob:limit=<n>[kmg] filter.
+# We boundary test around the size parameter.  The filter is strictly less than
+# the value, so size 500 and 1000 should have the same results, but 1001 should
+# filter more.
+
+test_expect_success 'setup r2' '
+       git init r2 &&
+       for n in 1000 10000
+       do
+               printf "%"$n"s" X > r2/large.$n
+               git -C r2 add large.$n
+               git -C r2 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../all.pack &&
+       git -C r2 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=500 omits all blobs' '
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=500 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       nr=$(wc -l <observed) &&
+       test 0 -eq $nr
+'
+
+test_expect_success 'verify blob:limit=1000' '
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1000 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       nr=$(wc -l <observed) &&
+       test 0 -eq $nr
+'
+
+test_expect_success 'verify blob:limit=1001' '
+       git -C r2 ls-files -s large.1000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1001 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=10001' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=10001 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1k' '
+       git -C r2 ls-files -s large.1000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1m' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 pack-objects --rev --stdout --filter=blob:limit=1m >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r2 index-pack ../filter.pack &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' '
+       git -C r2 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r2 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:path=<path> filter.
+# Use a local file containing a sparse-checkout specification to filter
+# out blobs not required for the corresponding sparse-checkout.  We do not
+# require sparse-checkout to actually be enabled.
+
+test_expect_success 'setup r3' '
+       git init r3 &&
+       mkdir r3/dir1 &&
+       for n in sparse1 sparse2
+       do
+               echo "This is file: $n" > r3/$n
+               git -C r3 add $n
+               echo "This is file: dir1/$n" > r3/dir1/$n
+               git -C r3 add dir1/$n
+       done &&
+       git -C r3 commit -m "sparse" &&
+       echo dir1/ >pattern1 &&
+       echo sparse1 >pattern2
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r3 index-pack ../all.pack &&
+       git -C r3 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:path=pattern1' '
+       git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern1 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r3 index-pack ../filter.pack &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify normal and sparse:path=pattern1 packfiles have same commits/trees' '
+       git -C r3 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:path=pattern2' '
+       git -C r3 ls-files -s sparse1 dir1/sparse1 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern2 >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r3 index-pack ../filter.pack &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify normal and sparse:path=pattern2 packfiles have same commits/trees' '
+       git -C r3 verify-pack -v ../all.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r3 verify-pack -v ../filter.pack \
+               | grep -E "commit|tree" \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:oid=<oid-ish> filter.
+# Like sparse:path, but we get the sparse-checkout specification from
+# a blob rather than a file on disk.
+
+test_expect_success 'setup r4' '
+       git init r4 &&
+       mkdir r4/dir1 &&
+       for n in sparse1 sparse2
+       do
+               echo "This is file: $n" > r4/$n
+               git -C r4 add $n
+               echo "This is file: dir1/$n" > r4/dir1/$n
+               git -C r4 add dir1/$n
+       done &&
+       echo dir1/ >r4/pattern &&
+       git -C r4 add pattern &&
+       git -C r4 commit -m "pattern"
+'
+
+test_expect_success 'verify blob count in normal packfile' '
+       git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r4 pack-objects --rev --stdout >all.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r4 index-pack ../all.pack &&
+       git -C r4 verify-pack -v ../all.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:oid=OID' '
+       git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) &&
+       git -C r4 pack-objects --rev --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r4 index-pack ../filter.pack &&
+       git -C r4 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:oid=oid-ish' '
+       git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r4 pack-objects --rev --stdout --filter=sparse:oid=master:pattern >filter.pack <<-EOF &&
+       HEAD
+       EOF
+       git -C r4 index-pack ../filter.pack &&
+       git -C r4 verify-pack -v ../filter.pack \
+               | grep blob \
+               | awk -f print_1.awk \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Delete some loose objects and use pack-objects, but WITHOUT any filtering.
+# This models previously omitted objects that we did not receive.
+
+test_expect_success 'setup r1 - delete loose blobs' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       for id in `cat expected | sed "s|..|&/|"`
+       do
+               rm r1/.git/objects/$id
+       done
+'
+
+test_expect_success 'verify pack-objects fails w/ missing objects' '
+       test_must_fail git -C r1 pack-objects --rev --stdout >miss.pack <<-EOF
+       HEAD
+       EOF
+'
+
+test_expect_success 'verify pack-objects fails w/ --missing=error' '
+       test_must_fail git -C r1 pack-objects --rev --stdout --missing=error >miss.pack <<-EOF
+       HEAD
+       EOF
+'
+
+test_expect_success 'verify pack-objects w/ --missing=allow-any' '
+       git -C r1 pack-objects --rev --stdout --missing=allow-any >miss.pack <<-EOF
+       HEAD
+       EOF
+'
+
+test_done
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh
new file mode 100755 (executable)
index 0000000..0a37dd5
--- /dev/null
@@ -0,0 +1,225 @@
+#!/bin/sh
+
+test_description='git rev-list using object filtering'
+
+. ./test-lib.sh
+
+# Test the blob:none filter.
+
+test_expect_success 'setup r1' '
+       echo "{print \$1}" >print_1.awk &&
+       echo "{print \$2}" >print_2.awk &&
+
+       git init r1 &&
+       for n in 1 2 3 4 5
+       do
+               echo "This is file: $n" > r1/file.$n
+               git -C r1 add file.$n
+               git -C r1 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob:none omits all 5 blobs' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r1 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:none \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify emitted+omitted == all' '
+       git -C r1 rev-list HEAD --objects \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r1 rev-list HEAD --objects --filter-print-omitted --filter=blob:none \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+
+# Test blob:limit=<n>[kmg] filter.
+# We boundary test around the size parameter.  The filter is strictly less than
+# the value, so size 500 and 1000 should have the same results, but 1001 should
+# filter more.
+
+test_expect_success 'setup r2' '
+       git init r2 &&
+       for n in 1000 10000
+       do
+               printf "%"$n"s" X > r2/large.$n
+               git -C r2 add large.$n
+               git -C r2 commit -m "$n"
+       done
+'
+
+test_expect_success 'verify blob:limit=500 omits all blobs' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=500 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify emitted+omitted == all' '
+       git -C r2 rev-list HEAD --objects \
+               | awk -f print_1.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --objects --filter-print-omitted --filter=blob:limit=500 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1000' '
+       git -C r2 ls-files -s large.1000 large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1000 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1001' '
+       git -C r2 ls-files -s large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1001 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1k' '
+       git -C r2 ls-files -s large.10000 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1k \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify blob:limit=1m' '
+       cat </dev/null >expected &&
+       git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:path=<path> filter.
+# Use a local file containing a sparse-checkout specification to filter
+# out blobs not required for the corresponding sparse-checkout.  We do not
+# require sparse-checkout to actually be enabled.
+
+test_expect_success 'setup r3' '
+       git init r3 &&
+       mkdir r3/dir1 &&
+       for n in sparse1 sparse2
+       do
+               echo "This is file: $n" > r3/$n
+               git -C r3 add $n
+               echo "This is file: dir1/$n" > r3/dir1/$n
+               git -C r3 add dir1/$n
+       done &&
+       git -C r3 commit -m "sparse" &&
+       echo dir1/ >pattern1 &&
+       echo sparse1 >pattern2
+'
+
+test_expect_success 'verify sparse:path=pattern1 omits top-level files' '
+       git -C r3 ls-files -s sparse1 sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern1 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:path=pattern2 omits both sparse2 files' '
+       git -C r3 ls-files -s sparse2 dir1/sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern2 \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Test sparse:oid=<oid-ish> filter.
+# Like sparse:path, but we get the sparse-checkout specification from
+# a blob rather than a file on disk.
+
+test_expect_success 'setup r3 part 2' '
+       echo dir1/ >r3/pattern &&
+       git -C r3 add pattern &&
+       git -C r3 commit -m "pattern"
+'
+
+test_expect_success 'verify sparse:oid=OID omits top-level files' '
+       git -C r3 ls-files -s pattern sparse1 sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       oid=$(git -C r3 ls-files -s pattern | awk -f print_2.awk) &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=$oid \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'verify sparse:oid=oid-ish omits top-level files' '
+       git -C r3 ls-files -s pattern sparse1 sparse2 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=master:pattern \
+               | awk -f print_1.awk \
+               | sed "s/~//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+# Delete some loose objects and use rev-list, but WITHOUT any filtering.
+# This models previously omitted objects that we did not receive.
+
+test_expect_success 'rev-list W/ --missing=print' '
+       git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \
+               | awk -f print_2.awk \
+               | sort >expected &&
+       for id in `cat expected | sed "s|..|&/|"`
+       do
+               rm r1/.git/objects/$id
+       done &&
+       git -C r1 rev-list --quiet HEAD --missing=print --objects \
+               | awk -f print_1.awk \
+               | sed "s/?//" \
+               | sort >observed &&
+       test_cmp observed expected
+'
+
+test_expect_success 'rev-list W/O --missing fails' '
+       test_must_fail git -C r1 rev-list --quiet --objects HEAD
+'
+
+test_expect_success 'rev-list W/ missing=allow-any' '
+       git -C r1 rev-list --quiet --missing=allow-any --objects HEAD
+'
+
+test_done
diff --git a/t/t9004-example.sh b/t/t9004-example.sh
new file mode 100755 (executable)
index 0000000..b28a028
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='check that example code compiles and runs'
+. ./test-lib.sh
+
+test_expect_success 'decorate' '
+       test-example-decorate
+'
+
+test_done
index d47560b..e4d06ac 100755 (executable)
@@ -876,7 +876,7 @@ test_expect_success 'L: verify internal tree sorting' '
        EXPECT_END
 
        git fast-import <input &&
-       git diff-tree --abbrev --raw L^ L >output &&
+       GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev --raw L^ L >output &&
        test_cmp expect output
 '
 
diff --git a/trace.c b/trace.c
index cb1293e..b7530b5 100644 (file)
--- a/trace.c
+++ b/trace.c
 #include "cache.h"
 #include "quote.h"
 
-/*
- * "Normalize" a key argument by converting NULL to our trace_default,
- * and otherwise passing through the value. All caller-facing functions
- * should normalize their inputs in this way, though most get it
- * for free by calling get_trace_fd() (directly or indirectly).
- */
-static void normalize_trace_key(struct trace_key **key)
-{
-       static struct trace_key trace_default = { "GIT_TRACE" };
-       if (!*key)
-               *key = &trace_default;
-}
+struct trace_key trace_default_key = { "GIT_TRACE", 0, 0, 0 };
+struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
 
 /* Get a trace file descriptor from "key" env variable. */
 static int get_trace_fd(struct trace_key *key)
 {
        const char *trace;
 
-       normalize_trace_key(&key);
-
        /* don't open twice */
        if (key->initialized)
                return key->fd;
@@ -81,8 +69,6 @@ static int get_trace_fd(struct trace_key *key)
 
 void trace_disable(struct trace_key *key)
 {
-       normalize_trace_key(&key);
-
        if (key->need_close)
                close(key->fd);
        key->fd = 0;
@@ -128,7 +114,6 @@ static int prepare_trace_line(const char *file, int line,
 static void trace_write(struct trace_key *key, const void *buf, unsigned len)
 {
        if (write_in_full(get_trace_fd(key), buf, len) < 0) {
-               normalize_trace_key(&key);
                warning("unable to write trace for %s: %s",
                        key->key, strerror(errno));
                trace_disable(key);
@@ -167,13 +152,13 @@ static void trace_argv_vprintf_fl(const char *file, int line,
 {
        struct strbuf buf = STRBUF_INIT;
 
-       if (!prepare_trace_line(file, line, NULL, &buf))
+       if (!prepare_trace_line(file, line, &trace_default_key, &buf))
                return;
 
        strbuf_vaddf(&buf, format, ap);
 
        sq_quote_argv(&buf, argv, 0);
-       print_trace_line(NULL, &buf);
+       print_trace_line(&trace_default_key, &buf);
 }
 
 void trace_strbuf_fl(const char *file, int line, struct trace_key *key,
@@ -188,8 +173,6 @@ void trace_strbuf_fl(const char *file, int line, struct trace_key *key,
        print_trace_line(key, &buf);
 }
 
-static struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
-
 static void trace_performance_vprintf_fl(const char *file, int line,
                                         uint64_t nanos, const char *format,
                                         va_list ap)
@@ -215,7 +198,7 @@ void trace_printf(const char *format, ...)
 {
        va_list ap;
        va_start(ap, format);