Merge branch 'jc/maint-undefined-i18n-observation-test'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Mar 2012 19:36:09 +0000 (12:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Mar 2012 19:36:09 +0000 (12:36 -0700)
It was unclear what a test in t0204 wanted to check; it turns out
that it was only to observe an undefined behaviour of the system,
and did not anticipate one kind of reasonable error behaviour.

* jc/maint-undefined-i18n-observation-test:
  t0204: clarify the "observe undefined behaviour" test

194 files changed:
.gitignore
Documentation/RelNotes/1.7.10.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.9.4.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-config.txt
Documentation/diff-options.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-config.txt
Documentation/git-fsck.txt
Documentation/git-merge.txt
Documentation/git-p4.txt
Documentation/git-push.txt
Documentation/git-repack.txt
Documentation/git-rerere.txt
Documentation/git-send-email.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/merge-options.txt
Documentation/technical/api-config.txt [new file with mode: 0644]
Documentation/technical/api-parse-options.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
advice.c
advice.h
builtin/blame.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/diff.c
builtin/fast-export.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fsck.c
builtin/grep.c
builtin/index-pack.c
builtin/log.c
builtin/mailinfo.c
builtin/merge.c
builtin/pack-objects.c
builtin/prune-packed.c
builtin/prune.c
builtin/push.c
builtin/remote.c
builtin/revert.c
builtin/symbolic-ref.c
builtin/tag.c
builtin/verify-tag.c
bundle.c
cache-tree.c
cache-tree.h
cache.h
commit.c
config.c
contrib/completion/git-completion.bash
contrib/diff-highlight/README
contrib/diff-highlight/diff-highlight
contrib/diffall/README [new file with mode: 0644]
contrib/diffall/git-diffall [new file with mode: 0755]
contrib/fast-import/git-p4
contrib/svn-fe/svn-fe.txt
convert.c
ctype.c
daemon.c
diff.c
diff.h
diffcore-pickaxe.c
git-am.sh
git-compat-util.h
git-parse-remote.sh
git-pull.sh
git-rebase.sh
git-sh-i18n.sh
git-submodule.sh
git-svn.perl
gitweb/gitweb.perl
gitweb/static/gitweb.css
grep.c
http.c
log-tree.c
merge-recursive.c
mergetools/deltawalker [new file with mode: 0644]
object.c
pack-refs.c
pager.c
parse-options.c
parse-options.h
po/.gitignore
po/README
po/TEAMS [new file with mode: 0644]
po/git.pot [new file with mode: 0644]
po/sv.po [new file with mode: 0644]
po/zh_CN.po [new file with mode: 0644]
refs.c
refs.h
remote.c
remote.h
revision.c
sequencer.c
sequencer.h
symlinks.c
t/Makefile
t/README
t/lib-git-daemon.sh [new file with mode: 0644]
t/perf/.gitignore [new file with mode: 0644]
t/perf/Makefile [new file with mode: 0644]
t/perf/README [new file with mode: 0644]
t/perf/aggregate.perl [new file with mode: 0755]
t/perf/min_time.perl [new file with mode: 0755]
t/perf/p0000-perf-lib-sanity.sh [new file with mode: 0755]
t/perf/p0001-rev-list.sh [new file with mode: 0755]
t/perf/p4000-diff-algorithms.sh [new file with mode: 0755]
t/perf/p7810-grep.sh [new file with mode: 0755]
t/perf/perf-lib.sh [new file with mode: 0644]
t/perf/run [new file with mode: 0755]
t/t0000-basic.sh
t/t0021-conversion.sh
t/t0040-parse-options.sh
t/t0080-vcs-svn.sh [deleted file]
t/t0300-credentials.sh
t/t1300-repo-config.sh
t/t1305-config-include.sh [new file with mode: 0755]
t/t1450-fsck.sh
t/t3400-rebase.sh
t/t3507-cherry-pick-conflict.sh
t/t4011-diff-symlink.sh
t/t4015-diff-whitespace.sh
t/t4052-stat-output.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4209-log-pickaxe.sh [new file with mode: 0755]
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5516-fetch-push.sh
t/t5570-git-daemon.sh [new file with mode: 0755]
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t5704-bundle.sh
t/t5706-clone-branch.sh
t/t7004-tag.sh
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7810-grep.sh
t/t9010-svn-fe.sh
t/t9011-svn-da.sh [new file with mode: 0755]
t/t9100-git-svn-basic.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9801-git-p4-branch.sh
t/t9803-git-p4-shell-metachars.sh
t/t9804-git-p4-label.sh [new file with mode: 0755]
t/t9806-git-p4-options.sh
t/t9809-git-p4-client-view.sh
t/t9810-git-p4-rcs.sh [new file with mode: 0755]
t/test-lib-functions.sh [new file with mode: 0644]
t/test-lib.sh
tag.c
tag.h
test-dump-cache-tree.c
test-obj-pool.c [deleted file]
test-parse-options.c
test-string-pool.c [deleted file]
test-svn-fe.c
test-treap.c [deleted file]
transport.c
transport.h
upload-pack.c
userdiff.c
vcs-svn/LICENSE
vcs-svn/fast_export.c
vcs-svn/fast_export.h
vcs-svn/line_buffer.c
vcs-svn/line_buffer.h
vcs-svn/obj_pool.h [deleted file]
vcs-svn/repo_tree.c
vcs-svn/repo_tree.h
vcs-svn/sliding_window.c [new file with mode: 0644]
vcs-svn/sliding_window.h [new file with mode: 0644]
vcs-svn/string_pool.c [deleted file]
vcs-svn/string_pool.h [deleted file]
vcs-svn/string_pool.txt [deleted file]
vcs-svn/svndiff.c [new file with mode: 0644]
vcs-svn/svndiff.h [new file with mode: 0644]
vcs-svn/svndump.c
vcs-svn/trp.h [deleted file]
vcs-svn/trp.txt [deleted file]
xdiff/xemit.c

index 3b7680e..87fcc5f 100644 (file)
 /test-line-buffer
 /test-match-trees
 /test-mktemp
-/test-obj-pool
 /test-parse-options
 /test-path-utils
 /test-run-command
 /test-sha1
 /test-sigchain
-/test-string-pool
 /test-subprocess
 /test-svn-fe
-/test-treap
 /common-cmds.h
 *.tar.gz
 *.dsc
diff --git a/Documentation/RelNotes/1.7.10.txt b/Documentation/RelNotes/1.7.10.txt
new file mode 100644 (file)
index 0000000..6286485
--- /dev/null
@@ -0,0 +1,197 @@
+Git v1.7.10 Release Notes
+=========================
+
+Compatibility Notes
+-------------------
+
+ * From this release on, the "git merge" command in an interactive
+   session will start an editor when it automatically resolves the
+   merge for the user to explain the resulting commit, just like the
+   "git commit" command does when it wasn't given a commit message.
+
+   If you have a script that runs "git merge" and keeps its standard
+   input and output attached to the user's terminal, and if you do not
+   want the user to explain the resulting merge commits, you can
+   export GIT_MERGE_AUTOEDIT environment variable set to "no", like
+   this:
+
+       #!/bin/sh
+       GIT_MERGE_AUTOEDIT=no
+       export GIT_MERGE_AUTOEDIT
+
+   to disable this behaviour (if you want your users to explain their
+   merge commits, you do not have to do anything).  Alternatively, you
+   can give the "--no-edit" option to individual invocations of the
+   "git merge" command if you know everybody who uses your script has
+   Git v1.7.8 or newer.
+
+ * The "--binary/-b" options to "git am" have been a no-op for quite a
+   while and were deprecated in mid 2008 (v1.6.0).  When you give these
+   options to "git am", it will now warn and ask you not to use them.
+
+
+Updates since v1.7.9
+--------------------
+
+UI, Workflows & Features
+
+ * Teams for localizing the messages from the Porcelain layer of
+   commands are starting to form, thanks to Jiang Xin who volunteered
+   to be the localization coordinator.  An initial set of translated
+   messages for simplified chinese is available.
+
+ * The configuration mechanism learned an "include" facility; an
+   assignment to the include.path pseudo-variable causes the named
+   file to be included in-place when Git looks up configuration
+   variables.
+
+ * A content filter (clean/smudge) used to be just a way to make the
+   recorded contents "more useful", and allowed to fail; a filter can
+   now optionally be marked as "required".
+
+ * Options whose names begin with "--no-" (e.g. the "--no-verify"
+   option of the "git commit" command) can be negated by omitting
+   "no-" from its name, e.g. "git commit --verify".
+
+ * "git am" learned to pass "-b" option to underlying "git mailinfo", so
+   that a bracketed string other than "PATCH" at the beginning can be kept.
+
+ * "git clone" learned "--single-branch" option to limit cloning to a
+   single branch (surprise!); tags that do not point into the history
+   of the branch are not fetched.
+
+ * "git clone" learned to detach the HEAD in the resulting repository
+   when the user specifies a tag with "--branch" (e.g., "--branch=v1.0").
+   Clone also learned to print the usual "detached HEAD" advice in such
+   a case, similar to "git checkout v1.0".
+
+ * When showing a patch while ignoring whitespace changes, the context
+   lines are taken from the postimage, in order to make it easier to
+   view the output.
+
+ * "git diff --stat" learned to adjust the width of the output on
+   wider terminals, and give more columns to pathnames as needed.
+
+ * "diff-highlight" filter (in contrib/) was updated to produce more
+   aesthetically pleasing output.
+
+ * "fsck" learned "--no-dangling" option to omit dangling object
+   information.
+
+ * "git log -G" and "git log -S" learned to pay attention to the "-i"
+   option.  With "-i", "log -G" ignores the case when finding patch
+   hunks that introduce or remove a string that matches the given
+   pattern.  Similarly with "-i", "log -S" ignores the case when
+   finding the commit the given block of text appears or disappears
+   from the file.
+
+ * "git merge" in an interactive session learned to spawn the editor
+   by default to let the user edit the auto-generated merge message,
+   to encourage people to explain their merges better. Legacy scripts
+   can export GIT_MERGE_AUTOEDIT=no to retain the historical behavior.
+   Both "git merge" and "git pull" can be given --no-edit from the
+   command line to accept the auto-generated merge message.
+
+ * The advice message given when the user didn't give enough clue on
+   what to merge to "git pull" and "git merge" has been updated to
+   be more concise and easier to understand.
+
+ * "git push" learned the "--prune" option, similar to "git fetch".
+
+ * The whole directory that houses a top-level superproject managed by
+   "git submodule" can be moved to another place.
+
+ * "git symbolic-ref" learned the "--short" option to abbreviate the
+   refname it shows unambiguously.
+
+ * "git tag --list" can be given "--points-at <object>" to limit its
+   output to those that point at the given object.
+
+ * "gitweb" allows intermediate entries in the directory hierarchy
+   that leads to a project to be clicked, which in turn shows the
+   list of projects inside that directory.
+
+ * "gitweb" learned to read various pieces of information for the
+   repositories lazily, instead of reading everything that could be
+   needed (including the ones that are not necessary for a specific
+   task).
+
+ * Project search in "gitweb" shows the substring that matched in the
+   project name and description highlighted.
+
+ * A new script "diffall" is added to contrib/; it drives an external
+   an external tool to perform a directory diff of two Git revisions
+   in one go, unlike "difftool" that compares one file at a time.
+
+Foreign Interface
+
+ * Improved handling of views, labels and branches in "git-p4" (in contrib).
+
+ * "git-p4" (in contrib) suffered from unnecessary merge conflicts when
+   p4 expanded the embedded $RCS$-like keywords; it can be now told to
+   unexpand them.
+
+ * Some "git-svn" updates.
+
+ * "vcs-svn"/"svn-fe" learned to read dumps with svn-deltas and
+   support incremental imports.
+
+ * "git difftool/mergetool" learned to drive DeltaWalker.
+
+Performance
+
+ * Unnecessary calls to parse_object() "git upload-pack" makes in
+   response to "git fetch", have been eliminated, to help performance
+   in repositories with excessive number of refs.
+
+Internal Implementation (please report possible regressions)
+
+ * Recursive call chains in "git index-pack" to deal with long delta
+   chains have been flattened, to reduce the stack footprint.
+
+ * Use of add_extra_ref() API is now gone, to make it possible to
+   cleanly restructure the overall refs API.
+
+ * The command line parser of "git pack-objects" now uses parse-options
+   API.
+
+ * The test suite supports the new "test_pause" helper function.
+
+ * Parallel to the test suite, there is a beginning of performance
+   benchmarking framework.
+
+ * t/Makefile is adjusted to prevent newer versions of GNU make from
+   running tests in seemingly random order.
+
+ * The code to check if a path points at a file beyond a symbolic link
+   has been restructured to be thread-safe.
+
+ * When pruning directories that has become empty during "git prune"
+   and "git prune-packed", call closedir() that iterates over a
+   directory before rmdir() it.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.9
+------------------
+
+Unless otherwise noted, all the fixes since v1.7.9 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * "git tag -s" honored "gpg.program" configuration variable since
+   1.7.9, but "git tag -v" and "git verify-tag" didn't.
+   (merge a2c2506 az/verify-tag-use-gpg-config later to maint).
+
+ * "gitweb" used to drop warnings in the log file when "heads" view is
+   accessed in a repository whose HEAD does not point at a valid
+   branch.
+
+---
+exec >/var/tmp/1
+O=v1.7.10-rc0-40-g57cec0a
+echo O=$(git describe)
+git log --first-parent --oneline ^maint $O..
+echo
+git shortlog --no-merges ^maint $O..
diff --git a/Documentation/RelNotes/1.7.9.4.txt b/Documentation/RelNotes/1.7.9.4.txt
new file mode 100644 (file)
index 0000000..e5217a1
--- /dev/null
@@ -0,0 +1,24 @@
+Git v1.7.9.4 Release Notes
+==========================
+
+Fixes since v1.7.9.3
+--------------------
+
+ * The code to synthesize the fake ancestor tree used by 3-way merge
+   fallback in "git am" was not prepared to read a patch created with
+   a non-standard -p<num> value.
+
+ * "git bundle" did not record boundary commits correctly when there
+   are many of them.
+
+ * "git diff-index" and its friends at the plumbing level showed the
+   "diff --git" header and nothing else for a path whose cached stat
+   info is dirty without actual difference when asked to produce a
+   patch. This was a longstanding bug that we could have fixed long
+   time ago.
+
+ * "gitweb" did use quotemeta() to prepare search string when asked to
+   do a fixed-string project search, but did not use it by mistake and
+   used the user-supplied string instead.
+
+Also contains minor fixes and documentation updates.
index 0e1168c..c081657 100644 (file)
@@ -86,6 +86,17 @@ customary UNIX fashion.
 
 Some variables may require a special value format.
 
+Includes
+~~~~~~~~
+
+You can include one config file from another by setting the special
+`include.path` variable to the name of the file to be included. The
+included file is expanded immediately, as if its contents had been
+found at the location of the include directive. If the value of the
+`include.path` variable is a relative path, the path is considered to be
+relative to the configuration file in which the include directive was
+found. See below for examples.
+
 Example
 ~~~~~~~
 
@@ -108,6 +119,10 @@ Example
                gitProxy="ssh" for "kernel.org"
                gitProxy=default-proxy ; for the rest
 
+       [include]
+               path = /path/to/foo.inc ; include by absolute path
+               path = foo ; expand "foo" relative to the current file
+
 Variables
 ~~~~~~~~~
 
index 1aed79e..6aa1be0 100644 (file)
@@ -52,6 +52,10 @@ directories with less than 10% of the total amount of changed files,
 and accumulating child directory counts in the parent directories:
 `files,10,cumulative`.
 
+diff.statGraphWidth::
+       Limit the width of the graph part in --stat output. If set, applies
+       to all commands generating --stat outuput except format-patch.
+
 diff.external::
        If this config variable is set, diff generation is not
        performed using the internal diff machinery, but using the
index 9f7cba2..7d4566f 100644 (file)
@@ -52,14 +52,23 @@ endif::git-format-patch[]
 --patience::
        Generate a diff using the "patience diff" algorithm.
 
+--histogram::
+       Generate a diff using the "histogram diff" algorithm.
+
 --stat[=<width>[,<name-width>[,<count>]]]::
-       Generate a diffstat.  You can override the default
-       output width for 80-column terminal by `--stat=<width>`.
-       The width of the filename part can be controlled by
-       giving another width to it separated by a comma.
+       Generate a diffstat. By default, as much space as necessary
+       will be used for the filename part, and the rest for the graph
+       part. Maximum width defaults to terminal width, or 80 columns
+       if not connected to a terminal, and can be overriden by
+       `<width>`. The width of the filename part can be limited by
+       giving another width `<name-width>` after a comma. The width
+       of the graph part can be limited by using
+       `--stat-graph-width=<width>` (affects all commands generating
+       a stat graph) or by setting `diff.statGraphWidth=<width>`
+       (does not affect `git format-patch`).
        By giving a third parameter `<count>`, you can limit the
-       output to the first `<count>` lines, followed by
-       `...` if there are more.
+       output to the first `<count>` lines, followed by `...` if
+       there are more.
 +
 These parameters can also be set individually with `--stat-width=<width>`,
 `--stat-name-width=<name-width>` and `--stat-count=<count>`.
index 887466d..ee6cca2 100644 (file)
@@ -40,6 +40,9 @@ OPTIONS
 --keep::
        Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--keep-non-patch::
+       Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]).
+
 --keep-cr::
 --no-keep-cr::
        With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1])
index 0427e80..6410c3d 100644 (file)
@@ -24,8 +24,8 @@ be highlighted with an asterisk.  Option `-r` causes the remote-tracking
 branches to be listed, and option `-a` shows both. This list mode is also
 activated by the `--list` option (see below).
 <pattern> restricts the output to matching branches, the pattern is a shell
-wildcard (i.e., matched using fnmatch(3))
-Multiple patterns may be given; if any of them matches, the tag is shown.
+wildcard (i.e., matched using fnmatch(3)).
+Multiple patterns may be given; if any of them matches, the branch is shown.
 
 With `--contains`, shows only the branches that contain the named commit
 (in other words, the branches whose tip commits are descendants of the
@@ -49,7 +49,7 @@ the remote-tracking branch. This behavior may be changed via the global
 overridden by using the `--track` and `--no-track` options, and
 changed later using `git branch --set-upstream`.
 
-With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
+With a `-m` or `-M` option, <oldbranch> will be renamed to <newbranch>.
 If <oldbranch> had a corresponding reflog, it is renamed to match
 <newbranch>, and a reflog entry is created to remember the branch
 renaming. If <newbranch> exists, -M must be used to force the rename
@@ -59,7 +59,7 @@ With a `-d` or `-D` option, `<branchname>` will be deleted.  You may
 specify more than one branch for deletion.  If the branch currently
 has a reflog then the reflog will also be deleted.
 
-Use -r together with -d to delete remote-tracking branches. Note, that it
+Use `-r` together with `-d` to delete remote-tracking branches. Note, that it
 only makes sense to delete remote-tracking branches if they no longer exist
 in the remote repository or if 'git fetch' was configured not to fetch
 them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a
@@ -154,17 +154,18 @@ start-point is either a local or remote-tracking branch.
        branch.autosetupmerge configuration variable is true.
 
 --set-upstream::
-       If specified branch does not exist yet or if '--force' has been
-       given, acts exactly like '--track'. Otherwise sets up configuration
-       like '--track' would when creating the branch, except that where
+       If specified branch does not exist yet or if `--force` has been
+       given, acts exactly like `--track`. Otherwise sets up configuration
+       like `--track` would when creating the branch, except that where
        branch points to is not changed.
 
 --edit-description::
        Open an editor and edit the text to explain what the branch is
        for, to be used by various other commands (e.g. `request-pull`).
 
---contains <commit>::
-       Only list branches which contain the specified commit.
+--contains [<commit>]::
+       Only list branches which contain the specified commit (HEAD
+       if not specified).
 
 --merged [<commit>]::
        Only list branches whose tips are reachable from the
index 4b8b26b..6e22522 100644 (file)
@@ -13,7 +13,8 @@ SYNOPSIS
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--separate-git-dir <git dir>]
-         [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>
+         [--depth <depth>] [--[no-]single-branch]
+         [--recursive|--recurse-submodules] [--] <repository>
          [<directory>]
 
 DESCRIPTION
@@ -146,8 +147,9 @@ objects from the source repository into a pack in the cloned repository.
 -b <name>::
        Instead of pointing the newly created HEAD to the branch pointed
        to by the cloned repository's HEAD, point to `<name>` branch
-       instead. In a non-bare repository, this is the branch that will
-       be checked out.
+       instead. `--branch` can also take tags and treat them like
+       detached HEAD. In a non-bare repository, this is the branch
+       that will be checked out.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
@@ -179,6 +181,14 @@ objects from the source repository into a pack in the cloned repository.
        with a long history, and would want to send in fixes
        as patches.
 
+--single-branch::
+       Clone only the history leading to the tip of a single branch,
+       either specified by the `--branch` option or the primary
+       branch remote's `HEAD` points at. When creating a shallow
+       clone with the `--depth` option, this is the default, unless
+       `--no-single-branch` is given to fetch the histories near the
+       tips of all branches.
+
 --recursive::
 --recurse-submodules::
        After the clone is created, initialize all submodules within,
index 7617d9e..81b0398 100644 (file)
@@ -181,6 +181,11 @@ See also <<FILES>>.
        Opens an editor to modify the specified config file; either
        '--system', '--global', or repository (default).
 
+--includes::
+--no-includes::
+       Respect `include.*` directives in config files when looking up
+       values. Defaults to on.
+
 [[FILES]]
 FILES
 -----
index 6c47395..bbb25da 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
         [--[no-]full] [--strict] [--verbose] [--lost-found]
-        [--[no-]progress] [<object>*]
+        [--[no-]dangling] [--[no-]progress] [<object>*]
 
 DESCRIPTION
 -----------
@@ -30,6 +30,11 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
        Print out objects that exist but that aren't reachable from any
        of the reference nodes.
 
+--dangling::
+--no-dangling::
+       Print objects that exist but that are never 'directly' used (default).
+       `--no-dangling` can be used to omit this information from the output.
+
 --root::
        Report root nodes.
 
index e2e6aba..3ceefb8 100644 (file)
@@ -9,7 +9,7 @@ git-merge - Join two or more development histories together
 SYNOPSIS
 --------
 [verse]
-'git merge' [-n] [--stat] [--no-commit] [--squash]
+'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
        [-s <strategy>] [-X <strategy-option>]
        [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
 'git merge' <msg> HEAD <commit>...
index ed82790..b7c7929 100644 (file)
@@ -318,6 +318,11 @@ around whitespace.  Of the possible wildcards, git-p4 only handles
 '...', and only when it is at the end of the path.  Git-p4 will complain
 if it encounters an unhandled wildcard.
 
+Bugs in the implementation of overlap mappings exist.  If multiple depot
+paths map through overlays to the same location in the repository,
+git-p4 can choose the wrong one.  This is hard to solve without
+dedicating a client spec just for git-p4.
+
 The name of the client can be given to git-p4 in multiple ways.  The
 variable 'git-p4.client' takes precedence if it exists.  Otherwise,
 normal p4 mechanisms of determining the client are used:  environment
@@ -482,6 +487,11 @@ git-p4.skipUserNameCheck::
        user map, 'git p4' exits.  This option can be used to force
        submission regardless.
 
+git-p4.attemptRCSCleanup:
+    If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
+    ($Header$, etc). These would otherwise cause merge conflicts and prevent
+    the submit going ahead. This option should be considered experimental at
+    present.
 
 IMPLEMENTATION DETAILS
 ----------------------
index aede488..48760db 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
-          [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
+          [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
           [<repository> [<refspec>...]]
 
 DESCRIPTION
@@ -71,6 +71,14 @@ nor in any Push line of the corresponding remotes file---see below).
        Instead of naming each ref to push, specifies that all
        refs under `refs/heads/` be pushed.
 
+--prune::
+       Remove remote branches that don't have a local counterpart. For example
+       a remote branch `tmp` will be removed if a local branch with the same
+       name doesn't exist any more. This also respects refspecs, e.g.
+       `git push --prune remote refs/heads/{asterisk}:refs/tmp/{asterisk}` would
+       make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
+       doesn't exist.
+
 --mirror::
        Instead of naming each ref to push, specifies that all
        refs under `refs/` (which includes but is not
index 40af321..4c1aff6 100644 (file)
@@ -34,7 +34,7 @@ OPTIONS
        Especially useful when packing a repository that is used
        for private development. Use
        with '-d'.  This will clean up the objects that `git prune`
-       leaves behind, but `git fsck --full` shows as
+       leaves behind, but `git fsck --full --dangling` shows as
        dangling.
 +
 Note that users fetching over dumb protocols will have to fetch the
index a6253ba..b43b7c8 100644 (file)
@@ -8,7 +8,7 @@ git-rerere - Reuse recorded resolution of conflicted merges
 SYNOPSIS
 --------
 [verse]
-'git rerere' ['clear'|'forget' <pathspec>|'diff'|'status'|'gc']
+'git rerere' ['clear'|'forget' <pathspec>|'diff'|'remaining'|'status'|'gc']
 
 DESCRIPTION
 -----------
@@ -37,30 +37,35 @@ its working state.
 
 'clear'::
 
-This resets the metadata used by rerere if a merge resolution is to be
+Reset the metadata used by rerere if a merge resolution is to be
 aborted.  Calling 'git am [--skip|--abort]' or 'git rebase [--skip|--abort]'
 will automatically invoke this command.
 
 'forget' <pathspec>::
 
-This resets the conflict resolutions which rerere has recorded for the current
+Reset the conflict resolutions which rerere has recorded for the current
 conflict in <pathspec>.
 
 'diff'::
 
-This displays diffs for the current state of the resolution.  It is
+Display diffs for the current state of the resolution.  It is
 useful for tracking what has changed while the user is resolving
 conflicts.  Additional arguments are passed directly to the system
 'diff' command installed in PATH.
 
 'status'::
 
-Like 'diff', but this only prints the filenames that will be tracked
-for resolutions.
+Print paths with conflicts whose merge resolution rerere will record.
+
+'remaining'::
+
+Print paths with conflicts that have not been autoresolved by rerere.
+This includes paths whose resolutions cannot be tracked by rerere,
+such as conflicting submodules.
 
 'gc'::
 
-This prunes records of conflicted merges that
+Prune records of conflicted merges that
 occurred a long time ago.  By default, unresolved conflicts older
 than 15 days and resolved conflicts older than 60
 days are pruned.  These defaults are controlled via the
index 327233c..3241170 100644 (file)
@@ -198,6 +198,10 @@ must be used for each option.
        if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'),
        then authentication is not attempted.
 
+--smtp-debug=0|1::
+       Enable (1) or disable (0) debug output. If enabled, SMTP
+       commands and replies will be printed. Useful to debug TLS
+       connection and authentication problems.
 
 Automating
 ~~~~~~~~~~
index a45d4c4..981d3a8 100644 (file)
@@ -8,7 +8,8 @@ git-symbolic-ref - Read and modify symbolic refs
 SYNOPSIS
 --------
 [verse]
-'git symbolic-ref' [-q] [-m <reason>] <name> [<ref>]
+'git symbolic-ref' [-m <reason>] <name> <ref>
+'git symbolic-ref' [-q] [--short] <name>
 
 DESCRIPTION
 -----------
@@ -33,6 +34,10 @@ OPTIONS
        symbolic ref but a detached HEAD; instead exit with
        non-zero status silently.
 
+--short::
+       When showing the value of <name> as a symbolic ref, try to shorten the
+       value, e.g. from `refs/heads/master` to `master`.
+
 -m::
        Update the reflog for <name> with <reason>.  This is valid only
        when creating or updating a symbolic ref.
index 53ff5f6..8d32b9a 100644 (file)
@@ -12,7 +12,8 @@ SYNOPSIS
 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
-'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...]
+'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
+       [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -86,6 +87,9 @@ OPTIONS
 --contains <commit>::
        Only list tags which contain the specified commit.
 
+--points-at <object>::
+       Only list tags of the given object.
+
 -m <msg>::
 --message=<msg>::
        Use the given tag message (instead of prompting).
index b257d80..d5b7667 100644 (file)
@@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.9.3/git.html[documentation for release 1.7.9.3]
+* link:v1.7.9.4/git.html[documentation for release 1.7.9.4]
 
 * release notes for
+  link:RelNotes/1.7.9.4.txt[1.7.9.4],
   link:RelNotes/1.7.9.3.txt[1.7.9.3],
   link:RelNotes/1.7.9.2.txt[1.7.9.2],
   link:RelNotes/1.7.9.1.txt[1.7.9.1],
index a85b187..80120ea 100644 (file)
@@ -294,16 +294,27 @@ output is used to update the worktree file.  Similarly, the
 `clean` command is used to convert the contents of worktree file
 upon checkin.
 
-A missing filter driver definition in the config is not an error
-but makes the filter a no-op passthru.
-
-The content filtering is done to massage the content into a
-shape that is more convenient for the platform, filesystem, and
-the user to use.  The key phrase here is "more convenient" and not
-"turning something unusable into usable".  In other words, the
-intent is that if someone unsets the filter driver definition,
-or does not have the appropriate filter program, the project
-should still be usable.
+One use of the content filtering is to massage the content into a shape
+that is more convenient for the platform, filesystem, and the user to use.
+For this mode of operation, the key phrase here is "more convenient" and
+not "turning something unusable into usable".  In other words, the intent
+is that if someone unsets the filter driver definition, or does not have
+the appropriate filter program, the project should still be usable.
+
+Another use of the content filtering is to store the content that cannot
+be directly used in the repository (e.g. a UUID that refers to the true
+content stored outside git, or an encrypted content) and turn it into a
+usable form upon checkout (e.g. download the external content, or decrypt
+the encrypted content).
+
+These two filters behave differently, and by default, a filter is taken as
+the former, massaging the contents into more convenient shape.  A missing
+filter driver definition in the config, or a filter driver that exits with
+a non-zero status, is not an error but makes the filter a no-op passthru.
+
+You can declare that a filter turns a content that by itself is unusable
+into a usable content by setting the filter.<driver>.required configuration
+variable to `true`.
 
 For example, in .gitattributes, you would assign the `filter`
 attribute for paths.
@@ -335,6 +346,16 @@ input that is already correctly indented.  In this case, the lack of a
 smudge filter means that the clean filter _must_ accept its own output
 without modifying it.
 
+If a filter _must_ succeed in order to make the stored contents usable,
+you can declare that the filter is `required`, in the configuration:
+
+------------------------
+[filter "crypt"]
+       clean = openssl enc ...
+       smudge = openssl enc -d ...
+       required
+------------------------
+
 Sequence "%f" on the filter command line is replaced with the name of
 the file the filter is working on.  A filter might use this in keyword
 substitution.  For example:
index 2bd92c4..0bcbe0a 100644 (file)
@@ -8,9 +8,20 @@ failed and do not autocommit, to give the user a chance to
 inspect and further tweak the merge result before committing.
 
 --edit::
--e::
-       Invoke editor before committing successful merge to further
-       edit the default merge message.
+--no-edit::
+       Invoke an editor before committing successful mechanical merge to
+       further edit the auto-generated merge message, so that the user
+       can explain and justify the merge. The `--no-edit` option can be
+       used to accept the auto-generated message (this is generally
+       discouraged). The `--edit` option is still useful if you are
+       giving a draft message with the `-m` option from the command line
+       and want to edit it in the editor.
++
+Older scripts may depend on the historical behaviour of not allowing the
+user to edit the merge log message. They will see an editor opened when
+they run `git merge`. To make it easier to adjust such scripts to the
+updated behaviour, the environment variable `GIT_MERGE_AUTOEDIT` can be
+set to `no` at the beginning of them.
 
 --ff::
        When the merge resolves as a fast-forward, only update the branch
diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt
new file mode 100644 (file)
index 0000000..edf8dfb
--- /dev/null
@@ -0,0 +1,140 @@
+config API
+==========
+
+The config API gives callers a way to access git configuration files
+(and files which have the same syntax). See linkgit:git-config[1] for a
+discussion of the config file syntax.
+
+General Usage
+-------------
+
+Config files are parsed linearly, and each variable found is passed to a
+caller-provided callback function. The callback function is responsible
+for any actions to be taken on the config option, and is free to ignore
+some options. It is not uncommon for the configuration to be parsed
+several times during the run of a git program, with different callbacks
+picking out different variables useful to themselves.
+
+A config callback function takes three parameters:
+
+- the name of the parsed variable. This is in canonical "flat" form: the
+  section, subsection, and variable segments will be separated by dots,
+  and the section and variable segments will be all lowercase. E.g.,
+  `core.ignorecase`, `diff.SomeType.textconv`.
+
+- the value of the found variable, as a string. If the variable had no
+  value specified, the value will be NULL (typically this means it
+  should be interpreted as boolean true).
+
+- a void pointer passed in by the caller of the config API; this can
+  contain callback-specific data
+
+A config callback should return 0 for success, or -1 if the variable
+could not be parsed properly.
+
+Basic Config Querying
+---------------------
+
+Most programs will simply want to look up variables in all config files
+that git knows about, using the normal precedence rules. To do this,
+call `git_config` with a callback function and void data pointer.
+
+`git_config` will read all config sources in order of increasing
+priority. Thus a callback should typically overwrite previously-seen
+entries with new ones (e.g., if both the user-wide `~/.gitconfig` and
+repo-specific `.git/config` contain `color.ui`, the config machinery
+will first feed the user-wide one to the callback, and then the
+repo-specific one; by overwriting, the higher-priority repo-specific
+value is left at the end).
+
+The `git_config_with_options` function lets the caller examine config
+while adjusting some of the default behavior of `git_config`. It should
+almost never be used by "regular" git code that is looking up
+configuration variables. It is intended for advanced callers like
+`git-config`, which are intentionally tweaking the normal config-lookup
+process. It takes two extra parameters:
+
+`filename`::
+If this parameter is non-NULL, it specifies the name of a file to
+parse for configuration, rather than looking in the usual files. Regular
+`git_config` defaults to `NULL`.
+
+`respect_includes`::
+Specify whether include directives should be followed in parsed files.
+Regular `git_config` defaults to `1`.
+
+There is a special version of `git_config` called `git_config_early`.
+This version takes an additional parameter to specify the repository
+config, instead of having it looked up via `git_path`. This is useful
+early in a git program before the repository has been found. Unless
+you're working with early setup code, you probably don't want to use
+this.
+
+Reading Specific Files
+----------------------
+
+To read a specific file in git-config format, use
+`git_config_from_file`. This takes the same callback and data parameters
+as `git_config`.
+
+Value Parsing Helpers
+---------------------
+
+To aid in parsing string values, the config API provides callbacks with
+a number of helper functions, including:
+
+`git_config_int`::
+Parse the string to an integer, including unit factors. Dies on error;
+otherwise, returns the parsed result.
+
+`git_config_ulong`::
+Identical to `git_config_int`, but for unsigned longs.
+
+`git_config_bool`::
+Parse a string into a boolean value, respecting keywords like "true" and
+"false". Integer values are converted into true/false values (when they
+are non-zero or zero, respectively). Other values cause a die(). If
+parsing is successful, the return value is the result.
+
+`git_config_bool_or_int`::
+Same as `git_config_bool`, except that integers are returned as-is, and
+an `is_bool` flag is unset.
+
+`git_config_maybe_bool`::
+Same as `git_config_bool`, except that it returns -1 on error rather
+than dying.
+
+`git_config_string`::
+Allocates and copies the value string into the `dest` parameter; if no
+string is given, prints an error message and returns -1.
+
+`git_config_pathname`::
+Similar to `git_config_string`, but expands `~` or `~user` into the
+user's home directory when found at the beginning of the path.
+
+Include Directives
+------------------
+
+By default, the config parser does not respect include directives.
+However, a caller can use the special `git_config_include` wrapper
+callback to support them. To do so, you simply wrap your "real" callback
+function and data pointer in a `struct config_include_data`, and pass
+the wrapper to the regular config-reading functions. For example:
+
+-------------------------------------------
+int read_file_with_include(const char *file, config_fn_t fn, void *data)
+{
+       struct config_include_data inc = CONFIG_INCLUDE_INIT;
+       inc.fn = fn;
+       inc.data = data;
+       return git_config_from_file(git_config_include, file, &inc);
+}
+-------------------------------------------
+
+`git_config` respects includes automatically. The lower-level
+`git_config_from_file` does not.
+
+Writing Config Files
+--------------------
+
+TODO
index 4b92514..2527b7e 100644 (file)
@@ -39,7 +39,8 @@ The parse-options API allows:
 * Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
 
 * Boolean long options can be 'negated' (or 'unset') by prepending
-  `no-`, e.g. `\--no-abbrev` instead of `\--abbrev`.
+  `no-`, e.g. `\--no-abbrev` instead of `\--abbrev`. Conversely,
+  options that begin with `no-` can be 'negated' by removing it.
 
 * Options and non-option arguments can clearly be separated using the `\--`
   option, e.g. `-a -b \--option \-- \--this-is-a-file` indicates that
index f13a846..6c7fee7 100644 (file)
@@ -1582,7 +1582,7 @@ Checking the repository for corruption
 
 The linkgit:git-fsck[1] command runs a number of self-consistency checks
 on the repository, and reports on any problems.  This may take some
-time.  The most common warning by far is about "dangling" objects:
+time.
 
 -------------------------------------------------
 $ git fsck
@@ -1597,9 +1597,11 @@ dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
 ...
 -------------------------------------------------
 
-Dangling objects are not a problem.  At worst they may take up a little
-extra disk space.  They can sometimes provide a last-resort method for
-recovering lost work--see <<dangling-objects>> for details.
+You will see informational messages on dangling objects. They are objects
+that still exist in the repository but are no longer referenced by any of
+your branches, and can (and will) be removed after a while with "gc".
+You can run `git fsck --no-dangling` to supress these messages, and still
+view real errors.
 
 [[recovering-lost-changes]]
 Recovering lost changes
@@ -3295,15 +3297,12 @@ it is with linkgit:git-fsck[1]; this may be time-consuming.
 Assume the output looks like this:
 
 ------------------------------------------------
-$ git fsck --full
+$ git fsck --full --no-dangling
 broken link from    tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
               to    blob 4b9458b3786228369c63936db65827de3cc06200
 missing blob 4b9458b3786228369c63936db65827de3cc06200
 ------------------------------------------------
 
-(Typically there will be some "dangling object" messages too, but they
-aren't interesting.)
-
 Now you know that blob 4b9458b3 is missing, and that the tree 2d9263c6
 points to it.  If you could find just one copy of that missing blob
 object, possibly in some other repository, you could move it into
index de203dd..f28ceef 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.9.3
+DEF_VER=v1.7.10-rc0
 
 LF='
 '
index e4f8e0e..be1957a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -47,6 +47,9 @@ all::
 # A translated Git requires GNU libintl or another gettext implementation,
 # plus libintl-perl at runtime.
 #
+# Define USE_GETTEXT_SCHEME and set it to 'fallthrough', if you don't trust
+# the installed gettext translation of the shell scripts output.
+#
 # Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
 # trust the langinfo.h's nl_langinfo(CODESET) function to return the
 # current character set. GNU and Solaris have a nl_langinfo(CODESET),
@@ -378,6 +381,11 @@ BUILTIN_OBJS =
 BUILT_INS =
 COMPAT_CFLAGS =
 COMPAT_OBJS =
+XDIFF_H =
+XDIFF_OBJS =
+VCSSVN_H =
+VCSSVN_OBJS =
+VCSSVN_TEST_OBJS =
 EXTRA_CPPFLAGS =
 LIB_H =
 LIB_OBJS =
@@ -473,16 +481,13 @@ TEST_PROGRAMS_NEED_X += test-index-version
 TEST_PROGRAMS_NEED_X += test-line-buffer
 TEST_PROGRAMS_NEED_X += test-match-trees
 TEST_PROGRAMS_NEED_X += test-mktemp
-TEST_PROGRAMS_NEED_X += test-obj-pool
 TEST_PROGRAMS_NEED_X += test-parse-options
 TEST_PROGRAMS_NEED_X += test-path-utils
 TEST_PROGRAMS_NEED_X += test-run-command
 TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
-TEST_PROGRAMS_NEED_X += test-string-pool
 TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
-TEST_PROGRAMS_NEED_X += test-treap
 
 TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
 
@@ -1529,6 +1534,7 @@ ifdef GETTEXT_POISON
 endif
 ifdef NO_GETTEXT
        BASIC_CFLAGS += -DNO_GETTEXT
+       USE_GETTEXT_SCHEME ?= fallthrough
 endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
@@ -1926,6 +1932,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
     -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+    -e 's/@@USE_GETTEXT_SCHEME@@/$(USE_GETTEXT_SCHEME)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
 endef
@@ -2027,12 +2034,24 @@ GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
 ifndef NO_CURL
        GIT_OBJS += http.o http-walker.o remote-curl.o
 endif
-XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
-       xdiff/xmerge.o xdiff/xpatience.o xdiff/xhistogram.o
-VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \
-       vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o
-VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \
-       test-line-buffer.o test-treap.o
+
+XDIFF_OBJS += xdiff/xdiffi.o
+XDIFF_OBJS += xdiff/xprepare.o
+XDIFF_OBJS += xdiff/xutils.o
+XDIFF_OBJS += xdiff/xemit.o
+XDIFF_OBJS += xdiff/xmerge.o
+XDIFF_OBJS += xdiff/xpatience.o
+XDIFF_OBJS += xdiff/xhistogram.o
+
+VCSSVN_OBJS += vcs-svn/line_buffer.o
+VCSSVN_OBJS += vcs-svn/sliding_window.o
+VCSSVN_OBJS += vcs-svn/repo_tree.o
+VCSSVN_OBJS += vcs-svn/fast_export.o
+VCSSVN_OBJS += vcs-svn/svndiff.o
+VCSSVN_OBJS += vcs-svn/svndump.o
+
+VCSSVN_TEST_OBJS += test-line-buffer.o
+
 OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
 
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
@@ -2151,16 +2170,25 @@ connect.o transport.o url.o http-backend.o: url.h
 http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
 http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
 
-xdiff-interface.o $(XDIFF_OBJS): \
-       xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
-       xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
+XDIFF_H += xdiff/xinclude.h
+XDIFF_H += xdiff/xmacros.h
+XDIFF_H += xdiff/xdiff.h
+XDIFF_H += xdiff/xtypes.h
+XDIFF_H += xdiff/xutils.h
+XDIFF_H += xdiff/xprepare.h
+XDIFF_H += xdiff/xdiffi.h
+XDIFF_H += xdiff/xemit.h
+
+xdiff-interface.o $(XDIFF_OBJS): $(XDIFF_H)
 
-$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \
-       vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \
-       vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \
-       vcs-svn/svndump.h
+VCSSVN_H += vcs-svn/line_buffer.h
+VCSSVN_H += vcs-svn/sliding_window.h
+VCSSVN_H += vcs-svn/repo_tree.h
+VCSSVN_H += vcs-svn/fast_export.h
+VCSSVN_H += vcs-svn/svndiff.h
+VCSSVN_H += vcs-svn/svndump.h
 
-test-svn-fe.o: vcs-svn/svndump.h
+$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
 endif
 
 exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
@@ -2303,7 +2331,7 @@ cscope:
 ### Detect prefix changes
 TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
-             $(localedir_SQ)
+             $(localedir_SQ):$(USE_GETTEXT_SCHEME)
 
 GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
@@ -2334,6 +2362,10 @@ GIT-BUILD-OPTIONS: FORCE
        @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+       @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef GIT_TEST_OPTS
+       @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
+endif
 ifdef GIT_TEST_CMP
        @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
 endif
@@ -2342,7 +2374,18 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
 endif
        @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
        @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
-       @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ifdef GIT_PERF_REPEAT_COUNT
+       @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@
+endif
+ifdef GIT_PERF_REPO
+       @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@
+endif
+ifdef GIT_PERF_LARGE_REPO
+       @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@
+endif
+ifdef GIT_PERF_MAKE_OPTS
+       @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@
+endif
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -2378,6 +2421,11 @@ export NO_SVN_TESTS
 test: all
        $(MAKE) -C t/ all
 
+perf: all
+       $(MAKE) -C t/perf/ all
+
+.PHONY: test perf
+
 test-ctype$X: ctype.o
 
 test-date$X: date.o ctype.o
@@ -2388,8 +2436,6 @@ test-line-buffer$X: vcs-svn/lib.a
 
 test-parse-options$X: parse-options.o parse-options-cb.o
 
-test-string-pool$X: vcs-svn/lib.a
-
 test-svn-fe$X: vcs-svn/lib.a
 
 .PRECIOUS: $(TEST_OBJS)
@@ -2589,7 +2635,6 @@ dist-doc:
 
 distclean: clean
        $(RM) configure
-       $(RM) po/git.pot
 
 profile-clean:
        $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
index 36a5885..2c2a169 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.9.3.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.10.txt
\ No newline at end of file
index e02e632..01130e5 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -21,11 +21,21 @@ static struct {
 
 void advise(const char *advice, ...)
 {
+       struct strbuf buf = STRBUF_INIT;
        va_list params;
+       const char *cp, *np;
 
        va_start(params, advice);
-       vreportf("hint: ", advice, params);
+       strbuf_addf(&buf, advice, params);
        va_end(params);
+
+       for (cp = buf.buf; *cp; cp = np) {
+               np = strchrnul(cp, '\n');
+               fprintf(stderr, _("hint: %.*s\n"), (int)(np - cp), cp);
+               if (*np)
+                       np++;
+       }
+       strbuf_release(&buf);
 }
 
 int git_default_advice_config(const char *var, const char *value)
@@ -46,16 +56,15 @@ int git_default_advice_config(const char *var, const char *value)
 int error_resolve_conflict(const char *me)
 {
        error("'%s' is not possible because you have unmerged files.", me);
-       if (advice_resolve_conflict) {
+       if (advice_resolve_conflict)
                /*
                 * Message used both when 'git commit' fails and when
                 * other commands doing a merge do.
                 */
-               advise("Fix them up in the work tree,");
-               advise("and then use 'git add/rm <file>' as");
-               advise("appropriate to mark resolution and make a commit,");
-               advise("or use 'git commit -a'.");
-       }
+               advise(_("Fix them up in the work tree,\n"
+                        "and then use 'git add/rm <file>' as\n"
+                        "appropriate to mark resolution and make a commit,\n"
+                        "or use 'git commit -a'."));
        return -1;
 }
 
@@ -64,3 +73,17 @@ void NORETURN die_resolve_conflict(const char *me)
        error_resolve_conflict(me);
        die("Exiting because of an unresolved conflict.");
 }
+
+void detach_advice(const char *new_name)
+{
+       const char fmt[] =
+       "Note: checking out '%s'.\n\n"
+       "You are in 'detached HEAD' state. You can look around, make experimental\n"
+       "changes and commit them, and you can discard any commits you make in this\n"
+       "state without impacting any branches by performing another checkout.\n\n"
+       "If you want to create a new branch to retain commits you create, you may\n"
+       "do so (now or later) by using -b with the checkout command again. Example:\n\n"
+       "  git checkout -b new_branch_name\n\n";
+
+       fprintf(stderr, fmt, new_name);
+}
index e5d0af7..7bda45b 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -14,5 +14,6 @@ int git_default_advice_config(const char *var, const char *value);
 void advise(const char *advice, ...);
 int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
+void detach_advice(const char *new_name);
 
 #endif /* ADVICE_H */
index 5a67c20..b35bd62 100644 (file)
@@ -1828,18 +1828,6 @@ static int read_ancestry(const char *graft_file)
        return 0;
 }
 
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
-{
-       int i, width;
-
-       for (width = 1, i = 10; i <= lines; width++)
-               i *= 10;
-       return width;
-}
-
 /*
  * How many columns do we need to show line numbers, authors,
  * and filenames?
@@ -1880,9 +1868,9 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (largest_score < ent_score(sb, e))
                        largest_score = ent_score(sb, e);
        }
-       max_orig_digits = lineno_width(longest_src_lines);
-       max_digits = lineno_width(longest_dst_lines);
-       max_score_digits = lineno_width(largest_score);
+       max_orig_digits = decimal_width(longest_src_lines);
+       max_digits = decimal_width(longest_dst_lines);
+       max_score_digits = decimal_width(largest_score);
 }
 
 /*
@@ -2050,14 +2038,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       switch (userdiff_config(var, value)) {
-       case 0:
-               break;
-       case -1:
+       if (userdiff_config(var, value) < 0)
                return -1;
-       default:
-               return 0;
-       }
 
        return git_default_config(var, value, cb);
 }
index 07bd984..8ed501f 100644 (file)
@@ -226,14 +226,8 @@ static const char * const cat_file_usage[] = {
 
 static int git_cat_file_config(const char *var, const char *value, void *cb)
 {
-       switch (userdiff_config(var, value)) {
-       case 0:
-               break;
-       case -1:
+       if (userdiff_config(var, value) < 0)
                return -1;
-       default:
-               return 0;
-       }
 
        return git_default_config(var, value, cb);
 }
index a76aa2a..6b9061f 100644 (file)
@@ -514,20 +514,6 @@ static void report_tracking(struct branch_info *new)
        strbuf_release(&sb);
 }
 
-static void detach_advice(const char *old_path, const char *new_name)
-{
-       const char fmt[] =
-       "Note: checking out '%s'.\n\n"
-       "You are in 'detached HEAD' state. You can look around, make experimental\n"
-       "changes and commit them, and you can discard any commits you make in this\n"
-       "state without impacting any branches by performing another checkout.\n\n"
-       "If you want to create a new branch to retain commits you create, you may\n"
-       "do so (now or later) by using -b with the checkout command again. Example:\n\n"
-       "  git checkout -b new_branch_name\n\n";
-
-       fprintf(stderr, fmt, new_name);
-}
-
 static void update_refs_for_switch(struct checkout_opts *opts,
                                   struct branch_info *old,
                                   struct branch_info *new)
@@ -575,7 +561,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                           REF_NODEREF, DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path && advice_detached_head)
-                               detach_advice(old->path, new->name);
+                               detach_advice(new->name);
                        describe_detached_head(_("HEAD is now at"), new->commit);
                }
        } else if (new->path) { /* Switch branches. */
index 0fb5956..bbd5c96 100644 (file)
@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {
        NULL
 };
 
-static int option_no_checkout, option_bare, option_mirror;
+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
 static int option_local, option_no_hardlinks, option_shared, option_recursive;
 static char *option_template, *option_depth;
 static char *option_origin = NULL;
@@ -92,6 +92,8 @@ static struct option builtin_clone_options[] = {
                   "path to git-upload-pack on the remote"),
        OPT_STRING(0, "depth", &option_depth, "depth",
                    "create a shallow clone of that depth"),
+       OPT_BOOL(0, "single-branch", &option_single_branch,
+                   "clone only one branch, HEAD or --branch"),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
                   "separate git dir from working tree"),
        OPT_STRING_LIST('c', "config", &option_config, "key=value",
@@ -230,9 +232,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
        char *ref_git;
        struct strbuf alternate = STRBUF_INIT;
-       struct remote *remote;
-       struct transport *transport;
-       const struct ref *extra;
 
        /* Beware: real_path() and mkpath() return static buffer */
        ref_git = xstrdup(real_path(item->string));
@@ -247,14 +246,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
        strbuf_addf(&alternate, "%s/objects", ref_git);
        add_to_alternates_file(alternate.buf);
        strbuf_release(&alternate);
-
-       remote = remote_get(ref_git);
-       transport = transport_get(remote, ref_git);
-       for (extra = transport_get_remote_refs(transport); extra;
-            extra = extra->next)
-               add_extra_ref(extra->name, extra->old_sha1, 0);
-
-       transport_disconnect(transport);
        free(ref_git);
        return 0;
 }
@@ -361,13 +352,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
        closedir(dir);
 }
 
-static const struct ref *clone_local(const char *src_repo,
-                                    const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
 {
-       const struct ref *ret;
-       struct remote *remote;
-       struct transport *transport;
-
        if (option_shared) {
                struct strbuf alt = STRBUF_INIT;
                strbuf_addf(&alt, "%s/objects", src_repo);
@@ -383,13 +369,8 @@ static const struct ref *clone_local(const char *src_repo,
                strbuf_release(&dest);
        }
 
-       remote = remote_get(src_repo);
-       transport = transport_get(remote, src_repo);
-       ret = transport_get_remote_refs(transport);
-       transport_disconnect(transport);
        if (0 <= option_verbosity)
                printf(_("done.\n"));
-       return ret;
 }
 
 static const char *junk_work_tree;
@@ -420,6 +401,26 @@ static void remove_junk_on_signal(int signo)
        raise(signo);
 }
 
+static struct ref *find_remote_branch(const struct ref *refs, const char *branch)
+{
+       struct ref *ref;
+       struct strbuf head = STRBUF_INIT;
+       strbuf_addstr(&head, "refs/heads/");
+       strbuf_addstr(&head, branch);
+       ref = find_ref_by_name(refs, head.buf);
+       strbuf_release(&head);
+
+       if (ref)
+               return ref;
+
+       strbuf_addstr(&head, "refs/tags/");
+       strbuf_addstr(&head, branch);
+       ref = find_ref_by_name(refs, head.buf);
+       strbuf_release(&head);
+
+       return ref;
+}
+
 static struct ref *wanted_peer_refs(const struct ref *refs,
                struct refspec *refspec)
 {
@@ -427,8 +428,27 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
        struct ref *local_refs = head;
        struct ref **tail = head ? &head->next : &local_refs;
 
-       get_fetch_map(refs, refspec, &tail, 0);
-       if (!option_mirror)
+       if (option_single_branch) {
+               struct ref *remote_head = NULL;
+
+               if (!option_branch)
+                       remote_head = guess_remote_head(head, refs, 0);
+               else
+                       remote_head = find_remote_branch(refs, option_branch);
+
+               if (!remote_head && option_branch)
+                       warning(_("Could not find remote branch %s to clone."),
+                               option_branch);
+               else {
+                       get_fetch_map(remote_head, refspec, &tail, 0);
+
+                       /* if --branch=tag, pull the requested tag explicitly */
+                       get_fetch_map(remote_head, tag_refspec, &tail, 0);
+               }
+       } else
+               get_fetch_map(refs, refspec, &tail, 0);
+
+       if (!option_mirror && !option_single_branch)
                get_fetch_map(refs, tag_refspec, &tail, 0);
 
        return local_refs;
@@ -441,11 +461,134 @@ static void write_remote_refs(const struct ref *local_refs)
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
-               add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+               add_packed_ref(r->peer_ref->name, r->old_sha1);
        }
 
        pack_refs(PACK_REFS_ALL);
-       clear_extra_refs();
+}
+
+static void write_followtags(const struct ref *refs, const char *msg)
+{
+       const struct ref *ref;
+       for (ref = refs; ref; ref = ref->next) {
+               if (prefixcmp(ref->name, "refs/tags/"))
+                       continue;
+               if (!suffixcmp(ref->name, "^{}"))
+                       continue;
+               if (!has_sha1_file(ref->old_sha1))
+                       continue;
+               update_ref(msg, ref->name, ref->old_sha1,
+                          NULL, 0, DIE_ON_ERR);
+       }
+}
+
+static void update_remote_refs(const struct ref *refs,
+                              const struct ref *mapped_refs,
+                              const struct ref *remote_head_points_at,
+                              const char *branch_top,
+                              const char *msg)
+{
+       if (refs) {
+               write_remote_refs(mapped_refs);
+               if (option_single_branch)
+                       write_followtags(refs, msg);
+       }
+
+       if (remote_head_points_at && !option_bare) {
+               struct strbuf head_ref = STRBUF_INIT;
+               strbuf_addstr(&head_ref, branch_top);
+               strbuf_addstr(&head_ref, "HEAD");
+               create_symref(head_ref.buf,
+                             remote_head_points_at->peer_ref->name,
+                             msg);
+       }
+}
+
+static void update_head(const struct ref *our, const struct ref *remote,
+                       const char *msg)
+{
+       if (our && !prefixcmp(our->name, "refs/heads/")) {
+               /* Local default branch link */
+               create_symref("HEAD", our->name, NULL);
+               if (!option_bare) {
+                       const char *head = skip_prefix(our->name, "refs/heads/");
+                       update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+                       install_branch_config(0, head, option_origin, our->name);
+               }
+       } else if (our) {
+               struct commit *c = lookup_commit_reference(our->old_sha1);
+               /* --branch specifies a non-branch (i.e. tags), detach HEAD */
+               update_ref(msg, "HEAD", c->object.sha1,
+                          NULL, REF_NODEREF, DIE_ON_ERR);
+       } else if (remote) {
+               /*
+                * We know remote HEAD points to a non-branch, or
+                * HEAD points to a branch but we don't know which one.
+                * Detach HEAD in all these cases.
+                */
+               update_ref(msg, "HEAD", remote->old_sha1,
+                          NULL, REF_NODEREF, DIE_ON_ERR);
+       }
+}
+
+static int checkout(void)
+{
+       unsigned char sha1[20];
+       char *head;
+       struct lock_file *lock_file;
+       struct unpack_trees_options opts;
+       struct tree *tree;
+       struct tree_desc t;
+       int err = 0, fd;
+
+       if (option_no_checkout)
+               return 0;
+
+       head = resolve_refdup("HEAD", sha1, 1, NULL);
+       if (!head) {
+               warning(_("remote HEAD refers to nonexistent ref, "
+                         "unable to checkout.\n"));
+               return 0;
+       }
+       if (!strcmp(head, "HEAD")) {
+               if (advice_detached_head)
+                       detach_advice(sha1_to_hex(sha1));
+       } else {
+               if (prefixcmp(head, "refs/heads/"))
+                       die(_("HEAD not found below refs/heads!"));
+       }
+       free(head);
+
+       /* We need to be in the new work tree for the checkout */
+       setup_work_tree();
+
+       lock_file = xcalloc(1, sizeof(struct lock_file));
+       fd = hold_locked_index(lock_file, 1);
+
+       memset(&opts, 0, sizeof opts);
+       opts.update = 1;
+       opts.merge = 1;
+       opts.fn = oneway_merge;
+       opts.verbose_update = (option_verbosity > 0);
+       opts.src_index = &the_index;
+       opts.dst_index = &the_index;
+
+       tree = parse_tree_indirect(sha1);
+       parse_tree(tree);
+       init_tree_desc(&t, tree->buffer, tree->size);
+       unpack_trees(1, &t, &opts);
+
+       if (write_cache(fd, active_cache, active_nr) ||
+           commit_locked_index(lock_file))
+               die(_("unable to write new index file"));
+
+       err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+                       sha1_to_hex(sha1), "1", NULL);
+
+       if (!err && option_recursive)
+               err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+       return err;
 }
 
 static int write_one_config(const char *key, const char *value, void *data)
@@ -475,11 +618,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
        struct ref *mapped_refs;
+       const struct ref *ref;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
-       char *src_ref_prefix = "refs/heads/";
-       int err = 0;
+       const char *src_ref_prefix = "refs/heads/";
+       struct remote *remote;
+       int err = 0, complete_refs_before_fetch = 1;
 
        struct refspec *refspec;
        const char *fetch_pattern;
@@ -498,6 +643,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                usage_msg_opt(_("You must specify a repository to clone."),
                        builtin_clone_usage, builtin_clone_options);
 
+       if (option_single_branch == -1)
+               option_single_branch = option_depth ? 1 : 0;
+
        if (option_mirror)
                option_bare = 1;
 
@@ -630,13 +778,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        strbuf_reset(&value);
 
-       if (is_local) {
-               refs = clone_local(path, git_dir);
-               mapped_refs = wanted_peer_refs(refs, refspec);
-       } else {
-               struct remote *remote = remote_get(option_origin);
-               transport = transport_get(remote, remote->url[0]);
+       remote = remote_get(option_origin);
+       transport = transport_get(remote, remote->url[0]);
 
+       if (!is_local) {
                if (!transport->get_refs_list || !transport->fetch)
                        die(_("Don't know how to clone %s"), transport->url);
 
@@ -645,49 +790,57 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (option_depth)
                        transport_set_option(transport, TRANS_OPT_DEPTH,
                                             option_depth);
+               if (option_single_branch)
+                       transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
 
                transport_set_verbosity(transport, option_verbosity, option_progress);
 
                if (option_upload_pack)
                        transport_set_option(transport, TRANS_OPT_UPLOADPACK,
                                             option_upload_pack);
-
-               refs = transport_get_remote_refs(transport);
-               if (refs) {
-                       mapped_refs = wanted_peer_refs(refs, refspec);
-                       transport_fetch_refs(transport, mapped_refs);
-               }
        }
 
+       refs = transport_get_remote_refs(transport);
+
        if (refs) {
-               clear_extra_refs();
+               mapped_refs = wanted_peer_refs(refs, refspec);
+               /*
+                * transport_get_remote_refs() may return refs with null sha-1
+                * in mapped_refs (see struct transport->get_refs_list
+                * comment). In that case we need fetch it early because
+                * remote_head code below relies on it.
+                *
+                * for normal clones, transport_get_remote_refs() should
+                * return reliable ref set, we can delay cloning until after
+                * remote HEAD check.
+                */
+               for (ref = refs; ref; ref = ref->next)
+                       if (is_null_sha1(ref->old_sha1)) {
+                               complete_refs_before_fetch = 0;
+                               break;
+                       }
 
-               write_remote_refs(mapped_refs);
+               if (!is_local && !complete_refs_before_fetch)
+                       transport_fetch_refs(transport, mapped_refs);
 
                remote_head = find_ref_by_name(refs, "HEAD");
                remote_head_points_at =
                        guess_remote_head(remote_head, mapped_refs, 0);
 
                if (option_branch) {
-                       struct strbuf head = STRBUF_INIT;
-                       strbuf_addstr(&head, src_ref_prefix);
-                       strbuf_addstr(&head, option_branch);
                        our_head_points_at =
-                               find_ref_by_name(mapped_refs, head.buf);
-                       strbuf_release(&head);
-
-                       if (!our_head_points_at) {
-                               warning(_("Remote branch %s not found in "
-                                       "upstream %s, using HEAD instead"),
-                                       option_branch, option_origin);
-                               our_head_points_at = remote_head_points_at;
-                       }
+                               find_remote_branch(mapped_refs, option_branch);
+
+                       if (!our_head_points_at)
+                               die(_("Remote branch %s not found in upstream %s"),
+                                   option_branch, option_origin);
                }
                else
                        our_head_points_at = remote_head_points_at;
        }
        else {
                warning(_("You appear to have cloned an empty repository."));
+               mapped_refs = NULL;
                our_head_points_at = NULL;
                remote_head_points_at = NULL;
                remote_head = NULL;
@@ -697,84 +850,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                              "refs/heads/master");
        }
 
-       if (remote_head_points_at && !option_bare) {
-               struct strbuf head_ref = STRBUF_INIT;
-               strbuf_addstr(&head_ref, branch_top.buf);
-               strbuf_addstr(&head_ref, "HEAD");
-               create_symref(head_ref.buf,
-                             remote_head_points_at->peer_ref->name,
-                             reflog_msg.buf);
-       }
+       if (is_local)
+               clone_local(path, git_dir);
+       else if (refs && complete_refs_before_fetch)
+               transport_fetch_refs(transport, mapped_refs);
 
-       if (our_head_points_at) {
-               /* Local default branch link */
-               create_symref("HEAD", our_head_points_at->name, NULL);
-               if (!option_bare) {
-                       const char *head = skip_prefix(our_head_points_at->name,
-                                                      "refs/heads/");
-                       update_ref(reflog_msg.buf, "HEAD",
-                                  our_head_points_at->old_sha1,
-                                  NULL, 0, DIE_ON_ERR);
-                       install_branch_config(0, head, option_origin,
-                                             our_head_points_at->name);
-               }
-       } else if (remote_head) {
-               /* Source had detached HEAD pointing somewhere. */
-               if (!option_bare) {
-                       update_ref(reflog_msg.buf, "HEAD",
-                                  remote_head->old_sha1,
-                                  NULL, REF_NODEREF, DIE_ON_ERR);
-                       our_head_points_at = remote_head;
-               }
-       } else {
-               /* Nothing to checkout out */
-               if (!option_no_checkout)
-                       warning(_("remote HEAD refers to nonexistent ref, "
-                               "unable to checkout.\n"));
-               option_no_checkout = 1;
-       }
+       update_remote_refs(refs, mapped_refs, remote_head_points_at,
+                          branch_top.buf, reflog_msg.buf);
 
-       if (transport) {
-               transport_unlock_pack(transport);
-               transport_disconnect(transport);
-       }
+       update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
-       if (!option_no_checkout) {
-               struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-               struct unpack_trees_options opts;
-               struct tree *tree;
-               struct tree_desc t;
-               int fd;
-
-               /* We need to be in the new work tree for the checkout */
-               setup_work_tree();
-
-               fd = hold_locked_index(lock_file, 1);
-
-               memset(&opts, 0, sizeof opts);
-               opts.update = 1;
-               opts.merge = 1;
-               opts.fn = oneway_merge;
-               opts.verbose_update = (option_verbosity > 0);
-               opts.src_index = &the_index;
-               opts.dst_index = &the_index;
-
-               tree = parse_tree_indirect(our_head_points_at->old_sha1);
-               parse_tree(tree);
-               init_tree_desc(&t, tree->buffer, tree->size);
-               unpack_trees(1, &t, &opts);
-
-               if (write_cache(fd, active_cache, active_nr) ||
-                   commit_locked_index(lock_file))
-                       die(_("unable to write new index file"));
-
-               err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-                               sha1_to_hex(our_head_points_at->old_sha1), "1",
-                               NULL);
-
-               if (!err && option_recursive)
-                       err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
-       }
+       transport_unlock_pack(transport);
+       transport_disconnect(transport);
+
+       err = checkout();
 
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);
index eae5a29..3714582 100644 (file)
@@ -400,7 +400,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache_or_die(refresh_flags);
-               update_main_cache_tree(1);
+               update_main_cache_tree(WRITE_TREE_SILENT);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die(_("unable to write new_index file"));
@@ -421,7 +421,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
-                       update_main_cache_tree(1);
+                       update_main_cache_tree(WRITE_TREE_SILENT);
                        if (write_cache(fd, active_cache, active_nr) ||
                            commit_locked_index(&index_lock))
                                die(_("unable to write new_index file"));
index d35c06a..d41a9bf 100644 (file)
@@ -25,6 +25,7 @@ static const char *given_config_file;
 static int actions, types;
 static const char *get_color_slot, *get_colorbool_slot;
 static int end_null;
+static int respect_includes = -1;
 
 #define ACTION_GET (1<<0)
 #define ACTION_GET_ALL (1<<1)
@@ -74,6 +75,7 @@ static struct option builtin_config_options[] = {
        OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
        OPT_GROUP("Other"),
        OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+       OPT_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"),
        OPT_END(),
 };
 
@@ -161,8 +163,11 @@ static int get_value(const char *key_, const char *regex_)
        int ret = -1;
        char *global = NULL, *repo_config = NULL;
        const char *system_wide = NULL, *local;
+       struct config_include_data inc = CONFIG_INCLUDE_INIT;
+       config_fn_t fn;
+       void *data;
 
-       local = config_exclusive_filename;
+       local = given_config_file;
        if (!local) {
                const char *home = getenv("HOME");
                local = repo_config = git_pathdup("config");
@@ -213,19 +218,28 @@ static int get_value(const char *key_, const char *regex_)
                }
        }
 
+       fn = show_config;
+       data = NULL;
+       if (respect_includes) {
+               inc.fn = fn;
+               inc.data = data;
+               fn = git_config_include;
+               data = &inc;
+       }
+
        if (do_all && system_wide)
-               git_config_from_file(show_config, system_wide, NULL);
+               git_config_from_file(fn, system_wide, data);
        if (do_all && global)
-               git_config_from_file(show_config, global, NULL);
+               git_config_from_file(fn, global, data);
        if (do_all)
-               git_config_from_file(show_config, local, NULL);
-       git_config_from_parameters(show_config, NULL);
+               git_config_from_file(fn, local, data);
+       git_config_from_parameters(fn, data);
        if (!do_all && !seen)
-               git_config_from_file(show_config, local, NULL);
+               git_config_from_file(fn, local, data);
        if (!do_all && !seen && global)
-               git_config_from_file(show_config, global, NULL);
+               git_config_from_file(fn, global, data);
        if (!do_all && !seen && system_wide)
-               git_config_from_file(show_config, system_wide, NULL);
+               git_config_from_file(fn, system_wide, data);
 
        free(key);
        if (regexp) {
@@ -301,7 +315,8 @@ static void get_color(const char *def_color)
 {
        get_color_found = 0;
        parsed_color[0] = '\0';
-       git_config(git_get_color_config, NULL);
+       git_config_with_options(git_get_color_config, NULL,
+                               given_config_file, respect_includes);
 
        if (!get_color_found && def_color)
                color_parse(def_color, "command line", parsed_color);
@@ -328,7 +343,8 @@ static int get_colorbool(int print)
 {
        get_colorbool_found = -1;
        get_diff_color_found = -1;
-       git_config(git_get_colorbool_config, NULL);
+       git_config_with_options(git_get_colorbool_config, NULL,
+                               given_config_file, respect_includes);
 
        if (get_colorbool_found < 0) {
                if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -351,7 +367,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        int nongit = !startup_info->have_repository;
        char *value;
 
-       config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
+       given_config_file = getenv(CONFIG_ENVIRONMENT);
 
        argc = parse_options(argc, argv, prefix, builtin_config_options,
                             builtin_config_usage,
@@ -366,24 +382,28 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                char *home = getenv("HOME");
                if (home) {
                        char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-                       config_exclusive_filename = user_config;
+                       given_config_file = user_config;
                } else {
                        die("$HOME not set");
                }
        }
        else if (use_system_config)
-               config_exclusive_filename = git_etc_gitconfig();
+               given_config_file = git_etc_gitconfig();
        else if (use_local_config)
-               config_exclusive_filename = git_pathdup("config");
+               given_config_file = git_pathdup("config");
        else if (given_config_file) {
                if (!is_absolute_path(given_config_file) && prefix)
-                       config_exclusive_filename = prefix_filename(prefix,
-                                                                   strlen(prefix),
-                                                                   given_config_file);
+                       given_config_file =
+                               xstrdup(prefix_filename(prefix,
+                                                       strlen(prefix),
+                                                       given_config_file));
                else
-                       config_exclusive_filename = given_config_file;
+                       given_config_file = given_config_file;
        }
 
+       if (respect_includes == -1)
+               respect_includes = !given_config_file;
+
        if (end_null) {
                term = '\0';
                delim = '\n';
@@ -420,28 +440,30 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 
        if (actions == ACTION_LIST) {
                check_argc(argc, 0, 0);
-               if (git_config(show_all_config, NULL) < 0) {
-                       if (config_exclusive_filename)
+               if (git_config_with_options(show_all_config, NULL,
+                                           given_config_file,
+                                           respect_includes) < 0) {
+                       if (given_config_file)
                                die_errno("unable to read config file '%s'",
-                                         config_exclusive_filename);
+                                         given_config_file);
                        else
                                die("error processing config file(s)");
                }
        }
        else if (actions == ACTION_EDIT) {
                check_argc(argc, 0, 0);
-               if (!config_exclusive_filename && nongit)
+               if (!given_config_file && nongit)
                        die("not in a git directory");
                git_config(git_default_config, NULL);
-               launch_editor(config_exclusive_filename ?
-                             config_exclusive_filename : git_path("config"),
+               launch_editor(given_config_file ?
+                             given_config_file : git_path("config"),
                              NULL, NULL);
        }
        else if (actions == ACTION_SET) {
                int ret;
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
-               ret = git_config_set(argv[0], value);
+               ret = git_config_set_in_file(given_config_file, argv[0], value);
                if (ret == CONFIG_NOTHING_SET)
                        error("cannot overwrite multiple values with a single value\n"
                        "       Use a regexp, --add or --replace-all to change %s.", argv[0]);
@@ -450,17 +472,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        else if (actions == ACTION_SET_ALL) {
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, argv[2], 0);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], value, argv[2], 0);
        }
        else if (actions == ACTION_ADD) {
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, "^$", 0);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], value, "^$", 0);
        }
        else if (actions == ACTION_REPLACE_ALL) {
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
-               return git_config_set_multivar(argv[0], value, argv[2], 1);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], value, argv[2], 1);
        }
        else if (actions == ACTION_GET) {
                check_argc(argc, 1, 2);
@@ -481,18 +506,22 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        else if (actions == ACTION_UNSET) {
                check_argc(argc, 1, 2);
                if (argc == 2)
-                       return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+                       return git_config_set_multivar_in_file(given_config_file,
+                                                              argv[0], NULL, argv[1], 0);
                else
-                       return git_config_set(argv[0], NULL);
+                       return git_config_set_in_file(given_config_file,
+                                                     argv[0], NULL);
        }
        else if (actions == ACTION_UNSET_ALL) {
                check_argc(argc, 1, 2);
-               return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+               return git_config_set_multivar_in_file(given_config_file,
+                                                      argv[0], NULL, argv[1], 1);
        }
        else if (actions == ACTION_RENAME_SECTION) {
                int ret;
                check_argc(argc, 2, 2);
-               ret = git_config_rename_section(argv[0], argv[1]);
+               ret = git_config_rename_section_in_file(given_config_file,
+                                                       argv[0], argv[1]);
                if (ret < 0)
                        return ret;
                if (ret == 0)
@@ -501,7 +530,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        else if (actions == ACTION_REMOVE_SECTION) {
                int ret;
                check_argc(argc, 1, 1);
-               ret = git_config_rename_section(argv[0], NULL);
+               ret = git_config_rename_section_in_file(given_config_file,
+                                                       argv[0], NULL);
                if (ret < 0)
                        return ret;
                if (ret == 0)
index 387afa7..424c815 100644 (file)
@@ -285,6 +285,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        /* Otherwise, we are doing the usual "git" diff */
        rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
 
+       /* Scale to real terminal size and respect statGraphWidth config */
+       rev.diffopt.stat_width = -1;
+       rev.diffopt.stat_graph_width = -1;
+
        /* Default to let external and textconv be used */
        DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
index 08fed98..19509ea 100644 (file)
@@ -647,9 +647,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                             "Output full tree for each commit"),
                OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
                             "Use the done feature to terminate the stream"),
-               { OPTION_NEGBIT, 0, "data", &no_data, NULL,
-                       "Skip output of blob data",
-                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
+               OPT_BOOL(0, "no-data", &no_data, "Skip output of blob data"),
                OPT_END()
        };
 
index a4d3e90..7124c4b 100644 (file)
@@ -58,9 +58,9 @@ static void rev_list_push(struct commit *commit, int mark)
        }
 }
 
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
+       struct object *o = deref_tag(parse_object(sha1), refname, 0);
 
        if (o && o->type == OBJ_COMMIT)
                rev_list_push((struct commit *)o, SEEN);
@@ -68,9 +68,9 @@ static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int
        return 0;
 }
 
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *o = deref_tag(parse_object(sha1), path, 0);
+       struct object *o = deref_tag(parse_object(sha1), refname, 0);
 
        if (o && o->type == OBJ_COMMIT)
                clear_commit_marks((struct commit *)o,
@@ -256,11 +256,6 @@ static void insert_one_alternate_ref(const struct ref *ref, void *unused)
        rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
 }
 
-static void insert_alternate_refs(void)
-{
-       for_each_alternate_ref(insert_one_alternate_ref, NULL);
-}
-
 #define INITIAL_FLUSH 16
 #define PIPESAFE_FLUSH 32
 #define LARGE_FLUSH 1024
@@ -295,7 +290,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        marked = 1;
 
        for_each_ref(rev_list_insert_ref, NULL);
-       insert_alternate_refs();
+       for_each_alternate_ref(insert_one_alternate_ref, NULL);
 
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
@@ -493,7 +488,7 @@ done:
 
 static struct commit_list *complete;
 
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = parse_object(sha1);
 
@@ -586,6 +581,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
        *refs = newlist;
 }
 
+static void mark_alternate_complete(const struct ref *ref, void *unused)
+{
+       mark_complete(NULL, ref->old_sha1, 0, NULL);
+}
+
 static int everything_local(struct ref **refs, int nr_match, char **match)
 {
        struct ref *ref;
@@ -614,6 +614,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
 
        if (!args.depth) {
                for_each_ref(mark_complete, NULL);
+               for_each_alternate_ref(mark_alternate_complete, NULL);
                if (cutoff)
                        mark_recent_complete_commits(cutoff);
        }
index 8ec4eae..65f5f9b 100644 (file)
@@ -585,7 +585,7 @@ static void find_non_local_tags(struct transport *transport,
 
        for_each_ref(add_existing, &existing_refs);
        for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
-               if (prefixcmp(ref->name, "refs/tags"))
+               if (prefixcmp(ref->name, "refs/tags/"))
                        continue;
 
                /*
index 8c479a7..67eb553 100644 (file)
@@ -29,6 +29,7 @@ static int errors_found;
 static int write_lost_and_found;
 static int verbose;
 static int show_progress = -1;
+static int show_dangling = 1;
 #define ERROR_OBJECT 01
 #define ERROR_REACHABLE 02
 #define ERROR_PACK 04
@@ -221,8 +222,9 @@ static void check_unreachable_object(struct object *obj)
         * start looking at, for example.
         */
        if (!obj->used) {
-               printf("dangling %s %s\n", typename(obj->type),
-                      sha1_to_hex(obj->sha1));
+               if (show_dangling)
+                       printf("dangling %s %s\n", typename(obj->type),
+                              sha1_to_hex(obj->sha1));
                if (write_lost_and_found) {
                        char *filename = git_path("lost-found/%s/%s",
                                obj->type == OBJ_COMMIT ? "commit" : "other",
@@ -614,6 +616,7 @@ static char const * const fsck_usage[] = {
 static struct option fsck_opts[] = {
        OPT__VERBOSE(&verbose, "be verbose"),
        OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
+       OPT_BOOL(0, "dangling", &show_dangling, "show dangling objects"),
        OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
        OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
        OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
index 9fc3e95..643938d 100644 (file)
@@ -265,11 +265,8 @@ static int grep_config(const char *var, const char *value, void *cb)
        struct grep_opt *opt = cb;
        char *color = NULL;
 
-       switch (userdiff_config(var, value)) {
-       case 0: break;
-       case -1: return -1;
-       default: return 0;
-       }
+       if (userdiff_config(var, value) < 0)
+               return -1;
 
        if (!strcmp(var, "grep.extendedregexp")) {
                if (git_config_bool(var, value))
@@ -687,9 +684,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT_BOOLEAN(0, "cached", &cached,
                        "search in index instead of in the work tree"),
-               { OPTION_BOOLEAN, 0, "index", &use_index, NULL,
-                       "finds in contents not managed by git",
-                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP },
+               OPT_NEGBIT(0, "no-index", &use_index,
+                        "finds in contents not managed by git", 1),
                OPT_BOOLEAN(0, "untracked", &untracked,
                        "search in both tracked and untracked files"),
                OPT_SET_INT(0, "exclude-standard", &opt_exclude,
index af7dc37..dd1c5c9 100644 (file)
@@ -34,6 +34,8 @@ struct base_data {
        struct object_entry *obj;
        void *data;
        unsigned long size;
+       int ref_first, ref_last;
+       int ofs_first, ofs_last;
 };
 
 /*
@@ -221,6 +223,15 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
        die("pack has bad object at offset %lu: %s", offset, buf);
 }
 
+static struct base_data *alloc_base_data(void)
+{
+       struct base_data *base = xmalloc(sizeof(struct base_data));
+       memset(base, 0, sizeof(*base));
+       base->ref_last = -1;
+       base->ofs_last = -1;
+       return base;
+}
+
 static void free_base_data(struct base_data *c)
 {
        if (c->data) {
@@ -504,14 +515,52 @@ static int is_delta_type(enum object_type type)
        return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
 }
 
+/*
+ * This function is part of find_unresolved_deltas(). There are two
+ * walkers going in the opposite ways.
+ *
+ * The first one in find_unresolved_deltas() traverses down from
+ * parent node to children, deflating nodes along the way. However,
+ * memory for deflated nodes is limited by delta_base_cache_limit, so
+ * at some point parent node's deflated content may be freed.
+ *
+ * The second walker is this function, which goes from current node up
+ * to top parent if necessary to deflate the node. In normal
+ * situation, its parent node would be already deflated, so it just
+ * needs to apply delta.
+ *
+ * In the worst case scenario, parent node is no longer deflated because
+ * we're running out of delta_base_cache_limit; we need to re-deflate
+ * parents, possibly up to the top base.
+ *
+ * All deflated objects here are subject to be freed if we exceed
+ * delta_base_cache_limit, just like in find_unresolved_deltas(), we
+ * just need to make sure the last node is not freed.
+ */
 static void *get_base_data(struct base_data *c)
 {
        if (!c->data) {
                struct object_entry *obj = c->obj;
+               struct base_data **delta = NULL;
+               int delta_nr = 0, delta_alloc = 0;
 
-               if (is_delta_type(obj->type)) {
-                       void *base = get_base_data(c->base);
-                       void *raw = get_data_from_pack(obj);
+               while (is_delta_type(c->obj->type) && !c->data) {
+                       ALLOC_GROW(delta, delta_nr + 1, delta_alloc);
+                       delta[delta_nr++] = c;
+                       c = c->base;
+               }
+               if (!delta_nr) {
+                       c->data = get_data_from_pack(obj);
+                       c->size = obj->size;
+                       base_cache_used += c->size;
+                       prune_base_data(c);
+               }
+               for (; delta_nr > 0; delta_nr--) {
+                       void *base, *raw;
+                       c = delta[delta_nr - 1];
+                       obj = c->obj;
+                       base = get_base_data(c->base);
+                       raw = get_data_from_pack(obj);
                        c->data = patch_delta(
                                base, c->base->size,
                                raw, obj->size,
@@ -519,13 +568,10 @@ static void *get_base_data(struct base_data *c)
                        free(raw);
                        if (!c->data)
                                bad_object(obj->idx.offset, "failed to apply delta");
-               } else {
-                       c->data = get_data_from_pack(obj);
-                       c->size = obj->size;
+                       base_cache_used += c->size;
+                       prune_base_data(c);
                }
-
-               base_cache_used += c->size;
-               prune_base_data(c);
+               free(delta);
        }
        return c->data;
 }
@@ -553,58 +599,76 @@ static void resolve_delta(struct object_entry *delta_obj,
        nr_resolved_deltas++;
 }
 
-static void find_unresolved_deltas(struct base_data *base,
-                                  struct base_data *prev_base)
+static struct base_data *find_unresolved_deltas_1(struct base_data *base,
+                                                 struct base_data *prev_base)
 {
-       int i, ref_first, ref_last, ofs_first, ofs_last;
-
-       /*
-        * This is a recursive function. Those brackets should help reducing
-        * stack usage by limiting the scope of the delta_base union.
-        */
-       {
+       if (base->ref_last == -1 && base->ofs_last == -1) {
                union delta_base base_spec;
 
                hashcpy(base_spec.sha1, base->obj->idx.sha1);
                find_delta_children(&base_spec,
-                                   &ref_first, &ref_last, OBJ_REF_DELTA);
+                                   &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
 
                memset(&base_spec, 0, sizeof(base_spec));
                base_spec.offset = base->obj->idx.offset;
                find_delta_children(&base_spec,
-                                   &ofs_first, &ofs_last, OBJ_OFS_DELTA);
-       }
+                                   &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
 
-       if (ref_last == -1 && ofs_last == -1) {
-               free(base->data);
-               return;
-       }
+               if (base->ref_last == -1 && base->ofs_last == -1) {
+                       free(base->data);
+                       return NULL;
+               }
 
-       link_base_data(prev_base, base);
+               link_base_data(prev_base, base);
+       }
 
-       for (i = ref_first; i <= ref_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               struct base_data result;
+       if (base->ref_first <= base->ref_last) {
+               struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+               struct base_data *result = alloc_base_data();
 
                assert(child->real_type == OBJ_REF_DELTA);
-               resolve_delta(child, base, &result);
-               if (i == ref_last && ofs_last == -1)
+               resolve_delta(child, base, result);
+               if (base->ref_first == base->ref_last && base->ofs_last == -1)
                        free_base_data(base);
-               find_unresolved_deltas(&result, base);
+
+               base->ref_first++;
+               return result;
        }
 
-       for (i = ofs_first; i <= ofs_last; i++) {
-               struct object_entry *child = objects + deltas[i].obj_no;
-               struct base_data result;
+       if (base->ofs_first <= base->ofs_last) {
+               struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+               struct base_data *result = alloc_base_data();
 
                assert(child->real_type == OBJ_OFS_DELTA);
-               resolve_delta(child, base, &result);
-               if (i == ofs_last)
+               resolve_delta(child, base, result);
+               if (base->ofs_first == base->ofs_last)
                        free_base_data(base);
-               find_unresolved_deltas(&result, base);
+
+               base->ofs_first++;
+               return result;
        }
 
        unlink_base_data(base);
+       return NULL;
+}
+
+static void find_unresolved_deltas(struct base_data *base)
+{
+       struct base_data *new_base, *prev_base = NULL;
+       for (;;) {
+               new_base = find_unresolved_deltas_1(base, prev_base);
+
+               if (new_base) {
+                       prev_base = base;
+                       base = new_base;
+               } else {
+                       free(base);
+                       base = prev_base;
+                       if (!base)
+                               return;
+                       prev_base = base->base;
+               }
+       }
 }
 
 static int compare_delta_entry(const void *a, const void *b)
@@ -684,13 +748,13 @@ static void parse_pack_objects(unsigned char *sha1)
                progress = start_progress("Resolving deltas", nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               struct base_data base_obj;
+               struct base_data *base_obj = alloc_base_data();
 
                if (is_delta_type(obj->type))
                        continue;
-               base_obj.obj = obj;
-               base_obj.data = NULL;
-               find_unresolved_deltas(&base_obj, NULL);
+               base_obj->obj = obj;
+               base_obj->data = NULL;
+               find_unresolved_deltas(base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
 }
@@ -783,20 +847,20 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
        for (i = 0; i < n; i++) {
                struct delta_entry *d = sorted_by_pos[i];
                enum object_type type;
-               struct base_data base_obj;
+               struct base_data *base_obj = alloc_base_data();
 
                if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
                        continue;
-               base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
-               if (!base_obj.data)
+               base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+               if (!base_obj->data)
                        continue;
 
-               if (check_sha1_signature(d->base.sha1, base_obj.data,
-                               base_obj.size, typename(type)))
+               if (check_sha1_signature(d->base.sha1, base_obj->data,
+                               base_obj->size, typename(type)))
                        die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
-                                       base_obj.data, base_obj.size, type);
-               find_unresolved_deltas(&base_obj, NULL);
+               base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+                                       base_obj->data, base_obj->size, type);
+               find_unresolved_deltas(base_obj);
                display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
index 7d1f6f8..8a47012 100644 (file)
@@ -77,6 +77,8 @@ static void cmd_log_init_defaults(struct rev_info *rev)
                get_commit_format(fmt_pretty, rev);
        rev->verbose_header = 1;
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+       rev->diffopt.stat_width = -1; /* use full terminal width */
+       rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */
        rev->abbrev_commit = default_abbrev_commit;
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
@@ -447,6 +449,8 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        rev.diff = 1;
        rev.always_show_header = 1;
        rev.no_walk = 1;
+       rev.diffopt.stat_width = -1;    /* Scale to real terminal size */
+
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
        opt.tweak = show_rev_tweak_rev;
index bfb32b7..eaf9e15 100644 (file)
@@ -250,8 +250,17 @@ static void cleanup_subject(struct strbuf *subject)
                            (7 <= remove &&
                             memmem(subject->buf + at, remove, "PATCH", 5)))
                                strbuf_remove(subject, at, remove);
-                       else
+                       else {
                                at += remove;
+                               /*
+                                * If the input had a space after the ], keep
+                                * it.  We don't bother with finding the end of
+                                * the space, since we later normalize it
+                                * anyway.
+                                */
+                               if (isspace(subject->buf[at]))
+                                       at += 1;
+                       }
                        continue;
                }
                break;
index 8018a14..cb8f149 100644 (file)
@@ -399,6 +399,8 @@ static void finish(struct commit *head_commit,
        if (new_head && show_diffstat) {
                struct diff_options opts;
                diff_setup(&opts);
+               opts.stat_width = -1; /* use full terminal width */
+               opts.stat_graph_width = -1; /* respect statGraphWidth config */
                opts.output_format |=
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
@@ -903,12 +905,12 @@ static void prepare_to_commit(void)
        write_merge_msg(&msg);
        run_hook(get_index_file(), "prepare-commit-msg",
                 git_path("MERGE_MSG"), "merge", NULL, NULL);
-       if (option_edit) {
+       if (0 < option_edit) {
                if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
                        abort_commit(NULL);
        }
        read_merge_msg(&msg);
-       stripspace(&msg, option_edit);
+       stripspace(&msg, 0 < option_edit);
        if (!msg.len)
                abort_commit(_("Empty commit message."));
        strbuf_release(&merge_msg);
@@ -1109,6 +1111,33 @@ static void write_merge_state(void)
        close(fd);
 }
 
+static int default_edit_option(void)
+{
+       static const char name[] = "GIT_MERGE_AUTOEDIT";
+       const char *e = getenv(name);
+       struct stat st_stdin, st_stdout;
+
+       if (have_message)
+               /* an explicit -m msg without --[no-]edit */
+               return 0;
+
+       if (e) {
+               int v = git_config_maybe_bool(name, e);
+               if (v < 0)
+                       die("Bad value '%s' in environment '%s'", e, name);
+               return v;
+       }
+
+       /* Use editor if stdin and stdout are the same and is a tty */
+       return (!fstat(0, &st_stdin) &&
+               !fstat(1, &st_stdout) &&
+               isatty(0) && isatty(1) &&
+               st_stdin.st_dev == st_stdout.st_dev &&
+               st_stdin.st_ino == st_stdout.st_ino &&
+               st_stdin.st_mode == st_stdout.st_mode);
+}
+
+
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
        unsigned char result_tree[20];
@@ -1304,7 +1333,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        }
 
        if (option_edit < 0)
-               option_edit = 0;
+               option_edit = default_edit_option();
 
        if (!use_strategies) {
                if (!remoteheads->next)
index 0f2e7b8..7b07c09 100644 (file)
 #include "refs.h"
 #include "thread-utils.h"
 
-static const char pack_usage[] =
-  "git pack-objects [ -q | --progress | --all-progress ]\n"
-  "        [--all-progress-implied]\n"
-  "        [--max-pack-size=<n>] [--local] [--incremental]\n"
-  "        [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n"
-  "        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
-  "        [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n"
-  "        [--reflog] [--stdout | base-name] [--include-tag]\n"
-  "        [--keep-unreachable | --unpack-unreachable]\n"
-  "        [< ref-list | < object-list]";
+static const char *pack_usage[] = {
+       "git pack-objects --stdout [options...] [< ref-list | < object-list]",
+       "git pack-objects [options...] base-name [< ref-list | < object-list]",
+       NULL
+};
 
 struct object_entry {
        struct pack_idx_entry idx;
@@ -2305,204 +2300,159 @@ static void get_object_list(int ac, const char **av)
                loosen_unused_packed_objects(&revs);
 }
 
+static int option_parse_index_version(const struct option *opt,
+                                     const char *arg, int unset)
+{
+       char *c;
+       const char *val = arg;
+       pack_idx_opts.version = strtoul(val, &c, 10);
+       if (pack_idx_opts.version > 2)
+               die(_("unsupported index version %s"), val);
+       if (*c == ',' && c[1])
+               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+       if (*c || pack_idx_opts.off32_limit & 0x80000000)
+               die(_("bad index version '%s'"), val);
+       return 0;
+}
+
+static int option_parse_ulong(const struct option *opt,
+                             const char *arg, int unset)
+{
+       if (unset)
+               die(_("option %s does not accept negative form"),
+                   opt->long_name);
+
+       if (!git_parse_ulong(arg, opt->value))
+               die(_("unable to parse value '%s' for option %s"),
+                   arg, opt->long_name);
+       return 0;
+}
+
+#define OPT_ULONG(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "n", (h),     \
+         PARSE_OPT_NONEG, option_parse_ulong }
+
 int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 {
        int use_internal_rev_list = 0;
        int thin = 0;
        int all_progress_implied = 0;
-       uint32_t i;
-       const char **rp_av;
-       int rp_ac_alloc = 64;
-       int rp_ac;
+       const char *rp_av[6];
+       int rp_ac = 0;
+       int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
+       struct option pack_objects_options[] = {
+               OPT_SET_INT('q', "quiet", &progress,
+                           "do not show progress meter", 0),
+               OPT_SET_INT(0, "progress", &progress,
+                           "show progress meter", 1),
+               OPT_SET_INT(0, "all-progress", &progress,
+                           "show progress meter during object writing phase", 2),
+               OPT_BOOL(0, "all-progress-implied",
+                        &all_progress_implied,
+                        "similar to --all-progress when progress meter is shown"),
+               { OPTION_CALLBACK, 0, "index-version", NULL, "version[,offset]",
+                 "write the pack index file in the specified idx format version",
+                 0, option_parse_index_version },
+               OPT_ULONG(0, "max-pack-size", &pack_size_limit,
+                         "maximum size of each output pack file"),
+               OPT_BOOL(0, "local", &local,
+                        "ignore borrowed objects from alternate object store"),
+               OPT_BOOL(0, "incremental", &incremental,
+                        "ignore packed objects"),
+               OPT_INTEGER(0, "window", &window,
+                           "limit pack window by objects"),
+               OPT_ULONG(0, "window-memory", &window_memory_limit,
+                         "limit pack window by memory in addition to object limit"),
+               OPT_INTEGER(0, "depth", &depth,
+                           "maximum length of delta chain allowed in the resulting pack"),
+               OPT_BOOL(0, "reuse-delta", &reuse_delta,
+                        "reuse existing deltas"),
+               OPT_BOOL(0, "reuse-object", &reuse_object,
+                        "reuse existing objects"),
+               OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta,
+                        "use OFS_DELTA objects"),
+               OPT_INTEGER(0, "threads", &delta_search_threads,
+                           "use threads when searching for best delta matches"),
+               OPT_BOOL(0, "non-empty", &non_empty,
+                        "do not create an empty pack output"),
+               OPT_BOOL(0, "revs", &use_internal_rev_list,
+                        "read revision arguments from standard input"),
+               { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL,
+                 "limit the objects to those that are not yet packed",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               { OPTION_SET_INT, 0, "all", &rev_list_all, NULL,
+                 "include objects reachable from any reference",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL,
+                 "include objects referred by reflog entries",
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+               OPT_BOOL(0, "stdout", &pack_to_stdout,
+                        "output pack to stdout"),
+               OPT_BOOL(0, "include-tag", &include_tag,
+                        "include tag objects that refer to objects to be packed"),
+               OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
+                        "keep unreachable objects"),
+               OPT_BOOL(0, "unpack-unreachable", &unpack_unreachable,
+                        "unpack unreachable objects"),
+               OPT_BOOL(0, "thin", &thin,
+                        "create thin packs"),
+               OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
+                        "ignore packs that have companion .keep file"),
+               OPT_INTEGER(0, "compression", &pack_compression_level,
+                           "pack compression level"),
+               OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents,
+                           "do not hide commits by grafts", 0),
+               OPT_END(),
+       };
 
        read_replace_refs = 0;
 
-       rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
-       rp_av[0] = "pack-objects";
-       rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
-       rp_ac = 2;
-
        reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
 
        progress = isatty(2);
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
+       argc = parse_options(argc, argv, prefix, pack_objects_options,
+                            pack_usage, 0);
 
-               if (!strcmp("--non-empty", arg)) {
-                       non_empty = 1;
-                       continue;
-               }
-               if (!strcmp("--local", arg)) {
-                       local = 1;
-                       continue;
-               }
-               if (!strcmp("--incremental", arg)) {
-                       incremental = 1;
-                       continue;
-               }
-               if (!strcmp("--honor-pack-keep", arg)) {
-                       ignore_packed_keep = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--compression=")) {
-                       char *end;
-                       int level = strtoul(arg+14, &end, 0);
-                       if (!arg[14] || *end)
-                               usage(pack_usage);
-                       if (level == -1)
-                               level = Z_DEFAULT_COMPRESSION;
-                       else if (level < 0 || level > Z_BEST_COMPRESSION)
-                               die("bad pack compression level %d", level);
-                       pack_compression_level = level;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--max-pack-size=")) {
-                       pack_size_limit_cfg = 0;
-                       if (!git_parse_ulong(arg+16, &pack_size_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window=")) {
-                       char *end;
-                       window = strtoul(arg+9, &end, 0);
-                       if (!arg[9] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--window-memory=")) {
-                       if (!git_parse_ulong(arg+16, &window_memory_limit))
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--threads=")) {
-                       char *end;
-                       delta_search_threads = strtoul(arg+10, &end, 0);
-                       if (!arg[10] || *end || delta_search_threads < 0)
-                               usage(pack_usage);
-#ifdef NO_PTHREADS
-                       if (delta_search_threads != 1)
-                               warning("no threads support, "
-                                       "ignoring %s", arg);
-#endif
-                       continue;
-               }
-               if (!prefixcmp(arg, "--depth=")) {
-                       char *end;
-                       depth = strtoul(arg+8, &end, 0);
-                       if (!arg[8] || *end)
-                               usage(pack_usage);
-                       continue;
-               }
-               if (!strcmp("--progress", arg)) {
-                       progress = 1;
-                       continue;
-               }
-               if (!strcmp("--all-progress", arg)) {
-                       progress = 2;
-                       continue;
-               }
-               if (!strcmp("--all-progress-implied", arg)) {
-                       all_progress_implied = 1;
-                       continue;
-               }
-               if (!strcmp("-q", arg)) {
-                       progress = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-delta", arg)) {
-                       reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--no-reuse-object", arg)) {
-                       reuse_object = reuse_delta = 0;
-                       continue;
-               }
-               if (!strcmp("--delta-base-offset", arg)) {
-                       allow_ofs_delta = 1;
-                       continue;
-               }
-               if (!strcmp("--stdout", arg)) {
-                       pack_to_stdout = 1;
-                       continue;
-               }
-               if (!strcmp("--revs", arg)) {
-                       use_internal_rev_list = 1;
-                       continue;
-               }
-               if (!strcmp("--keep-unreachable", arg)) {
-                       keep_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--unpack-unreachable", arg)) {
-                       unpack_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp("--include-tag", arg)) {
-                       include_tag = 1;
-                       continue;
-               }
-               if (!strcmp("--unpacked", arg) ||
-                   !strcmp("--reflog", arg) ||
-                   !strcmp("--all", arg)) {
-                       use_internal_rev_list = 1;
-                       if (rp_ac >= rp_ac_alloc - 1) {
-                               rp_ac_alloc = alloc_nr(rp_ac_alloc);
-                               rp_av = xrealloc(rp_av,
-                                                rp_ac_alloc * sizeof(*rp_av));
-                       }
-                       rp_av[rp_ac++] = arg;
-                       continue;
-               }
-               if (!strcmp("--thin", arg)) {
-                       use_internal_rev_list = 1;
-                       thin = 1;
-                       rp_av[1] = "--objects-edge";
-                       continue;
-               }
-               if (!prefixcmp(arg, "--index-version=")) {
-                       char *c;
-                       pack_idx_opts.version = strtoul(arg + 16, &c, 10);
-                       if (pack_idx_opts.version > 2)
-                               die("bad %s", arg);
-                       if (*c == ',')
-                               pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
-                       if (*c || pack_idx_opts.off32_limit & 0x80000000)
-                               die("bad %s", arg);
-                       continue;
-               }
-               if (!strcmp(arg, "--keep-true-parents")) {
-                       grafts_replace_parents = 0;
-                       continue;
-               }
-               usage(pack_usage);
-       }
-
-       /* Traditionally "pack-objects [options] base extra" failed;
-        * we would however want to take refs parameter that would
-        * have been given to upstream rev-list ourselves, which means
-        * we somehow want to say what the base name is.  So the
-        * syntax would be:
-        *
-        * pack-objects [options] base <refs...>
-        *
-        * in other words, we would treat the first non-option as the
-        * base_name and send everything else to the internal revision
-        * walker.
-        */
+       if (argc) {
+               base_name = argv[0];
+               argc--;
+       }
+       if (pack_to_stdout != !base_name || argc)
+               usage_with_options(pack_usage, pack_objects_options);
 
-       if (!pack_to_stdout)
-               base_name = argv[i++];
+       rp_av[rp_ac++] = "pack-objects";
+       if (thin) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--objects-edge";
+       } else
+               rp_av[rp_ac++] = "--objects";
 
-       if (pack_to_stdout != !base_name)
-               usage(pack_usage);
+       if (rev_list_all) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--all";
+       }
+       if (rev_list_reflog) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--reflog";
+       }
+       if (rev_list_unpacked) {
+               use_internal_rev_list = 1;
+               rp_av[rp_ac++] = "--unpacked";
+       }
 
+       if (!reuse_object)
+               reuse_delta = 0;
+       if (pack_compression_level == -1)
+               pack_compression_level = Z_DEFAULT_COMPRESSION;
+       else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)
+               die("bad pack compression level %d", pack_compression_level);
+#ifdef NO_PTHREADS
+       if (delta_search_threads != 1)
+               warning("no threads support, ignoring --threads");
+#endif
        if (!pack_to_stdout && !pack_size_limit)
                pack_size_limit = pack_size_limit_cfg;
        if (pack_to_stdout && pack_size_limit)
index f9463de..b58a2e1 100644 (file)
@@ -35,8 +35,6 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
                        unlink_or_warn(pathname);
                display_progress(progress, i + 1);
        }
-       pathname[len] = 0;
-       rmdir(pathname);
 }
 
 void prune_packed_objects(int opts)
@@ -65,6 +63,8 @@ void prune_packed_objects(int opts)
                        continue;
                prune_dir(i, d, pathname, len + 3, opts);
                closedir(d);
+               pathname[len + 2] = '\0';
+               rmdir(pathname);
        }
        stop_progress(&progress);
 }
index 58d7cb8..b99b635 100644 (file)
@@ -85,9 +85,9 @@ static int prune_dir(int i, char *path)
                }
                fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
        }
+       closedir(dir);
        if (!show_only)
                rmdir(path);
-       closedir(dir);
        return 0;
 }
 
index 6c373cf..d315475 100644 (file)
@@ -261,6 +261,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
                        TRANSPORT_PUSH_SET_UPSTREAM),
                OPT_BOOL(0, "progress", &progress, "force progress reporting"),
+               OPT_BIT(0, "prune", &flags, "prune locally removed refs",
+                       TRANSPORT_PUSH_PRUNE),
                OPT_END()
        };
 
index adc456e..fec92bc 100644 (file)
@@ -534,7 +534,7 @@ static int add_branch_for_removal(const char *refname,
        }
 
        /* don't delete non-remote-tracking refs */
-       if (prefixcmp(refname, "refs/remotes")) {
+       if (prefixcmp(refname, "refs/remotes/")) {
                /* advise user how to delete local branches */
                if (!prefixcmp(refname, "refs/heads/"))
                        string_list_append(branches->skipped,
index 0d8020c..e6840f2 100644 (file)
@@ -1,18 +1,9 @@
 #include "cache.h"
 #include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
 #include "parse-options.h"
-#include "cache-tree.h"
 #include "diff.h"
 #include "revision.h"
 #include "rerere.h"
-#include "merge-recursive.h"
-#include "refs.h"
 #include "dir.h"
 #include "sequencer.h"
 
@@ -39,49 +30,14 @@ static const char * const cherry_pick_usage[] = {
        NULL
 };
 
-enum replay_action { REVERT, CHERRY_PICK };
-enum replay_subcommand {
-       REPLAY_NONE,
-       REPLAY_REMOVE_STATE,
-       REPLAY_CONTINUE,
-       REPLAY_ROLLBACK
-};
-
-struct replay_opts {
-       enum replay_action action;
-       enum replay_subcommand subcommand;
-
-       /* Boolean options */
-       int edit;
-       int record_origin;
-       int no_commit;
-       int signoff;
-       int allow_ff;
-       int allow_rerere_auto;
-
-       int mainline;
-
-       /* Merge strategy */
-       const char *strategy;
-       const char **xopts;
-       size_t xopts_nr, xopts_alloc;
-
-       /* Only used by REPLAY_NONE */
-       struct rev_info *revs;
-};
-
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
 static const char *action_name(const struct replay_opts *opts)
 {
-       return opts->action == REVERT ? "revert" : "cherry-pick";
+       return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
 }
 
-static char *get_encoding(const char *message);
-
 static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
 {
-       return opts->action == REVERT ? revert_usage : cherry_pick_usage;
+       return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
 }
 
 static int option_parse_x(const struct option *opt,
@@ -160,7 +116,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                OPT_END(),
        };
 
-       if (opts->action == CHERRY_PICK) {
+       if (opts->action == REPLAY_PICK) {
                struct option cp_extra[] = {
                        OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
                        OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
@@ -237,902 +193,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                usage_with_options(usage_str, options);
 }
 
-struct commit_message {
-       char *parent_label;
-       const char *label;
-       const char *subject;
-       char *reencoded_message;
-       const char *message;
-};
-
-static int get_message(struct commit *commit, struct commit_message *out)
-{
-       const char *encoding;
-       const char *abbrev, *subject;
-       int abbrev_len, subject_len;
-       char *q;
-
-       if (!commit->buffer)
-               return -1;
-       encoding = get_encoding(commit->buffer);
-       if (!encoding)
-               encoding = "UTF-8";
-       if (!git_commit_encoding)
-               git_commit_encoding = "UTF-8";
-
-       out->reencoded_message = NULL;
-       out->message = commit->buffer;
-       if (strcmp(encoding, git_commit_encoding))
-               out->reencoded_message = reencode_string(commit->buffer,
-                                       git_commit_encoding, encoding);
-       if (out->reencoded_message)
-               out->message = out->reencoded_message;
-
-       abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
-       abbrev_len = strlen(abbrev);
-
-       subject_len = find_commit_subject(out->message, &subject);
-
-       out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
-                             strlen("... ") + subject_len + 1);
-       q = out->parent_label;
-       q = mempcpy(q, "parent of ", strlen("parent of "));
-       out->label = q;
-       q = mempcpy(q, abbrev, abbrev_len);
-       q = mempcpy(q, "... ", strlen("... "));
-       out->subject = q;
-       q = mempcpy(q, subject, subject_len);
-       *q = '\0';
-       return 0;
-}
-
-static void free_message(struct commit_message *msg)
-{
-       free(msg->parent_label);
-       free(msg->reencoded_message);
-}
-
-static char *get_encoding(const char *message)
-{
-       const char *p = message, *eol;
-
-       while (*p && *p != '\n') {
-               for (eol = p + 1; *eol && *eol != '\n'; eol++)
-                       ; /* do nothing */
-               if (!prefixcmp(p, "encoding ")) {
-                       char *result = xmalloc(eol - 8 - p);
-                       strlcpy(result, p + 9, eol - 8 - p);
-                       return result;
-               }
-               p = eol;
-               if (*p == '\n')
-                       p++;
-       }
-       return NULL;
-}
-
-static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
-{
-       const char *filename;
-       int fd;
-       struct strbuf buf = STRBUF_INIT;
-
-       strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
-
-       filename = git_path("%s", pseudoref);
-       fd = open(filename, O_WRONLY | O_CREAT, 0666);
-       if (fd < 0)
-               die_errno(_("Could not open '%s' for writing"), filename);
-       if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
-               die_errno(_("Could not write to '%s'"), filename);
-       strbuf_release(&buf);
-}
-
-static void print_advice(int show_hint)
-{
-       char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
-       if (msg) {
-               fprintf(stderr, "%s\n", msg);
-               /*
-                * A conflict has occured but the porcelain
-                * (typically rebase --interactive) wants to take care
-                * of the commit itself so remove CHERRY_PICK_HEAD
-                */
-               unlink(git_path("CHERRY_PICK_HEAD"));
-               return;
-       }
-
-       if (show_hint) {
-               advise("after resolving the conflicts, mark the corrected paths");
-               advise("with 'git add <paths>' or 'git rm <paths>'");
-               advise("and commit the result with 'git commit'");
-       }
-}
-
-static void write_message(struct strbuf *msgbuf, const char *filename)
-{
-       static struct lock_file msg_file;
-
-       int msg_fd = hold_lock_file_for_update(&msg_file, filename,
-                                              LOCK_DIE_ON_ERROR);
-       if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
-               die_errno(_("Could not write to %s"), filename);
-       strbuf_release(msgbuf);
-       if (commit_lock_file(&msg_file) < 0)
-               die(_("Error wrapping up %s"), filename);
-}
-
-static struct tree *empty_tree(void)
-{
-       return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
-}
-
-static int error_dirty_index(struct replay_opts *opts)
-{
-       if (read_cache_unmerged())
-               return error_resolve_conflict(action_name(opts));
-
-       /* Different translation strings for cherry-pick and revert */
-       if (opts->action == CHERRY_PICK)
-               error(_("Your local changes would be overwritten by cherry-pick."));
-       else
-               error(_("Your local changes would be overwritten by revert."));
-
-       if (advice_commit_before_merge)
-               advise(_("Commit your changes or stash them to proceed."));
-       return -1;
-}
-
-static int fast_forward_to(const unsigned char *to, const unsigned char *from)
-{
-       struct ref_lock *ref_lock;
-
-       read_cache();
-       if (checkout_fast_forward(from, to))
-               exit(1); /* the callee should have complained already */
-       ref_lock = lock_any_ref_for_update("HEAD", from, 0);
-       return write_ref_sha1(ref_lock, to, "cherry-pick");
-}
-
-static int do_recursive_merge(struct commit *base, struct commit *next,
-                             const char *base_label, const char *next_label,
-                             unsigned char *head, struct strbuf *msgbuf,
-                             struct replay_opts *opts)
-{
-       struct merge_options o;
-       struct tree *result, *next_tree, *base_tree, *head_tree;
-       int clean, index_fd;
-       const char **xopt;
-       static struct lock_file index_lock;
-
-       index_fd = hold_locked_index(&index_lock, 1);
-
-       read_cache();
-
-       init_merge_options(&o);
-       o.ancestor = base ? base_label : "(empty tree)";
-       o.branch1 = "HEAD";
-       o.branch2 = next ? next_label : "(empty tree)";
-
-       head_tree = parse_tree_indirect(head);
-       next_tree = next ? next->tree : empty_tree();
-       base_tree = base ? base->tree : empty_tree();
-
-       for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
-               parse_merge_opt(&o, *xopt);
-
-       clean = merge_trees(&o,
-                           head_tree,
-                           next_tree, base_tree, &result);
-
-       if (active_cache_changed &&
-           (write_cache(index_fd, active_cache, active_nr) ||
-            commit_locked_index(&index_lock)))
-               /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
-               die(_("%s: Unable to write new index file"), action_name(opts));
-       rollback_lock_file(&index_lock);
-
-       if (!clean) {
-               int i;
-               strbuf_addstr(msgbuf, "\nConflicts:\n\n");
-               for (i = 0; i < active_nr;) {
-                       struct cache_entry *ce = active_cache[i++];
-                       if (ce_stage(ce)) {
-                               strbuf_addch(msgbuf, '\t');
-                               strbuf_addstr(msgbuf, ce->name);
-                               strbuf_addch(msgbuf, '\n');
-                               while (i < active_nr && !strcmp(ce->name,
-                                               active_cache[i]->name))
-                                       i++;
-                       }
-               }
-       }
-
-       return !clean;
-}
-
-/*
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-static int run_git_commit(const char *defmsg, struct replay_opts *opts)
-{
-       /* 6 is max possible length of our args array including NULL */
-       const char *args[6];
-       int i = 0;
-
-       args[i++] = "commit";
-       args[i++] = "-n";
-       if (opts->signoff)
-               args[i++] = "-s";
-       if (!opts->edit) {
-               args[i++] = "-F";
-               args[i++] = defmsg;
-       }
-       args[i] = NULL;
-
-       return run_command_v_opt(args, RUN_GIT_CMD);
-}
-
-static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
-{
-       unsigned char head[20];
-       struct commit *base, *next, *parent;
-       const char *base_label, *next_label;
-       struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
-       char *defmsg = NULL;
-       struct strbuf msgbuf = STRBUF_INIT;
-       int res;
-
-       if (opts->no_commit) {
-               /*
-                * We do not intend to commit immediately.  We just want to
-                * merge the differences in, so let's compute the tree
-                * that represents the "current" state for merge-recursive
-                * to work on.
-                */
-               if (write_cache_as_tree(head, 0, NULL))
-                       die (_("Your index file is unmerged."));
-       } else {
-               if (get_sha1("HEAD", head))
-                       return error(_("You do not have a valid HEAD"));
-               if (index_differs_from("HEAD", 0))
-                       return error_dirty_index(opts);
-       }
-       discard_cache();
-
-       if (!commit->parents) {
-               parent = NULL;
-       }
-       else if (commit->parents->next) {
-               /* Reverting or cherry-picking a merge commit */
-               int cnt;
-               struct commit_list *p;
-
-               if (!opts->mainline)
-                       return error(_("Commit %s is a merge but no -m option was given."),
-                               sha1_to_hex(commit->object.sha1));
-
-               for (cnt = 1, p = commit->parents;
-                    cnt != opts->mainline && p;
-                    cnt++)
-                       p = p->next;
-               if (cnt != opts->mainline || !p)
-                       return error(_("Commit %s does not have parent %d"),
-                               sha1_to_hex(commit->object.sha1), opts->mainline);
-               parent = p->item;
-       } else if (0 < opts->mainline)
-               return error(_("Mainline was specified but commit %s is not a merge."),
-                       sha1_to_hex(commit->object.sha1));
-       else
-               parent = commit->parents->item;
-
-       if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
-               return fast_forward_to(commit->object.sha1, head);
-
-       if (parent && parse_commit(parent) < 0)
-               /* TRANSLATORS: The first %s will be "revert" or
-                  "cherry-pick", the second %s a SHA1 */
-               return error(_("%s: cannot parse parent commit %s"),
-                       action_name(opts), sha1_to_hex(parent->object.sha1));
-
-       if (get_message(commit, &msg) != 0)
-               return error(_("Cannot get commit message for %s"),
-                       sha1_to_hex(commit->object.sha1));
-
-       /*
-        * "commit" is an existing commit.  We would want to apply
-        * the difference it introduces since its first parent "prev"
-        * on top of the current HEAD if we are cherry-pick.  Or the
-        * reverse of it if we are revert.
-        */
-
-       defmsg = git_pathdup("MERGE_MSG");
-
-       if (opts->action == REVERT) {
-               base = commit;
-               base_label = msg.label;
-               next = parent;
-               next_label = msg.parent_label;
-               strbuf_addstr(&msgbuf, "Revert \"");
-               strbuf_addstr(&msgbuf, msg.subject);
-               strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
-               strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-
-               if (commit->parents && commit->parents->next) {
-                       strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
-                       strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
-               }
-               strbuf_addstr(&msgbuf, ".\n");
-       } else {
-               const char *p;
-
-               base = parent;
-               base_label = msg.parent_label;
-               next = commit;
-               next_label = msg.label;
-
-               /*
-                * Append the commit log message to msgbuf; it starts
-                * after the tree, parent, author, committer
-                * information followed by "\n\n".
-                */
-               p = strstr(msg.message, "\n\n");
-               if (p) {
-                       p += 2;
-                       strbuf_addstr(&msgbuf, p);
-               }
-
-               if (opts->record_origin) {
-                       strbuf_addstr(&msgbuf, "(cherry picked from commit ");
-                       strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-                       strbuf_addstr(&msgbuf, ")\n");
-               }
-       }
-
-       if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
-               res = do_recursive_merge(base, next, base_label, next_label,
-                                        head, &msgbuf, opts);
-               write_message(&msgbuf, defmsg);
-       } else {
-               struct commit_list *common = NULL;
-               struct commit_list *remotes = NULL;
-
-               write_message(&msgbuf, defmsg);
-
-               commit_list_insert(base, &common);
-               commit_list_insert(next, &remotes);
-               res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
-                                       common, sha1_to_hex(head), remotes);
-               free_commit_list(common);
-               free_commit_list(remotes);
-       }
-
-       /*
-        * If the merge was clean or if it failed due to conflict, we write
-        * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
-        * However, if the merge did not even start, then we don't want to
-        * write it at all.
-        */
-       if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1))
-               write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
-       if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1))
-               write_cherry_pick_head(commit, "REVERT_HEAD");
-
-       if (res) {
-               error(opts->action == REVERT
-                     ? _("could not revert %s... %s")
-                     : _("could not apply %s... %s"),
-                     find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
-                     msg.subject);
-               print_advice(res == 1);
-               rerere(opts->allow_rerere_auto);
-       } else {
-               if (!opts->no_commit)
-                       res = run_git_commit(defmsg, opts);
-       }
-
-       free_message(&msg);
-       free(defmsg);
-
-       return res;
-}
-
-static void prepare_revs(struct replay_opts *opts)
-{
-       if (opts->action != REVERT)
-               opts->revs->reverse ^= 1;
-
-       if (prepare_revision_walk(opts->revs))
-               die(_("revision walk setup failed"));
-
-       if (!opts->revs->commits)
-               die(_("empty commit set passed"));
-}
-
-static void read_and_refresh_cache(struct replay_opts *opts)
-{
-       static struct lock_file index_lock;
-       int index_fd = hold_locked_index(&index_lock, 0);
-       if (read_index_preload(&the_index, NULL) < 0)
-               die(_("git %s: failed to read the index"), action_name(opts));
-       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
-       if (the_index.cache_changed) {
-               if (write_index(&the_index, index_fd) ||
-                   commit_locked_index(&index_lock))
-                       die(_("git %s: failed to refresh the index"), action_name(opts));
-       }
-       rollback_lock_file(&index_lock);
-}
-
-/*
- * Append a commit to the end of the commit_list.
- *
- * next starts by pointing to the variable that holds the head of an
- * empty commit_list, and is updated to point to the "next" field of
- * the last item on the list as new commits are appended.
- *
- * Usage example:
- *
- *     struct commit_list *list;
- *     struct commit_list **next = &list;
- *
- *     next = commit_list_append(c1, next);
- *     next = commit_list_append(c2, next);
- *     assert(commit_list_count(list) == 2);
- *     return list;
- */
-static struct commit_list **commit_list_append(struct commit *commit,
-                                              struct commit_list **next)
-{
-       struct commit_list *new = xmalloc(sizeof(struct commit_list));
-       new->item = commit;
-       *next = new;
-       new->next = NULL;
-       return &new->next;
-}
-
-static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
-               struct replay_opts *opts)
-{
-       struct commit_list *cur = NULL;
-       const char *sha1_abbrev = NULL;
-       const char *action_str = opts->action == REVERT ? "revert" : "pick";
-       const char *subject;
-       int subject_len;
-
-       for (cur = todo_list; cur; cur = cur->next) {
-               sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
-               subject_len = find_commit_subject(cur->item->buffer, &subject);
-               strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
-                       subject_len, subject);
-       }
-       return 0;
-}
-
-static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
-{
-       unsigned char commit_sha1[20];
-       enum replay_action action;
-       char *end_of_object_name;
-       int saved, status, padding;
-
-       if (!prefixcmp(bol, "pick")) {
-               action = CHERRY_PICK;
-               bol += strlen("pick");
-       } else if (!prefixcmp(bol, "revert")) {
-               action = REVERT;
-               bol += strlen("revert");
-       } else
-               return NULL;
-
-       /* Eat up extra spaces/ tabs before object name */
-       padding = strspn(bol, " \t");
-       if (!padding)
-               return NULL;
-       bol += padding;
-
-       end_of_object_name = bol + strcspn(bol, " \t\n");
-       saved = *end_of_object_name;
-       *end_of_object_name = '\0';
-       status = get_sha1(bol, commit_sha1);
-       *end_of_object_name = saved;
-
-       /*
-        * Verify that the action matches up with the one in
-        * opts; we don't support arbitrary instructions
-        */
-       if (action != opts->action) {
-               const char *action_str;
-               action_str = action == REVERT ? "revert" : "cherry-pick";
-               error(_("Cannot %s during a %s"), action_str, action_name(opts));
-               return NULL;
-       }
-
-       if (status < 0)
-               return NULL;
-
-       return lookup_commit_reference(commit_sha1);
-}
-
-static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
-                       struct replay_opts *opts)
-{
-       struct commit_list **next = todo_list;
-       struct commit *commit;
-       char *p = buf;
-       int i;
-
-       for (i = 1; *p; i++) {
-               char *eol = strchrnul(p, '\n');
-               commit = parse_insn_line(p, eol, opts);
-               if (!commit)
-                       return error(_("Could not parse line %d."), i);
-               next = commit_list_append(commit, next);
-               p = *eol ? eol + 1 : eol;
-       }
-       if (!*todo_list)
-               return error(_("No commits parsed."));
-       return 0;
-}
-
-static void read_populate_todo(struct commit_list **todo_list,
-                       struct replay_opts *opts)
-{
-       const char *todo_file = git_path(SEQ_TODO_FILE);
-       struct strbuf buf = STRBUF_INIT;
-       int fd, res;
-
-       fd = open(todo_file, O_RDONLY);
-       if (fd < 0)
-               die_errno(_("Could not open %s"), todo_file);
-       if (strbuf_read(&buf, fd, 0) < 0) {
-               close(fd);
-               strbuf_release(&buf);
-               die(_("Could not read %s."), todo_file);
-       }
-       close(fd);
-
-       res = parse_insn_buffer(buf.buf, todo_list, opts);
-       strbuf_release(&buf);
-       if (res)
-               die(_("Unusable instruction sheet: %s"), todo_file);
-}
-
-static int populate_opts_cb(const char *key, const char *value, void *data)
-{
-       struct replay_opts *opts = data;
-       int error_flag = 1;
-
-       if (!value)
-               error_flag = 0;
-       else if (!strcmp(key, "options.no-commit"))
-               opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
-       else if (!strcmp(key, "options.edit"))
-               opts->edit = git_config_bool_or_int(key, value, &error_flag);
-       else if (!strcmp(key, "options.signoff"))
-               opts->signoff = git_config_bool_or_int(key, value, &error_flag);
-       else if (!strcmp(key, "options.record-origin"))
-               opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
-       else if (!strcmp(key, "options.allow-ff"))
-               opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
-       else if (!strcmp(key, "options.mainline"))
-               opts->mainline = git_config_int(key, value);
-       else if (!strcmp(key, "options.strategy"))
-               git_config_string(&opts->strategy, key, value);
-       else if (!strcmp(key, "options.strategy-option")) {
-               ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
-               opts->xopts[opts->xopts_nr++] = xstrdup(value);
-       } else
-               return error(_("Invalid key: %s"), key);
-
-       if (!error_flag)
-               return error(_("Invalid value for %s: %s"), key, value);
-
-       return 0;
-}
-
-static void read_populate_opts(struct replay_opts **opts_ptr)
-{
-       const char *opts_file = git_path(SEQ_OPTS_FILE);
-
-       if (!file_exists(opts_file))
-               return;
-       if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
-               die(_("Malformed options sheet: %s"), opts_file);
-}
-
-static void walk_revs_populate_todo(struct commit_list **todo_list,
-                               struct replay_opts *opts)
-{
-       struct commit *commit;
-       struct commit_list **next;
-
-       prepare_revs(opts);
-
-       next = todo_list;
-       while ((commit = get_revision(opts->revs)))
-               next = commit_list_append(commit, next);
-}
-
-static int create_seq_dir(void)
-{
-       const char *seq_dir = git_path(SEQ_DIR);
-
-       if (file_exists(seq_dir)) {
-               error(_("a cherry-pick or revert is already in progress"));
-               advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
-               return -1;
-       }
-       else if (mkdir(seq_dir, 0777) < 0)
-               die_errno(_("Could not create sequencer directory %s"), seq_dir);
-       return 0;
-}
-
-static void save_head(const char *head)
-{
-       const char *head_file = git_path(SEQ_HEAD_FILE);
-       static struct lock_file head_lock;
-       struct strbuf buf = STRBUF_INIT;
-       int fd;
-
-       fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
-       strbuf_addf(&buf, "%s\n", head);
-       if (write_in_full(fd, buf.buf, buf.len) < 0)
-               die_errno(_("Could not write to %s"), head_file);
-       if (commit_lock_file(&head_lock) < 0)
-               die(_("Error wrapping up %s."), head_file);
-}
-
-static int reset_for_rollback(const unsigned char *sha1)
-{
-       const char *argv[4];    /* reset --merge <arg> + NULL */
-       argv[0] = "reset";
-       argv[1] = "--merge";
-       argv[2] = sha1_to_hex(sha1);
-       argv[3] = NULL;
-       return run_command_v_opt(argv, RUN_GIT_CMD);
-}
-
-static int rollback_single_pick(void)
-{
-       unsigned char head_sha1[20];
-
-       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
-           !file_exists(git_path("REVERT_HEAD")))
-               return error(_("no cherry-pick or revert in progress"));
-       if (read_ref_full("HEAD", head_sha1, 0, NULL))
-               return error(_("cannot resolve HEAD"));
-       if (is_null_sha1(head_sha1))
-               return error(_("cannot abort from a branch yet to be born"));
-       return reset_for_rollback(head_sha1);
-}
-
-static int sequencer_rollback(struct replay_opts *opts)
-{
-       const char *filename;
-       FILE *f;
-       unsigned char sha1[20];
-       struct strbuf buf = STRBUF_INIT;
-
-       filename = git_path(SEQ_HEAD_FILE);
-       f = fopen(filename, "r");
-       if (!f && errno == ENOENT) {
-               /*
-                * There is no multiple-cherry-pick in progress.
-                * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
-                * a single-cherry-pick in progress, abort that.
-                */
-               return rollback_single_pick();
-       }
-       if (!f)
-               return error(_("cannot open %s: %s"), filename,
-                                               strerror(errno));
-       if (strbuf_getline(&buf, f, '\n')) {
-               error(_("cannot read %s: %s"), filename, ferror(f) ?
-                       strerror(errno) : _("unexpected end of file"));
-               fclose(f);
-               goto fail;
-       }
-       fclose(f);
-       if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
-               error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
-                       filename);
-               goto fail;
-       }
-       if (reset_for_rollback(sha1))
-               goto fail;
-       remove_sequencer_state();
-       strbuf_release(&buf);
-       return 0;
-fail:
-       strbuf_release(&buf);
-       return -1;
-}
-
-static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
-{
-       const char *todo_file = git_path(SEQ_TODO_FILE);
-       static struct lock_file todo_lock;
-       struct strbuf buf = STRBUF_INIT;
-       int fd;
-
-       fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
-       if (format_todo(&buf, todo_list, opts) < 0)
-               die(_("Could not format %s."), todo_file);
-       if (write_in_full(fd, buf.buf, buf.len) < 0) {
-               strbuf_release(&buf);
-               die_errno(_("Could not write to %s"), todo_file);
-       }
-       if (commit_lock_file(&todo_lock) < 0) {
-               strbuf_release(&buf);
-               die(_("Error wrapping up %s."), todo_file);
-       }
-       strbuf_release(&buf);
-}
-
-static void save_opts(struct replay_opts *opts)
-{
-       const char *opts_file = git_path(SEQ_OPTS_FILE);
-
-       if (opts->no_commit)
-               git_config_set_in_file(opts_file, "options.no-commit", "true");
-       if (opts->edit)
-               git_config_set_in_file(opts_file, "options.edit", "true");
-       if (opts->signoff)
-               git_config_set_in_file(opts_file, "options.signoff", "true");
-       if (opts->record_origin)
-               git_config_set_in_file(opts_file, "options.record-origin", "true");
-       if (opts->allow_ff)
-               git_config_set_in_file(opts_file, "options.allow-ff", "true");
-       if (opts->mainline) {
-               struct strbuf buf = STRBUF_INIT;
-               strbuf_addf(&buf, "%d", opts->mainline);
-               git_config_set_in_file(opts_file, "options.mainline", buf.buf);
-               strbuf_release(&buf);
-       }
-       if (opts->strategy)
-               git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
-       if (opts->xopts) {
-               int i;
-               for (i = 0; i < opts->xopts_nr; i++)
-                       git_config_set_multivar_in_file(opts_file,
-                                                       "options.strategy-option",
-                                                       opts->xopts[i], "^$", 0);
-       }
-}
-
-static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
-{
-       struct commit_list *cur;
-       int res;
-
-       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
-       if (opts->allow_ff)
-               assert(!(opts->signoff || opts->no_commit ||
-                               opts->record_origin || opts->edit));
-       read_and_refresh_cache(opts);
-
-       for (cur = todo_list; cur; cur = cur->next) {
-               save_todo(cur, opts);
-               res = do_pick_commit(cur->item, opts);
-               if (res)
-                       return res;
-       }
-
-       /*
-        * Sequence of picks finished successfully; cleanup by
-        * removing the .git/sequencer directory
-        */
-       remove_sequencer_state();
-       return 0;
-}
-
-static int continue_single_pick(void)
-{
-       const char *argv[] = { "commit", NULL };
-
-       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
-           !file_exists(git_path("REVERT_HEAD")))
-               return error(_("no cherry-pick or revert in progress"));
-       return run_command_v_opt(argv, RUN_GIT_CMD);
-}
-
-static int sequencer_continue(struct replay_opts *opts)
-{
-       struct commit_list *todo_list = NULL;
-
-       if (!file_exists(git_path(SEQ_TODO_FILE)))
-               return continue_single_pick();
-       read_populate_opts(&opts);
-       read_populate_todo(&todo_list, opts);
-
-       /* Verify that the conflict has been resolved */
-       if (file_exists(git_path("CHERRY_PICK_HEAD")) ||
-           file_exists(git_path("REVERT_HEAD"))) {
-               int ret = continue_single_pick();
-               if (ret)
-                       return ret;
-       }
-       if (index_differs_from("HEAD", 0))
-               return error_dirty_index(opts);
-       todo_list = todo_list->next;
-       return pick_commits(todo_list, opts);
-}
-
-static int single_pick(struct commit *cmit, struct replay_opts *opts)
-{
-       setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
-       return do_pick_commit(cmit, opts);
-}
-
-static int pick_revisions(struct replay_opts *opts)
-{
-       struct commit_list *todo_list = NULL;
-       unsigned char sha1[20];
-
-       if (opts->subcommand == REPLAY_NONE)
-               assert(opts->revs);
-
-       read_and_refresh_cache(opts);
-
-       /*
-        * Decide what to do depending on the arguments; a fresh
-        * cherry-pick should be handled differently from an existing
-        * one that is being continued
-        */
-       if (opts->subcommand == REPLAY_REMOVE_STATE) {
-               remove_sequencer_state();
-               return 0;
-       }
-       if (opts->subcommand == REPLAY_ROLLBACK)
-               return sequencer_rollback(opts);
-       if (opts->subcommand == REPLAY_CONTINUE)
-               return sequencer_continue(opts);
-
-       /*
-        * If we were called as "git cherry-pick <commit>", just
-        * cherry-pick/revert it, set CHERRY_PICK_HEAD /
-        * REVERT_HEAD, and don't touch the sequencer state.
-        * This means it is possible to cherry-pick in the middle
-        * of a cherry-pick sequence.
-        */
-       if (opts->revs->cmdline.nr == 1 &&
-           opts->revs->cmdline.rev->whence == REV_CMD_REV &&
-           opts->revs->no_walk &&
-           !opts->revs->cmdline.rev->flags) {
-               struct commit *cmit;
-               if (prepare_revision_walk(opts->revs))
-                       die(_("revision walk setup failed"));
-               cmit = get_revision(opts->revs);
-               if (!cmit || get_revision(opts->revs))
-                       die("BUG: expected exactly one commit from walk");
-               return single_pick(cmit, opts);
-       }
-
-       /*
-        * Start a new cherry-pick/ revert sequence; but
-        * first, make sure that an existing one isn't in
-        * progress
-        */
-
-       walk_revs_populate_todo(&todo_list, opts);
-       if (create_seq_dir() < 0)
-               return -1;
-       if (get_sha1("HEAD", sha1)) {
-               if (opts->action == REVERT)
-                       return error(_("Can't revert as initial commit"));
-               return error(_("Can't cherry-pick into empty head"));
-       }
-       save_head(sha1_to_hex(sha1));
-       save_opts(opts);
-       return pick_commits(todo_list, opts);
-}
-
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
        struct replay_opts opts;
@@ -1141,10 +201,10 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
        memset(&opts, 0, sizeof(opts));
        if (isatty(0))
                opts.edit = 1;
-       opts.action = REVERT;
+       opts.action = REPLAY_REVERT;
        git_config(git_default_config, NULL);
        parse_args(argc, argv, &opts);
-       res = pick_revisions(&opts);
+       res = sequencer_pick_revisions(&opts);
        if (res < 0)
                die(_("revert failed"));
        return res;
@@ -1156,10 +216,10 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
        int res;
 
        memset(&opts, 0, sizeof(opts));
-       opts.action = CHERRY_PICK;
+       opts.action = REPLAY_PICK;
        git_config(git_default_config, NULL);
        parse_args(argc, argv, &opts);
-       res = pick_revisions(&opts);
+       res = sequencer_pick_revisions(&opts);
        if (res < 0)
                die(_("cherry-pick failed"));
        return res;
index 2ef5962..801d62e 100644 (file)
@@ -8,13 +8,15 @@ static const char * const git_symbolic_ref_usage[] = {
        NULL
 };
 
+static int shorten;
+
 static void check_symref(const char *HEAD, int quiet)
 {
        unsigned char sha1[20];
        int flag;
-       const char *refs_heads_master = resolve_ref_unsafe(HEAD, sha1, 0, &flag);
+       const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag);
 
-       if (!refs_heads_master)
+       if (!refname)
                die("No such ref: %s", HEAD);
        else if (!(flag & REF_ISSYMREF)) {
                if (!quiet)
@@ -22,7 +24,9 @@ static void check_symref(const char *HEAD, int quiet)
                else
                        exit(1);
        }
-       puts(refs_heads_master);
+       if (shorten)
+               refname = shorten_unambiguous_ref(refname, 0);
+       puts(refname);
 }
 
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
@@ -32,6 +36,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT__QUIET(&quiet,
                        "suppress error message for non-symbolic (detached) refs"),
+               OPT_BOOL(0, "short", &shorten, "shorten ref output"),
                OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
                OPT_END(),
        };
index 03df16a..fe7e5e5 100644 (file)
 #include "diff.h"
 #include "revision.h"
 #include "gpg-interface.h"
+#include "sha1-array.h"
 
 static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
        "git tag -d <tagname>...",
-       "git tag -l [-n[<num>]] [<pattern>...]",
+       "git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] "
+               "\n\t\t[<pattern>...]",
        "git tag -v <tagname>...",
        NULL
 };
@@ -30,6 +32,8 @@ struct tag_filter {
        struct commit_list *with_commit;
 };
 
+static struct sha1_array points_at;
+
 static int match_pattern(const char **patterns, const char *ref)
 {
        /* no pattern means match everything */
@@ -41,6 +45,24 @@ static int match_pattern(const char **patterns, const char *ref)
        return 0;
 }
 
+static const unsigned char *match_points_at(const char *refname,
+                                           const unsigned char *sha1)
+{
+       const unsigned char *tagged_sha1 = NULL;
+       struct object *obj;
+
+       if (sha1_array_lookup(&points_at, sha1) >= 0)
+               return sha1;
+       obj = parse_object(sha1);
+       if (!obj)
+               die(_("malformed object at '%s'"), refname);
+       if (obj->type == OBJ_TAG)
+               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+       if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
+               return tagged_sha1;
+       return NULL;
+}
+
 static int in_commit_list(const struct commit_list *want, struct commit *c)
 {
        for (; want; want = want->next)
@@ -138,6 +160,9 @@ static int show_reference(const char *refname, const unsigned char *sha1,
                                return 0;
                }
 
+               if (points_at.nr && !match_points_at(refname, sha1))
+                       return 0;
+
                if (!filter->lines) {
                        printf("%s\n", refname);
                        return 0;
@@ -383,6 +408,23 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
        return check_refname_format(sb->buf, 0);
 }
 
+static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
+                       const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(&points_at);
+               return 0;
+       }
+       if (!arg)
+               return error(_("switch 'points-at' requires an object"));
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(&points_at, sha1);
+       return 0;
+}
+
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -425,6 +467,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_LASTARG_DEFAULT,
                        parse_opt_with_commit, (intptr_t)"HEAD",
                },
+               {
+                       OPTION_CALLBACK, 0, "points-at", NULL, "object",
+                       "print only tags of the object", 0, parse_opt_points_at
+               },
                OPT_END()
        };
 
@@ -456,6 +502,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                die(_("-n option is only allowed with -l."));
        if (with_commit)
                die(_("--contains option is only allowed with -l."));
+       if (points_at.nr)
+               die(_("--points-at option is only allowed with -l."));
        if (delete)
                return for_each_tag_name(argv, delete_tag);
        if (verify)
index 28c2174..986789f 100644 (file)
@@ -58,6 +58,14 @@ static int verify_tag(const char *name, int verbose)
        return ret;
 }
 
+static int git_verify_tag_config(const char *var, const char *value, void *cb)
+{
+       int status = git_gpg_config(var, value, cb);
+       if (status)
+               return status;
+       return git_default_config(var, value, cb);
+}
+
 int cmd_verify_tag(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
@@ -66,7 +74,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_default_config, NULL);
+       git_config(git_verify_tag_config, NULL);
 
        argc = parse_options(argc, argv, prefix, verify_tag_options,
                             verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
index 7a760db..d9cfd90 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -273,7 +273,7 @@ int create_bundle(struct bundle_header *header, const char *path,
                        if (!get_sha1_hex(buf.buf + 1, sha1)) {
                                struct object *object = parse_object(sha1);
                                object->flags |= UNINTERESTING;
-                               add_pending_object(&revs, object, buf.buf);
+                               add_pending_object(&revs, object, xstrdup(buf.buf));
                        }
                } else if (!get_sha1_hex(buf.buf, sha1)) {
                        struct object *object = parse_object(sha1);
index bf03cb7..28ed657 100644 (file)
@@ -150,9 +150,10 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
 }
 
 static int verify_cache(struct cache_entry **cache,
-                       int entries, int silent)
+                       int entries, int flags)
 {
        int i, funny;
+       int silent = flags & WRITE_TREE_SILENT;
 
        /* Verify that the tree is merged */
        funny = 0;
@@ -241,10 +242,11 @@ static int update_one(struct cache_tree *it,
                      int entries,
                      const char *base,
                      int baselen,
-                     int missing_ok,
-                     int dryrun)
+                     int flags)
 {
        struct strbuf buffer;
+       int missing_ok = flags & WRITE_TREE_MISSING_OK;
+       int dryrun = flags & WRITE_TREE_DRY_RUN;
        int i;
 
        if (0 <= it->entry_count && has_sha1_file(it->sha1))
@@ -288,8 +290,7 @@ static int update_one(struct cache_tree *it,
                                    cache + i, entries - i,
                                    path,
                                    baselen + sublen + 1,
-                                   missing_ok,
-                                   dryrun);
+                                   flags);
                if (subcnt < 0)
                        return subcnt;
                i += subcnt - 1;
@@ -371,15 +372,13 @@ static int update_one(struct cache_tree *it,
 int cache_tree_update(struct cache_tree *it,
                      struct cache_entry **cache,
                      int entries,
-                     int missing_ok,
-                     int dryrun,
-                     int silent)
+                     int flags)
 {
        int i;
-       i = verify_cache(cache, entries, silent);
+       i = verify_cache(cache, entries, flags);
        if (i)
                return i;
-       i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+       i = update_one(it, cache, entries, "", 0, flags);
        if (i < 0)
                return i;
        return 0;
@@ -572,11 +571,9 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix)
 
        was_valid = cache_tree_fully_valid(active_cache_tree);
        if (!was_valid) {
-               int missing_ok = flags & WRITE_TREE_MISSING_OK;
-
                if (cache_tree_update(active_cache_tree,
                                      active_cache, active_nr,
-                                     missing_ok, 0, 0) < 0)
+                                     flags) < 0)
                        return WRITE_TREE_UNMERGED_INDEX;
                if (0 <= newfd) {
                        if (!write_cache(newfd, active_cache, active_nr) &&
@@ -672,10 +669,10 @@ int cache_tree_matches_traversal(struct cache_tree *root,
        return 0;
 }
 
-int update_main_cache_tree (int silent)
+int update_main_cache_tree(int flags)
 {
        if (!the_index.cache_tree)
                the_index.cache_tree = cache_tree();
        return cache_tree_update(the_index.cache_tree,
-                                the_index.cache, the_index.cache_nr, 0, 0, silent);
+                                the_index.cache, the_index.cache_nr, flags);
 }
index 0ec0b2a..d8cb2e9 100644 (file)
@@ -29,13 +29,15 @@ void cache_tree_write(struct strbuf *, struct cache_tree *root);
 struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
 
 int cache_tree_fully_valid(struct cache_tree *);
-int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int, int);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int);
 
 int update_main_cache_tree(int);
 
 /* bitmasks to write_cache_as_tree flags */
 #define WRITE_TREE_MISSING_OK 1
 #define WRITE_TREE_IGNORE_CACHE_TREE 2
+#define WRITE_TREE_DRY_RUN 4
+#define WRITE_TREE_SILENT 8
 
 /* error return codes */
 #define WRITE_TREE_UNREADABLE_INDEX (-1)
diff --git a/cache.h b/cache.h
index 3a8e125..e5e1aa4 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -950,7 +950,9 @@ struct cache_def {
 extern int has_symlink_leading_path(const char *name, int len);
 extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
 extern int check_leading_path(const char *name, int len);
+extern int threaded_check_leading_path(struct cache_def *cache, const char *name, int len);
 extern int has_dirs_only_path(const char *name, int len, int prefix_len);
+extern int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len);
 extern void schedule_dir_for_removal(const char *name, int len);
 extern void remove_scheduled_dirs(void);
 
@@ -1115,6 +1117,8 @@ extern int git_config_from_file(config_fn_t fn, const char *, void *);
 extern void git_config_push_parameter(const char *text);
 extern int git_config_from_parameters(config_fn_t fn, void *data);
 extern int git_config(config_fn_t fn, void *);
+extern int git_config_with_options(config_fn_t fn, void *,
+                                  const char *filename, int respect_includes);
 extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_config_int(const char *, const char *);
@@ -1130,6 +1134,7 @@ extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
 extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
+extern int git_config_rename_section_in_file(const char *, const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
 extern int git_env_bool(const char *, int);
@@ -1140,7 +1145,13 @@ extern const char *get_commit_output_encoding(void);
 
 extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
 
-extern const char *config_exclusive_filename;
+struct config_include_data {
+       int depth;
+       config_fn_t fn;
+       void *data;
+};
+#define CONFIG_INCLUDE_INIT { 0 }
+extern int git_config_include(const char *name, const char *value, void *data);
 
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
@@ -1178,6 +1189,7 @@ extern const char *pager_program;
 extern int pager_in_use(void);
 extern int pager_use_color;
 extern int term_columns(void);
+extern int decimal_width(int);
 
 extern const char *editor_program;
 extern const char *askpass_program;
index 35af498..4b39c19 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -422,7 +422,8 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
        return ret;
 }
 
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+static void clear_commit_marks_1(struct commit_list **plist,
+                                struct commit *commit, unsigned int mark)
 {
        while (commit) {
                struct commit_list *parents;
@@ -437,12 +438,20 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
                        return;
 
                while ((parents = parents->next))
-                       clear_commit_marks(parents->item, mark);
+                       commit_list_insert(parents->item, plist);
 
                commit = commit->parents->item;
        }
 }
 
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+       struct commit_list *list = NULL;
+       commit_list_insert(commit, &list);
+       while (list)
+               clear_commit_marks_1(&list, pop_commit(&list), mark);
+}
+
 void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
 {
        struct object *object;
index 40f9c6d..68d3294 100644 (file)
--- a/config.c
+++ b/config.c
@@ -26,7 +26,68 @@ static config_file *cf;
 
 static int zlib_compression_seen;
 
-const char *config_exclusive_filename = NULL;
+#define MAX_INCLUDE_DEPTH 10
+static const char include_depth_advice[] =
+"exceeded maximum include depth (%d) while including\n"
+"      %s\n"
+"from\n"
+"      %s\n"
+"Do you have circular includes?";
+static int handle_path_include(const char *path, struct config_include_data *inc)
+{
+       int ret = 0;
+       struct strbuf buf = STRBUF_INIT;
+
+       /*
+        * Use an absolute path as-is, but interpret relative paths
+        * based on the including config file.
+        */
+       if (!is_absolute_path(path)) {
+               char *slash;
+
+               if (!cf || !cf->name)
+                       return error("relative config includes must come from files");
+
+               slash = find_last_dir_sep(cf->name);
+               if (slash)
+                       strbuf_add(&buf, cf->name, slash - cf->name + 1);
+               strbuf_addstr(&buf, path);
+               path = buf.buf;
+       }
+
+       if (!access(path, R_OK)) {
+               if (++inc->depth > MAX_INCLUDE_DEPTH)
+                       die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
+                           cf && cf->name ? cf->name : "the command line");
+               ret = git_config_from_file(git_config_include, path, inc);
+               inc->depth--;
+       }
+       strbuf_release(&buf);
+       return ret;
+}
+
+int git_config_include(const char *var, const char *value, void *data)
+{
+       struct config_include_data *inc = data;
+       const char *type;
+       int ret;
+
+       /*
+        * Pass along all values, including "include" directives; this makes it
+        * possible to query information on the includes themselves.
+        */
+       ret = inc->fn(var, value, inc->data);
+       if (ret < 0)
+               return ret;
+
+       type = skip_prefix(var, "include.");
+       if (!type)
+               return ret;
+
+       if (!strcmp(type, "path"))
+               ret = handle_path_include(value, inc);
+       return ret;
+}
 
 static void lowercase(char *p)
 {
@@ -135,8 +196,10 @@ static char *parse_value(void)
        for (;;) {
                int c = get_next_char();
                if (c == '\n') {
-                       if (quote)
+                       if (quote) {
+                               cf->linenr--;
                                return NULL;
+                       }
                        return cf->value.buf;
                }
                if (comment)
@@ -226,7 +289,7 @@ static int get_extended_base_var(char *name, int baselen, int c)
 {
        do {
                if (c == '\n')
-                       return -1;
+                       goto error_incomplete_line;
                c = get_next_char();
        } while (isspace(c));
 
@@ -238,13 +301,13 @@ static int get_extended_base_var(char *name, int baselen, int c)
        for (;;) {
                int c = get_next_char();
                if (c == '\n')
-                       return -1;
+                       goto error_incomplete_line;
                if (c == '"')
                        break;
                if (c == '\\') {
                        c = get_next_char();
                        if (c == '\n')
-                               return -1;
+                               goto error_incomplete_line;
                }
                name[baselen++] = c;
                if (baselen > MAXNAME / 2)
@@ -255,6 +318,9 @@ static int get_extended_base_var(char *name, int baselen, int c)
        if (get_next_char() != ']')
                return -1;
        return baselen;
+error_incomplete_line:
+       cf->linenr--;
+       return -1;
 }
 
 static int get_base_var(char *name)
@@ -879,9 +945,6 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
        int ret = 0, found = 0;
        const char *home = NULL;
 
-       /* Setting $GIT_CONFIG makes git read _only_ the given config file. */
-       if (config_exclusive_filename)
-               return git_config_from_file(fn, config_exclusive_filename, data);
        if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
                                            data);
@@ -917,10 +980,26 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
        return ret == 0 ? found : ret;
 }
 
-int git_config(config_fn_t fn, void *data)
+int git_config_with_options(config_fn_t fn, void *data,
+                           const char *filename, int respect_includes)
 {
        char *repo_config = NULL;
        int ret;
+       struct config_include_data inc = CONFIG_INCLUDE_INIT;
+
+       if (respect_includes) {
+               inc.fn = fn;
+               inc.data = data;
+               fn = git_config_include;
+               data = &inc;
+       }
+
+       /*
+        * If we have a specific filename, use it. Otherwise, follow the
+        * regular lookup sequence.
+        */
+       if (filename)
+               return git_config_from_file(fn, filename, data);
 
        repo_config = git_pathdup("config");
        ret = git_config_early(fn, data, repo_config);
@@ -929,6 +1008,11 @@ int git_config(config_fn_t fn, void *data)
        return ret;
 }
 
+int git_config(config_fn_t fn, void *data)
+{
+       return git_config_with_options(fn, data, NULL, 1);
+}
+
 /*
  * Find all the stuff for git_config_set() below.
  */
@@ -1233,6 +1317,7 @@ int git_config_set_multivar_in_file(const char *config_filename,
        int fd = -1, in_fd;
        int ret;
        struct lock_file *lock = NULL;
+       char *filename_buf = NULL;
 
        /* parse-key returns negative; flip the sign to feed exit(3) */
        ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
@@ -1241,6 +1326,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
 
        store.multi_replace = multi_replace;
 
+       if (!config_filename)
+               config_filename = filename_buf = git_pathdup("config");
 
        /*
         * The lock serves a purpose in addition to locking: the new
@@ -1410,6 +1497,7 @@ int git_config_set_multivar_in_file(const char *config_filename,
 out_free:
        if (lock)
                rollback_lock_file(lock);
+       free(filename_buf);
        return ret;
 
 write_err_out:
@@ -1421,19 +1509,8 @@ write_err_out:
 int git_config_set_multivar(const char *key, const char *value,
                        const char *value_regex, int multi_replace)
 {
-       const char *config_filename;
-       char *buf = NULL;
-       int ret;
-
-       if (config_exclusive_filename)
-               config_filename = config_exclusive_filename;
-       else
-               config_filename = buf = git_pathdup("config");
-
-       ret = git_config_set_multivar_in_file(config_filename, key, value,
-                                       value_regex, multi_replace);
-       free(buf);
-       return ret;
+       return git_config_set_multivar_in_file(NULL, key, value, value_regex,
+                                              multi_replace);
 }
 
 static int section_name_match (const char *buf, const char *name)
@@ -1476,19 +1553,19 @@ static int section_name_match (const char *buf, const char *name)
 }
 
 /* if new_name == NULL, the section is removed instead */
-int git_config_rename_section(const char *old_name, const char *new_name)
+int git_config_rename_section_in_file(const char *config_filename,
+                                     const char *old_name, const char *new_name)
 {
        int ret = 0, remove = 0;
-       char *config_filename;
+       char *filename_buf = NULL;
        struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
        int out_fd;
        char buf[1024];
        FILE *config_file;
 
-       if (config_exclusive_filename)
-               config_filename = xstrdup(config_exclusive_filename);
-       else
-               config_filename = git_pathdup("config");
+       if (!config_filename)
+               config_filename = filename_buf = git_pathdup("config");
+
        out_fd = hold_lock_file_for_update(lock, config_filename, 0);
        if (out_fd < 0) {
                ret = error("could not lock config file %s", config_filename);
@@ -1552,10 +1629,15 @@ unlock_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error("could not commit config file %s", config_filename);
 out:
-       free(config_filename);
+       free(filename_buf);
        return ret;
 }
 
+int git_config_rename_section(const char *old_name, const char *new_name)
+{
+       return git_config_rename_section_in_file(NULL, old_name, new_name);
+}
+
 /*
  * Call this to report error for your variable that should not
  * get a boolean value (i.e. "[my] var" means "true").
index 0acbdda..fba076d 100755 (executable)
@@ -137,7 +137,7 @@ __git_ps1_show_upstream ()
                        svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
                        svn_upstream=${svn_upstream%@*}
                        local n_stop="${#svn_remote[@]}"
-                       for ((n=1; n <= n_stop; ++n)); do
+                       for ((n=1; n <= n_stop; n++)); do
                                svn_upstream=${svn_upstream#${svn_remote[$n]}}
                        done
 
@@ -166,10 +166,8 @@ __git_ps1_show_upstream ()
                        for commit in $commits
                        do
                                case "$commit" in
-                               "<"*) let ++behind
-                                       ;;
-                               *)    let ++ahead
-                                       ;;
+                               "<"*) ((behind++)) ;;
+                               *)    ((ahead++))  ;;
                                esac
                        done
                        count="$behind  $ahead"
@@ -726,6 +724,9 @@ __git_complete_remote_or_refspec ()
 {
        local cur_="$cur" cmd="${words[1]}"
        local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+       if [ "$cmd" = "remote" ]; then
+               ((c++))
+       fi
        while [ $c -lt $cword ]; do
                i="${words[c]}"
                case "$i" in
@@ -743,7 +744,7 @@ __git_complete_remote_or_refspec ()
                -*) ;;
                *) remote="$i"; break ;;
                esac
-               c=$((++c))
+               ((c++))
        done
        if [ -z "$remote" ]; then
                __gitcomp_nl "$(__git_remotes)"
@@ -776,7 +777,7 @@ __git_complete_remote_or_refspec ()
                        __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                fi
                ;;
-       pull)
+       pull|remote)
                if [ $lhs = 1 ]; then
                        __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
                else
@@ -983,7 +984,7 @@ __git_find_on_cmdline ()
                                return
                        fi
                done
-               c=$((++c))
+               ((c++))
        done
 }
 
@@ -994,7 +995,7 @@ __git_has_doubledash ()
                if [ "--" = "${words[c]}" ]; then
                        return 0
                fi
-               c=$((++c))
+               ((c++))
        done
        return 1
 }
@@ -1117,7 +1118,7 @@ _git_branch ()
                -d|-m)  only_local_ref="y" ;;
                -r)     has_r="y" ;;
                esac
-               c=$((++c))
+               ((c++))
        done
 
        case "$cur" in
@@ -2091,6 +2092,7 @@ _git_config ()
                core.whitespace
                core.worktree
                diff.autorefreshindex
+               diff.statGraphWidth
                diff.external
                diff.ignoreSubmodules
                diff.mnemonicprefix
@@ -2277,7 +2279,7 @@ _git_config ()
 
 _git_remote ()
 {
-       local subcommands="add rename rm show prune update set-head"
+       local subcommands="add rename rm set-head set-branches set-url show prune update"
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@ -2285,9 +2287,12 @@ _git_remote ()
        fi
 
        case "$subcommand" in
-       rename|rm|show|prune)
+       rename|rm|set-url|show|prune)
                __gitcomp_nl "$(__git_remotes)"
                ;;
+       set-head|set-branches)
+               __git_complete_remote_or_refspec
+               ;;
        update)
                local i c='' IFS=$'\n'
                for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do
@@ -2500,7 +2505,7 @@ _git_svn ()
                        __gitcomp "
                                --merge --strategy= --verbose --dry-run
                                --fetch-all --no-rebase --commit-url
-                               --revision $cmt_opts $fc_opts
+                               --revision --interactive $cmt_opts $fc_opts
                                "
                        ;;
                set-tree,--*)
@@ -2568,7 +2573,7 @@ _git_tag ()
                        f=1
                        ;;
                esac
-               c=$((++c))
+               ((c++))
        done
 
        case "$prev" in
@@ -2621,7 +2626,7 @@ _git ()
                --help) command="help"; break ;;
                *) command="$i"; break ;;
                esac
-               c=$((++c))
+               ((c++))
        done
 
        if [ -z "$command" ]; then
index 1b7b6df..502e03b 100644 (file)
@@ -14,13 +14,15 @@ Instead, this script post-processes the line-oriented diff, finds pairs
 of lines, and highlights the differing segments.  It's currently very
 simple and stupid about doing these tasks. In particular:
 
-  1. It will only highlight a pair of lines if they are the only two
-     lines in a hunk.  It could instead try to match up "before" and
-     "after" lines for a given hunk into pairs of similar lines.
-     However, this may end up visually distracting, as the paired
-     lines would have other highlighted lines in between them. And in
-     practice, the lines which most need attention called to their
-     small, hard-to-see changes are touching only a single line.
+  1. It will only highlight hunks in which the number of removed and
+     added lines is the same, and it will pair lines within the hunk by
+     position (so the first removed line is compared to the first added
+     line, and so forth). This is simple and tends to work well in
+     practice. More complex changes don't highlight well, so we tend to
+     exclude them due to the "same number of removed and added lines"
+     restriction. Or even if we do try to highlight them, they end up
+     not highlighting because of our "don't highlight if the whole line
+     would be highlighted" rule.
 
   2. It will find the common prefix and suffix of two lines, and
      consider everything in the middle to be "different". It could
@@ -55,3 +57,96 @@ following in your git configuration:
        show = diff-highlight | less
        diff = diff-highlight | less
 ---------------------------------------------
+
+Bugs
+----
+
+Because diff-highlight relies on heuristics to guess which parts of
+changes are important, there are some cases where the highlighting is
+more distracting than useful. Fortunately, these cases are rare in
+practice, and when they do occur, the worst case is simply a little
+extra highlighting. This section documents some cases known to be
+sub-optimal, in case somebody feels like working on improving the
+heuristics.
+
+1. Two changes on the same line get highlighted in a blob. For example,
+   highlighting:
+
+----------------------------------------------
+-foo(buf, size);
++foo(obj->buf, obj->size);
+----------------------------------------------
+
+   yields (where the inside of "+{}" would be highlighted):
+
+----------------------------------------------
+-foo(buf, size);
++foo(+{obj->buf, obj->}size);
+----------------------------------------------
+
+   whereas a more semantically meaningful output would be:
+
+----------------------------------------------
+-foo(buf, size);
++foo(+{obj->}buf, +{obj->}size);
+----------------------------------------------
+
+   Note that doing this right would probably involve a set of
+   content-specific boundary patterns, similar to word-diff. Otherwise
+   you get junk like:
+
+-----------------------------------------------------
+-this line has some -{i}nt-{ere}sti-{ng} text on it
++this line has some +{fa}nt+{a}sti+{c} text on it
+-----------------------------------------------------
+
+   which is less readable than the current output.
+
+2. The multi-line matching assumes that lines in the pre- and post-image
+   match by position. This is often the case, but can be fooled when a
+   line is removed from the top and a new one added at the bottom (or
+   vice versa). Unless the lines in the middle are also changed, diffs
+   will show this as two hunks, and it will not get highlighted at all
+   (which is good). But if the lines in the middle are changed, the
+   highlighting can be misleading. Here's a pathological case:
+
+-----------------------------------------------------
+-one
+-two
+-three
+-four
++two 2
++three 3
++four 4
++five 5
+-----------------------------------------------------
+
+   which gets highlighted as:
+
+-----------------------------------------------------
+-one
+-t-{wo}
+-three
+-f-{our}
++two 2
++t+{hree 3}
++four 4
++f+{ive 5}
+-----------------------------------------------------
+
+   because it matches "two" to "three 3", and so forth. It would be
+   nicer as:
+
+-----------------------------------------------------
+-one
+-two
+-three
+-four
++two +{2}
++three +{3}
++four +{4}
++five 5
+-----------------------------------------------------
+
+   which would probably involve pre-matching the lines into pairs
+   according to some heuristic.
index d893898..c4404d4 100755 (executable)
@@ -1,28 +1,37 @@
 #!/usr/bin/perl
 
+use warnings FATAL => 'all';
+use strict;
+
 # Highlight by reversing foreground and background. You could do
 # other things like bold or underline if you prefer.
 my $HIGHLIGHT   = "\x1b[7m";
 my $UNHIGHLIGHT = "\x1b[27m";
 my $COLOR = qr/\x1b\[[0-9;]*m/;
+my $BORING = qr/$COLOR|\s/;
 
-my @window;
+my @removed;
+my @added;
+my $in_hunk;
 
 while (<>) {
-       # We highlight only single-line changes, so we need
-       # a 4-line window to make a decision on whether
-       # to highlight.
-       push @window, $_;
-       next if @window < 4;
-       if ($window[0] =~ /^$COLOR*(\@| )/ &&
-           $window[1] =~ /^$COLOR*-/ &&
-           $window[2] =~ /^$COLOR*\+/ &&
-           $window[3] !~ /^$COLOR*\+/) {
-               print shift @window;
-               show_pair(shift @window, shift @window);
+       if (!$in_hunk) {
+               print;
+               $in_hunk = /^$COLOR*\@/;
+       }
+       elsif (/^$COLOR*-/) {
+               push @removed, $_;
+       }
+       elsif (/^$COLOR*\+/) {
+               push @added, $_;
        }
        else {
-               print shift @window;
+               show_hunk(\@removed, \@added);
+               @removed = ();
+               @added = ();
+
+               print;
+               $in_hunk = /^$COLOR*[\@ ]/;
        }
 
        # Most of the time there is enough output to keep things streaming,
@@ -38,23 +47,40 @@ while (<>) {
        }
 }
 
-# Special case a single-line hunk at the end of file.
-if (@window == 3 &&
-    $window[0] =~ /^$COLOR*(\@| )/ &&
-    $window[1] =~ /^$COLOR*-/ &&
-    $window[2] =~ /^$COLOR*\+/) {
-       print shift @window;
-       show_pair(shift @window, shift @window);
-}
-
-# And then flush any remaining lines.
-while (@window) {
-       print shift @window;
-}
+# Flush any queued hunk (this can happen when there is no trailing context in
+# the final diff of the input).
+show_hunk(\@removed, \@added);
 
 exit 0;
 
-sub show_pair {
+sub show_hunk {
+       my ($a, $b) = @_;
+
+       # If one side is empty, then there is nothing to compare or highlight.
+       if (!@$a || !@$b) {
+               print @$a, @$b;
+               return;
+       }
+
+       # If we have mismatched numbers of lines on each side, we could try to
+       # be clever and match up similar lines. But for now we are simple and
+       # stupid, and only handle multi-line hunks that remove and add the same
+       # number of lines.
+       if (@$a != @$b) {
+               print @$a, @$b;
+               return;
+       }
+
+       my @queue;
+       for (my $i = 0; $i < @$a; $i++) {
+               my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]);
+               print $rm;
+               push @queue, $add;
+       }
+       print @queue;
+}
+
+sub highlight_pair {
        my @a = split_line(shift);
        my @b = split_line(shift);
 
@@ -101,8 +127,14 @@ sub show_pair {
                }
        }
 
-       print highlight(\@a, $pa, $sa);
-       print highlight(\@b, $pb, $sb);
+       if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) {
+               return highlight_line(\@a, $pa, $sa),
+                      highlight_line(\@b, $pb, $sb);
+       }
+       else {
+               return join('', @a),
+                      join('', @b);
+       }
 }
 
 sub split_line {
@@ -111,7 +143,7 @@ sub split_line {
               split /($COLOR*)/;
 }
 
-sub highlight {
+sub highlight_line {
        my ($line, $prefix, $suffix) = @_;
 
        return join('',
@@ -122,3 +154,20 @@ sub highlight {
                @{$line}[($suffix+1)..$#$line]
        );
 }
+
+# Pairs are interesting to highlight only if we are going to end up
+# highlighting a subset (i.e., not the whole line). Otherwise, the highlighting
+# is just useless noise. We can detect this by finding either a matching prefix
+# or suffix (disregarding boring bits like whitespace and colorization).
+sub is_pair_interesting {
+       my ($a, $pa, $sa, $b, $pb, $sb) = @_;
+       my $prefix_a = join('', @$a[0..($pa-1)]);
+       my $prefix_b = join('', @$b[0..($pb-1)]);
+       my $suffix_a = join('', @$a[($sa+1)..$#$a]);
+       my $suffix_b = join('', @$b[($sb+1)..$#$b]);
+
+       return $prefix_a !~ /^$COLOR*-$BORING*$/ ||
+              $prefix_b !~ /^$COLOR*\+$BORING*$/ ||
+              $suffix_a !~ /^$BORING*$/ ||
+              $suffix_b !~ /^$BORING*$/;
+}
diff --git a/contrib/diffall/README b/contrib/diffall/README
new file mode 100644 (file)
index 0000000..507f17d
--- /dev/null
@@ -0,0 +1,31 @@
+The git-diffall script provides a directory based diff mechanism
+for git.
+
+To determine what diff viewer is used, the script requires either
+the 'diff.tool' or 'merge.tool' configuration option to be set.
+
+This script is compatible with most common forms used to specify a
+range of revisions to diff:
+
+  1. git diffall: shows diff between working tree and staged changes
+  2. git diffall --cached [<commit>]: shows diff between staged
+     changes and HEAD (or other named commit)
+  3. git diffall <commit>: shows diff between working tree and named
+     commit
+  4. git diffall <commit> <commit>: show diff between two named commits
+  5. git diffall <commit>..<commit>: same as above
+  6. git diffall <commit>...<commit>: show the changes on the branch
+     containing and up to the second, starting at a common ancestor
+     of both <commit>
+
+Note: all forms take an optional path limiter [-- <path>*]
+
+The '--extcmd=<command>' option allows the user to specify a custom
+command for viewing diffs.  When given, configured defaults are
+ignored and the script runs $command $LOCAL $REMOTE.  Additionally,
+$BASE is set in the environment.
+
+This script is based on an example provided by Thomas Rast on the
+Git list [1]:
+
+[1] http://thread.gmane.org/gmane.comp.version-control.git/124807
diff --git a/contrib/diffall/git-diffall b/contrib/diffall/git-diffall
new file mode 100755 (executable)
index 0000000..9bbd27f
--- /dev/null
@@ -0,0 +1,261 @@
+#!/bin/sh
+# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com>
+#
+# Perform a directory diff between commits in the repository using
+# the external diff or merge tool specified in the user's config.
+
+USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*]
+
+    --cached     Compare to the index rather than the working tree.
+
+    --copy-back  Copy files back to the working tree when the diff
+                 tool exits (in case they were modified by the
+                 user).  This option is only valid if the diff
+                 compared with the working tree.
+
+    -x=<command>
+    --extcmd=<command>  Specify a custom command for viewing diffs.
+                 git-diffall ignores the configured defaults and
+                 runs $command $LOCAL $REMOTE when this option is
+                 specified. Additionally, $BASE is set in the
+                 environment.
+'
+
+SUBDIRECTORY_OK=1
+. "$(git --exec-path)/git-sh-setup"
+
+TOOL_MODE=diff
+. "$(git --exec-path)/git-mergetool--lib"
+
+merge_tool="$(get_merge_tool)"
+if test -z "$merge_tool"
+then
+       echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set."
+       usage
+fi
+
+start_dir=$(pwd)
+
+# needed to access tar utility
+cdup=$(git rev-parse --show-cdup) &&
+cd "$cdup" || {
+       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+       exit 1
+}
+
+# mktemp is not available on all platforms (missing from msysgit)
+# Use a hard-coded tmp dir if it is not available
+tmp="$(mktemp -d -t tmp.XXXXXX 2>/dev/null)" || {
+       tmp=/tmp/git-diffall-tmp.$$
+       mkdir "$tmp" || exit 1
+}
+
+trap 'rm -rf "$tmp" 2>/dev/null' EXIT
+
+left=
+right=
+paths=
+dashdash_seen=
+compare_staged=
+merge_base=
+left_dir=
+right_dir=
+diff_tool=
+copy_back=
+
+while test $# != 0
+do
+       case "$1" in
+       -h|--h|--he|--hel|--help)
+               usage
+               ;;
+       --cached)
+               compare_staged=1
+               ;;
+       --copy-back)
+               copy_back=1
+               ;;
+       -x|--e|--ex|--ext|--extc|--extcm|--extcmd)
+               if test $# = 1
+               then
+                       echo You must specify the tool for use with --extcmd
+                       usage
+               else
+                       diff_tool=$2
+                       shift
+               fi
+               ;;
+       --)
+               dashdash_seen=1
+               ;;
+       -*)
+               echo Invalid option: "$1"
+               usage
+               ;;
+       *)
+               # could be commit, commit range or path limiter
+               case "$1" in
+               *...*)
+                       left=${1%...*}
+                       right=${1#*...}
+                       merge_base=1
+                       ;;
+               *..*)
+                       left=${1%..*}
+                       right=${1#*..}
+                       ;;
+               *)
+                       if test -n "$dashdash_seen"
+                       then
+                               paths="$paths$1 "
+                       elif test -z "$left"
+                       then
+                               left=$1
+                       elif test -z "$right"
+                       then
+                               right=$1
+                       else
+                               paths="$paths$1 "
+                       fi
+                       ;;
+               esac
+               ;;
+       esac
+       shift
+done
+
+# Determine the set of files which changed
+if test -n "$left" && test -n "$right"
+then
+       left_dir="cmt-$(git rev-parse --short $left)"
+       right_dir="cmt-$(git rev-parse --short $right)"
+
+       if test -n "$compare_staged"
+       then
+               usage
+       elif test -n "$merge_base"
+       then
+               git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist"
+       else
+               git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist"
+       fi
+elif test -n "$left"
+then
+       left_dir="cmt-$(git rev-parse --short $left)"
+
+       if test -n "$compare_staged"
+       then
+               right_dir="staged"
+               git diff --name-only --cached "$left" -- $paths >"$tmp/filelist"
+       else
+               right_dir="working_tree"
+               git diff --name-only "$left" -- $paths >"$tmp/filelist"
+       fi
+else
+       left_dir="HEAD"
+
+       if test -n "$compare_staged"
+       then
+               right_dir="staged"
+               git diff --name-only --cached -- $paths >"$tmp/filelist"
+       else
+               right_dir="working_tree"
+               git diff --name-only -- $paths >"$tmp/filelist"
+       fi
+fi
+
+# Exit immediately if there are no diffs
+if test ! -s "$tmp/filelist"
+then
+       exit 0
+fi
+
+if test -n "$copy_back" && test "$right_dir" != "working_tree"
+then
+       echo "--copy-back is only valid when diff includes the working tree."
+       exit 1
+fi
+
+# Create the named tmp directories that will hold the files to be compared
+mkdir -p "$tmp/$left_dir" "$tmp/$right_dir"
+
+# Populate the tmp/right_dir directory with the files to be compared
+if test -n "$right"
+then
+       while read name
+       do
+               ls_list=$(git ls-tree $right "$name")
+               if test -n "$ls_list"
+               then
+                       mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+                       git show "$right":"$name" >"$tmp/$right_dir/$name" || true
+               fi
+       done < "$tmp/filelist"
+elif test -n "$compare_staged"
+then
+       while read name
+       do
+               ls_list=$(git ls-files -- "$name")
+               if test -n "$ls_list"
+               then
+                       mkdir -p "$tmp/$right_dir/$(dirname "$name")"
+                       git show :"$name" >"$tmp/$right_dir/$name"
+               fi
+       done < "$tmp/filelist"
+else
+       # Mac users have gnutar rather than tar
+       (tar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && tar -x)) || {
+               gnutar --ignore-failed-read -c -T "$tmp/filelist" | (cd "$tmp/$right_dir" && gnutar -x)
+       }
+fi
+
+# Populate the tmp/left_dir directory with the files to be compared
+while read name
+do
+       if test -n "$left"
+       then
+               ls_list=$(git ls-tree $left "$name")
+               if test -n "$ls_list"
+               then
+                       mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+                       git show "$left":"$name" >"$tmp/$left_dir/$name" || true
+               fi
+       else
+               if test -n "$compare_staged"
+               then
+                       ls_list=$(git ls-tree HEAD "$name")
+                       if test -n "$ls_list"
+                       then
+                               mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+                               git show HEAD:"$name" >"$tmp/$left_dir/$name"
+                       fi
+               else
+                       mkdir -p "$tmp/$left_dir/$(dirname "$name")"
+                       git show :"$name" >"$tmp/$left_dir/$name"
+               fi
+       fi
+done < "$tmp/filelist"
+
+cd "$tmp"
+LOCAL="$left_dir"
+REMOTE="$right_dir"
+
+if test -n "$diff_tool"
+then
+       export BASE
+       eval $diff_tool '"$LOCAL"' '"$REMOTE"'
+else
+       run_merge_tool "$merge_tool" false
+fi
+
+# Copy files back to the working dir, if requested
+if test -n "$copy_back" && test "$right_dir" = "working_tree"
+then
+       cd "$start_dir"
+       git_top_dir=$(git rev-parse --show-toplevel)
+       find "$tmp/$right_dir" -type f |
+       while read file
+       do
+               cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}"
+       done
+fi
index 9ccc87b..c5362c4 100755 (executable)
@@ -10,7 +10,7 @@
 
 import optparse, sys, os, marshal, subprocess, shelve
 import tempfile, getopt, os.path, time, platform
-import re
+import re, shutil
 
 verbose = False
 
@@ -38,7 +38,7 @@ def p4_build_cmd(cmd):
 
     host = gitConfig("git-p4.host")
     if len(host) > 0:
-        real_cmd += ["-h", host]
+        real_cmd += ["-H", host]
 
     client = gitConfig("git-p4.client")
     if len(client) > 0:
@@ -186,6 +186,47 @@ def split_p4_type(p4type):
         mods = s[1]
     return (base, mods)
 
+#
+# return the raw p4 type of a file (text, text+ko, etc)
+#
+def p4_type(file):
+    results = p4CmdList(["fstat", "-T", "headType", file])
+    return results[0]['headType']
+
+#
+# Given a type base and modifier, return a regexp matching
+# the keywords that can be expanded in the file
+#
+def p4_keywords_regexp_for_type(base, type_mods):
+    if base in ("text", "unicode", "binary"):
+        kwords = None
+        if "ko" in type_mods:
+            kwords = 'Id|Header'
+        elif "k" in type_mods:
+            kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
+        else:
+            return None
+        pattern = r"""
+            \$              # Starts with a dollar, followed by...
+            (%s)            # one of the keywords, followed by...
+            (:[^$]+)?       # possibly an old expansion, followed by...
+            \$              # another dollar
+            """ % kwords
+        return pattern
+    else:
+        return None
+
+#
+# Given a file, return a regexp matching the possible
+# RCS keywords that will be expanded, or None for files
+# with kw expansion turned off.
+#
+def p4_keywords_regexp_for_file(file):
+    if not os.path.exists(file):
+        return None
+    else:
+        (type_base, type_mods) = split_p4_type(p4_type(file))
+        return p4_keywords_regexp_for_type(type_base, type_mods)
 
 def setP4ExecBit(file, mode):
     # Reopens an already open file and changes the execute bit to match
@@ -603,6 +644,26 @@ class Command:
 class P4UserMap:
     def __init__(self):
         self.userMapFromPerforceServer = False
+        self.myP4UserId = None
+
+    def p4UserId(self):
+        if self.myP4UserId:
+            return self.myP4UserId
+
+        results = p4CmdList("user -o")
+        for r in results:
+            if r.has_key('User'):
+                self.myP4UserId = r['User']
+                return r['User']
+        die("Could not find your p4 user id")
+
+    def p4UserIsMe(self, p4User):
+        # return True if the given p4 user is actually me
+        me = self.p4UserId()
+        if not p4User or p4User != me:
+            return False
+        else:
+            return True
 
     def getUserCacheFilename(self):
         home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
@@ -740,7 +801,6 @@ class P4Submit(Command, P4UserMap):
         self.verbose = False
         self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
         self.isWindows = (platform.system() == "Windows")
-        self.myP4UserId = None
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
@@ -774,6 +834,29 @@ class P4Submit(Command, P4UserMap):
 
         return result
 
+    def patchRCSKeywords(self, file, pattern):
+        # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
+        (handle, outFileName) = tempfile.mkstemp(dir='.')
+        try:
+            outFile = os.fdopen(handle, "w+")
+            inFile = open(file, "r")
+            regexp = re.compile(pattern, re.VERBOSE)
+            for line in inFile.readlines():
+                line = regexp.sub(r'$\1$', line)
+                outFile.write(line)
+            inFile.close()
+            outFile.close()
+            # Forcibly overwrite the original file
+            os.unlink(file)
+            shutil.move(outFileName, file)
+        except:
+            # cleanup our temporary file
+            os.unlink(outFileName)
+            print "Failed to strip RCS keywords in %s" % file
+            raise
+
+        print "Patched up RCS keywords in %s" % file
+
     def p4UserForCommit(self,id):
         # Return the tuple (perforce user,git email) for a given git commit id
         self.getUserMapFromPerforceServer()
@@ -839,7 +922,7 @@ class P4Submit(Command, P4UserMap):
     def canChangeChangelists(self):
         # check to see if we have p4 admin or super-user permissions, either of
         # which are required to modify changelists.
-        results = p4CmdList("protects %s" % self.depotPath)
+        results = p4CmdList(["protects", self.depotPath])
         for r in results:
             if r.has_key('perm'):
                 if r['perm'] == 'admin':
@@ -848,25 +931,6 @@ class P4Submit(Command, P4UserMap):
                     return 1
         return 0
 
-    def p4UserId(self):
-        if self.myP4UserId:
-            return self.myP4UserId
-
-        results = p4CmdList("user -o")
-        for r in results:
-            if r.has_key('User'):
-                self.myP4UserId = r['User']
-                return r['User']
-        die("Could not find your p4 user id")
-
-    def p4UserIsMe(self, p4User):
-        # return True if the given p4 user is actually me
-        me = self.p4UserId()
-        if not p4User or p4User != me:
-            return False
-        else:
-            return True
-
     def prepareSubmitTemplate(self):
         # remove lines in the Files section that show changes to files outside the depot path we're committing into
         template = ""
@@ -958,6 +1022,7 @@ class P4Submit(Command, P4UserMap):
         filesToDelete = set()
         editedFiles = set()
         filesToChangeExecBit = {}
+
         for line in diff:
             diff = parseDiffTreeEntry(line)
             modifier = diff['status']
@@ -1004,9 +1069,45 @@ class P4Submit(Command, P4UserMap):
         patchcmd = diffcmd + " | git apply "
         tryPatchCmd = patchcmd + "--check -"
         applyPatchCmd = patchcmd + "--check --apply -"
+        patch_succeeded = True
 
         if os.system(tryPatchCmd) != 0:
+            fixed_rcs_keywords = False
+            patch_succeeded = False
             print "Unfortunately applying the change failed!"
+
+            # Patch failed, maybe it's just RCS keyword woes. Look through
+            # the patch to see if that's possible.
+            if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
+                file = None
+                pattern = None
+                kwfiles = {}
+                for file in editedFiles | filesToDelete:
+                    # did this file's delta contain RCS keywords?
+                    pattern = p4_keywords_regexp_for_file(file)
+
+                    if pattern:
+                        # this file is a possibility...look for RCS keywords.
+                        regexp = re.compile(pattern, re.VERBOSE)
+                        for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
+                            if regexp.search(line):
+                                if verbose:
+                                    print "got keyword match on %s in %s in %s" % (pattern, line, file)
+                                kwfiles[file] = pattern
+                                break
+
+                for file in kwfiles:
+                    if verbose:
+                        print "zapping %s with %s" % (line,pattern)
+                    self.patchRCSKeywords(file, kwfiles[file])
+                    fixed_rcs_keywords = True
+
+            if fixed_rcs_keywords:
+                print "Retrying the patch with RCS keywords cleaned up"
+                if os.system(tryPatchCmd) == 0:
+                    patch_succeeded = True
+
+        if not patch_succeeded:
             print "What do you want to do?"
             response = "x"
             while response != "s" and response != "a" and response != "w":
@@ -1256,8 +1357,8 @@ class View(object):
                 die("Can't handle * wildcards in view: %s" % self.path)
             triple_dot_index = self.path.find("...")
             if triple_dot_index >= 0:
-                if not self.path.endswith("..."):
-                    die("Can handle ... wildcard only at end of path: %s" %
+                if triple_dot_index != len(self.path) - 3:
+                    die("Can handle only single ... wildcard, at end: %s" %
                         self.path)
                 self.ends_triple_dot = True
 
@@ -1312,7 +1413,7 @@ class View(object):
             if self.exclude:
                 c = "-"
             return "View.Mapping: %s%s -> %s" % \
-                   (c, self.depot_side, self.client_side)
+                   (c, self.depot_side.path, self.client_side.path)
 
         def map_depot_to_client(self, depot_path):
             """Calculate the client path if using this mapping on the
@@ -1412,7 +1513,8 @@ class View(object):
             else:
                 # This mapping matched; no need to search any further.
                 # But, the mapping could be rejected if the client path
-                # has already been claimed by an earlier mapping.
+                # has already been claimed by an earlier mapping (i.e.
+                # one later in the list, which we are walking backwards).
                 already_mapped_in_client = False
                 for f in paths_filled:
                     # this is View.Path.match
@@ -1479,6 +1581,8 @@ class P4Sync(Command, P4UserMap):
         self.useClientSpec = False
         self.useClientSpec_from_options = False
         self.clientSpecDirs = None
+        self.tempBranches = []
+        self.tempBranchLocation = "git-p4-tmp"
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
@@ -1500,6 +1604,14 @@ class P4Sync(Command, P4UserMap):
                    .replace("%25", "%")
         return path
 
+    # Force a checkpoint in fast-import and wait for it to finish
+    def checkpoint(self):
+        self.gitStream.write("checkpoint\n\n")
+        self.gitStream.write("progress checkpoint\n\n")
+        out = self.gitOutput.readline()
+        if self.verbose:
+            print "checkpoint finished: " + out
+
     def extractFilesFromCommit(self, commit):
         self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
                              for path in self.cloneExclude]
@@ -1624,15 +1736,12 @@ class P4Sync(Command, P4UserMap):
 
         # Note that we do not try to de-mangle keywords on utf16 files,
         # even though in theory somebody may want that.
-        if type_base in ("text", "unicode", "binary"):
-            if "ko" in type_mods:
-                text = ''.join(contents)
-                text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text)
-                contents = [ text ]
-            elif "k" in type_mods:
-                text = ''.join(contents)
-                text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text)
-                contents = [ text ]
+        pattern = p4_keywords_regexp_for_type(type_base, type_mods)
+        if pattern:
+            regexp = re.compile(pattern, re.VERBOSE)
+            text = ''.join(contents)
+            text = regexp.sub(r'$\1$', text)
+            contents = [ text ]
 
         self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
 
@@ -1714,6 +1823,12 @@ class P4Sync(Command, P4UserMap):
             if self.stream_file.has_key('depotFile'):
                 self.streamOneP4File(self.stream_file, self.stream_contents)
 
+    def make_email(self, userid):
+        if userid in self.users:
+            return self.users[userid]
+        else:
+            return "%s <a@b>" % userid
+
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
         author = details["user"]
@@ -1737,10 +1852,7 @@ class P4Sync(Command, P4UserMap):
         committer = ""
         if author not in self.users:
             self.getUserMapFromPerforceServer()
-        if author in self.users:
-            committer = "%s %s %s" % (self.users[author], epoch, self.tz)
-        else:
-            committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
+        committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
 
         self.gitStream.write("committer %s\n" % committer)
 
@@ -1785,15 +1897,21 @@ class P4Sync(Command, P4UserMap):
                     self.gitStream.write("from %s\n" % branch)
 
                     owner = labelDetails["Owner"]
-                    tagger = ""
-                    if author in self.users:
-                        tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
+
+                    # Try to use the owner of the p4 label, or failing that,
+                    # the current p4 user id.
+                    if owner:
+                        email = self.make_email(owner)
                     else:
-                        tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
+                        email = self.make_email(self.p4UserId())
+                    tagger = "%s %s %s" % (email, epoch, self.tz)
+
                     self.gitStream.write("tagger %s\n" % tagger)
-                    self.gitStream.write("data <<EOT\n")
-                    self.gitStream.write(labelDetails["Description"])
-                    self.gitStream.write("EOT\n\n")
+
+                    description = labelDetails["Description"]
+                    self.gitStream.write("data %d\n" % len(description))
+                    self.gitStream.write(description)
+                    self.gitStream.write("\n")
 
                 else:
                     if not self.silent:
@@ -1808,7 +1926,7 @@ class P4Sync(Command, P4UserMap):
     def getLabels(self):
         self.labels = {}
 
-        l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
+        l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
         if len(l) > 0 and not self.silent:
             print "Finding files belonging to labels in %s" % `self.depotPaths`
 
@@ -1850,7 +1968,7 @@ class P4Sync(Command, P4UserMap):
             command = "branches"
 
         for info in p4CmdList(command):
-            details = p4Cmd("branch -o %s" % info["branch"])
+            details = p4Cmd(["branch", "-o", info["branch"]])
             viewIdx = 0
             while details.has_key("View%s" % viewIdx):
                 paths = details["View%s" % viewIdx].split(" ")
@@ -1988,7 +2106,7 @@ class P4Sync(Command, P4UserMap):
         sourceRef = self.gitRefForBranch(sourceBranch)
         #print "source " + sourceBranch
 
-        branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
+        branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
         #print "branch parent: %s" % branchParentChange
         gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
         if len(gitParent) > 0:
@@ -1998,10 +2116,24 @@ class P4Sync(Command, P4UserMap):
         self.importChanges(changes)
         return True
 
+    def searchParent(self, parent, branch, target):
+        parentFound = False
+        for blob in read_pipe_lines(["git", "rev-list", "--reverse", "--no-merges", parent]):
+            blob = blob.strip()
+            if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
+                parentFound = True
+                if self.verbose:
+                    print "Found parent of %s in commit %s" % (branch, blob)
+                break
+        if parentFound:
+            return blob
+        else:
+            return None
+
     def importChanges(self, changes):
         cnt = 1
         for change in changes:
-            description = p4Cmd("describe %s" % change)
+            description = p4Cmd(["describe", str(change)])
             self.updateOptionDict(description)
 
             if not self.silent:
@@ -2054,7 +2186,21 @@ class P4Sync(Command, P4UserMap):
                             parent = self.initialParents[branch]
                             del self.initialParents[branch]
 
-                        self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+                        blob = None
+                        if len(parent) > 0:
+                            tempBranch = os.path.join(self.tempBranchLocation, "%d" % (change))
+                            if self.verbose:
+                                print "Creating temporary branch: " + tempBranch
+                            self.commit(description, filesForCommit, tempBranch, [branchPrefix])
+                            self.tempBranches.append(tempBranch)
+                            self.checkpoint()
+                            blob = self.searchParent(parent, branch, tempBranch)
+                        if blob:
+                            self.commit(description, filesForCommit, branch, [branchPrefix], blob)
+                        else:
+                            if self.verbose:
+                                print "Parent of %s not found. Committing into head of %s" % (branch, parent)
+                            self.commit(description, filesForCommit, branch, [branchPrefix], parent)
                 else:
                     files = self.extractFilesFromCommit(description)
                     self.commit(description, files, self.branch, self.depotPaths,
@@ -2366,6 +2512,12 @@ class P4Sync(Command, P4UserMap):
         self.gitOutput.close()
         self.gitError.close()
 
+        # Cleanup temporary branches created during import
+        if self.tempBranches != []:
+            for branch in self.tempBranches:
+                read_pipe("git update-ref -d %s" % branch)
+            os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
+
         return True
 
 class P4Rebase(Command):
index 72ffea0..2dd27ce 100644 (file)
@@ -8,7 +8,10 @@ svn-fe - convert an SVN "dumpfile" to a fast-import stream
 SYNOPSIS
 --------
 [verse]
-svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
+mkfifo backchannel &&
+svnadmin dump --deltas REPO |
+       svn-fe [url] 3<backchannel |
+       git fast-import --cat-blob-fd=3 3>backchannel
 
 DESCRIPTION
 -----------
@@ -29,9 +32,6 @@ Subversion's repository dump format is documented in full in
 Files in this format can be generated using the 'svnadmin dump' or
 'svk admin dump' command.
 
-Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3)
-are not supported.
-
 OUTPUT FORMAT
 -------------
 The fast-import format is documented by the git-fast-import(1)
index 4534e2c..6602155 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -452,6 +452,7 @@ static struct convert_driver {
        struct convert_driver *next;
        const char *smudge;
        const char *clean;
+       int required;
 } *user_convert, **user_convert_tail;
 
 static int read_convert_config(const char *var, const char *value, void *cb)
@@ -495,6 +496,11 @@ static int read_convert_config(const char *var, const char *value, void *cb)
        if (!strcmp("clean", ep))
                return git_config_string(&drv->clean, var, value);
 
+       if (!strcmp("required", ep)) {
+               drv->required = git_config_bool(var, value);
+               return 0;
+       }
+
        return 0;
 }
 
@@ -773,13 +779,19 @@ int convert_to_git(const char *path, const char *src, size_t len,
 {
        int ret = 0;
        const char *filter = NULL;
+       int required = 0;
        struct conv_attrs ca;
 
        convert_attrs(&ca, path);
-       if (ca.drv)
+       if (ca.drv) {
                filter = ca.drv->clean;
+               required = ca.drv->required;
+       }
 
        ret |= apply_filter(path, src, len, dst, filter);
+       if (!ret && required)
+               die("%s: clean filter '%s' failed", path, ca.drv->name);
+
        if (ret && dst) {
                src = dst->buf;
                len = dst->len;
@@ -797,13 +809,16 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
                                            size_t len, struct strbuf *dst,
                                            int normalizing)
 {
-       int ret = 0;
+       int ret = 0, ret_filter = 0;
        const char *filter = NULL;
+       int required = 0;
        struct conv_attrs ca;
 
        convert_attrs(&ca, path);
-       if (ca.drv)
+       if (ca.drv) {
                filter = ca.drv->smudge;
+               required = ca.drv->required;
+       }
 
        ret |= ident_to_worktree(path, src, len, dst, ca.ident);
        if (ret) {
@@ -822,7 +837,12 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
                        len = dst->len;
                }
        }
-       return ret | apply_filter(path, src, len, dst, filter);
+
+       ret_filter = apply_filter(path, src, len, dst, filter);
+       if (!ret_filter && required)
+               die("%s: smudge filter %s failed", path, ca.drv->name);
+
+       return ret | ret_filter;
 }
 
 int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
diff --git a/ctype.c b/ctype.c
index af722f9..9353271 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -25,3 +25,39 @@ unsigned char sane_ctype[256] = {
        A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0,         /* 112..127 */
        /* Nothing in the 128.. range */
 };
+
+/* For case-insensitive kwset */
+const char tolower_trans_tbl[256] = {
+       0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+       0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+       0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+        ' ',  '!',  '"',  '#',  '$',  '%',  '&', 0x27,
+        '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
+        '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
+        '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
+        '@',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
+        'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
+        'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
+        'x',  'y',  'z',  '[', 0x5c,  ']',  '^',  '_',
+        '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
+        'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
+        'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
+        'x',  'y',  'z',  '{',  '|',  '}',  '~', 0x7f,
+       0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+       0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+       0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+       0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+       0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+       0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+       0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+       0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+       0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+       0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+       0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+       0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+       0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+       0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+       0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+       0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+};
index 15ce918..ab21e66 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1086,6 +1086,8 @@ static int serve(struct string_list *listen_addr, int listen_port,
 
        drop_privileges(cred);
 
+       loginfo("Ready to rumble");
+
        return service_loop(&socklist);
 }
 
@@ -1270,10 +1272,8 @@ int main(int argc, char **argv)
        if (inetd_mode || serve_mode)
                return execute();
 
-       if (detach) {
+       if (detach)
                daemonize();
-               loginfo("Ready to rumble");
-       }
        else
                sanitize_stdfds();
 
diff --git a/diff.c b/diff.c
index 05d0814..377ec1e 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 static int diff_no_prefix;
+static int diff_stat_graph_width;
 static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
 
@@ -156,6 +157,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_no_prefix = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.statgraphwidth")) {
+               diff_stat_graph_width = git_config_int(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
@@ -177,11 +182,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       switch (userdiff_config(var, value)) {
-               case 0: break;
-               case -1: return -1;
-               default: return 0;
-       }
+       if (userdiff_config(var, value) < 0)
+               return -1;
 
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
@@ -1378,7 +1380,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
-       int width, name_width, count;
+       int width, name_width, graph_width, number_width = 4, count;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
        int extra_shown = 0;
@@ -1392,25 +1394,15 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                line_prefix = msg->buf;
        }
 
-       width = options->stat_width ? options->stat_width : 80;
-       name_width = options->stat_name_width ? options->stat_name_width : 50;
        count = options->stat_count ? options->stat_count : data->nr;
 
-       /* Sanity: give at least 5 columns to the graph,
-        * but leave at least 10 columns for the name.
-        */
-       if (width < 25)
-               width = 25;
-       if (name_width < 10)
-               name_width = 10;
-       else if (width < name_width + 15)
-               name_width = width - 15;
-
-       /* Find the longest filename and max number of changes */
        reset = diff_get_color_opt(options, DIFF_RESET);
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
 
+       /*
+        * Find the longest filename and max number of changes
+        */
        for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
@@ -1431,19 +1423,72 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        }
        count = i; /* min(count, data->nr) */
 
-       /* Compute the width of the graph part;
-        * 10 is for one blank at the beginning of the line plus
-        * " | count " between the name and the graph.
+       /*
+        * We have width = stat_width or term_columns() columns total.
+        * We want a maximum of min(max_len, stat_name_width) for the name part.
+        * We want a maximum of min(max_change, stat_graph_width) for the +- part.
+        * We also need 1 for " " and 4 + decimal_width(max_change)
+        * for " | NNNN " and one the empty column at the end, altogether
+        * 6 + decimal_width(max_change).
         *
-        * From here on, name_width is the width of the name area,
-        * and width is the width of the graph area.
+        * If there's not enough space, we will use the smaller of
+        * stat_name_width (if set) and 5/8*width for the filename,
+        * and the rest for constant elements + graph part, but no more
+        * than stat_graph_width for the graph part.
+        * (5/8 gives 50 for filename and 30 for the constant parts + graph
+        * for the standard terminal size).
+        *
+        * In other words: stat_width limits the maximum width, and
+        * stat_name_width fixes the maximum width of the filename,
+        * and is also used to divide available columns if there
+        * aren't enough.
         */
-       name_width = (name_width < max_len) ? name_width : max_len;
-       if (width < (name_width + 10) + max_change)
-               width = width - (name_width + 10);
+
+       if (options->stat_width == -1)
+               width = term_columns();
        else
-               width = max_change;
+               width = options->stat_width ? options->stat_width : 80;
 
+       if (options->stat_graph_width == -1)
+               options->stat_graph_width = diff_stat_graph_width;
+
+       /*
+        * Guarantee 3/8*16==6 for the graph part
+        * and 5/8*16==10 for the filename part
+        */
+       if (width < 16 + 6 + number_width)
+               width = 16 + 6 + number_width;
+
+       /*
+        * First assign sizes that are wanted, ignoring available width.
+        */
+       graph_width = (options->stat_graph_width &&
+                      options->stat_graph_width < max_change) ?
+               options->stat_graph_width : max_change;
+       name_width = (options->stat_name_width > 0 &&
+                     options->stat_name_width < max_len) ?
+               options->stat_name_width : max_len;
+
+       /*
+        * Adjust adjustable widths not to exceed maximum width
+        */
+       if (name_width + number_width + 6 + graph_width > width) {
+               if (graph_width > width * 3/8 - number_width - 6)
+                       graph_width = width * 3/8 - number_width - 6;
+               if (options->stat_graph_width &&
+                   graph_width > options->stat_graph_width)
+                       graph_width = options->stat_graph_width;
+               if (name_width > width - number_width - 6 - graph_width)
+                       name_width = width - number_width - 6 - graph_width;
+               else
+                       graph_width = width - number_width - 6 - name_width;
+       }
+
+       /*
+        * From here name_width is the width of the name area,
+        * and graph_width is the width of the graph area.
+        * max_change is used to scale graph properly.
+        */
        for (i = 0; i < count; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
@@ -1499,18 +1544,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                adds += add;
                dels += del;
 
-               if (width <= max_change) {
+               if (graph_width <= max_change) {
                        int total = add + del;
 
-                       total = scale_linear(add + del, width, max_change);
+                       total = scale_linear(add + del, graph_width, max_change);
                        if (total < 2 && add && del)
                                /* width >= 2 due to the sanity check */
                                total = 2;
                        if (add < del) {
-                               add = scale_linear(add, width, max_change);
+                               add = scale_linear(add, graph_width, max_change);
                                del = total - add;
                        } else {
-                               del = scale_linear(del, width, max_change);
+                               del = scale_linear(del, graph_width, max_change);
                                add = total - del;
                        }
                }
@@ -2210,7 +2255,7 @@ static void builtin_diff(const char *name_a,
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
-               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
+               if (must_show_header) {
                        fprintf(o->file, "%s", header.buf);
                        strbuf_reset(&header);
                }
@@ -3302,6 +3347,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
+       int graph_width = options->stat_graph_width;
        int count = options->stat_count;
        int argcount = 1;
 
@@ -3330,6 +3376,16 @@ static int stat_opt(struct diff_options *options, const char **av)
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
+               } else if (!prefixcmp(arg, "-graph-width")) {
+                       arg += strlen("-graph-width");
+                       if (*arg == '=')
+                               graph_width = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-graph-width' requires a value");
+                       else if (!*arg) {
+                               graph_width = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
                } else if (!prefixcmp(arg, "-count")) {
                        arg += strlen("-count");
                        if (*arg == '=')
@@ -3355,6 +3411,7 @@ static int stat_opt(struct diff_options *options, const char **av)
                return 0;
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
+       options->stat_graph_width = graph_width;
        options->stat_width = width;
        options->stat_count = count;
        return argcount;
diff --git a/diff.h b/diff.h
index 7af5f1e..cb68743 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -82,6 +82,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
 #define DIFF_OPT_DIRSTAT_BY_LINE     (1 << 28)
 #define DIFF_OPT_FUNCCONTEXT         (1 << 29)
+#define DIFF_OPT_PICKAXE_IGNORE_CASE (1 << 30)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
@@ -129,6 +130,7 @@ struct diff_options {
 
        int stat_width;
        int stat_name_width;
+       int stat_graph_width;
        int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;