Sync with maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 4 Nov 2015 22:21:41 +0000 (14:21 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 4 Nov 2015 22:21:41 +0000 (14:21 -0800)
* maint:

225 files changed:
.gitignore
Documentation/RelNotes/2.7.0.txt [new file with mode: 0644]
Documentation/blame-options.txt
Documentation/config.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-for-each-ref.txt
Documentation/git-p4.txt
Documentation/git-quiltimport.txt
Documentation/git-rebase.txt
Documentation/git-remote.txt
Documentation/git-rev-list.txt
Documentation/git-stash.txt
Documentation/git-tag.txt
Documentation/git-worktree.txt
Documentation/gitignore.txt
Documentation/glossary-content.txt
Documentation/rev-list-options.txt
Documentation/technical/api-run-command.txt
GIT-VERSION-GEN
Makefile
RelNotes
archive-tar.c
archive.c
bisect.c
branch.c
branch.h
builtin.h
builtin/add.c
builtin/am.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/clean.c
builtin/clone.c
builtin/config.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/gc.c
builtin/help.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/ls-remote.c
builtin/ls-tree.c
builtin/mailinfo.c
builtin/mailsplit.c
builtin/merge-index.c
builtin/merge-recursive.c
builtin/merge.c
builtin/name-rev.c
builtin/notes.c
builtin/read-tree.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote-ext.c
builtin/remote.c
builtin/rerere.c
builtin/rev-list.c
builtin/rev-parse.c
builtin/show-branch.c
builtin/submodule--helper.c [new file with mode: 0644]
builtin/tag.c
builtin/unpack-file.c
builtin/upload-archive.c
builtin/worktree.c
bulk-checkin.c
cache.h
color.c
color.h
commit.c
compat/hstrerror.c
compat/inet_ntop.c
compat/mingw.c
compat/nedmalloc/nedmalloc.c
compat/precompose_utf8.c
compat/precompose_utf8.h
compat/regex/regcomp.c
compat/winansi.c
config.mak.uname
configure.ac
connect.c
contrib/hooks/multimail/CHANGES
contrib/hooks/multimail/CONTRIBUTING.rst [new file with mode: 0644]
contrib/hooks/multimail/README
contrib/hooks/multimail/README.Git
contrib/hooks/multimail/doc/gerrit.rst [new file with mode: 0644]
contrib/hooks/multimail/doc/gitolite.rst [new file with mode: 0644]
contrib/hooks/multimail/git_multimail.py
contrib/hooks/multimail/migrate-mailhook-config
contrib/hooks/multimail/post-receive.example
contrib/subtree/git-subtree.sh
contrib/subtree/t/t7900-subtree.sh
convert.c
credential-cache--daemon.c
daemon.c
date.c
diff-no-index.c
diff.c
dir.c
entry.c
environment.c
fast-import.c
fetch-pack.c
git-bisect.sh
git-compat-util.h
git-difftool.perl
git-filter-branch.sh
git-merge-one-file.sh
git-p4.py
git-quiltimport.sh
git-rebase.sh
git-send-email.perl
git-stash.sh
git-submodule.sh
git.c
gitk-git/gitk
gitk-git/po/bg.po
gitk-git/po/ca.po
gitk-git/po/de.po
gitk-git/po/es.po
gitk-git/po/fr.po
gitk-git/po/hu.po
gitk-git/po/it.po
gitk-git/po/ja.po
gitk-git/po/pt_br.po
gitk-git/po/ru.po
gitk-git/po/sv.po
gitk-git/po/vi.po
grep.c
hex.c
http-push.c
http-walker.c
http.c
imap-send.c
ll-merge.c
mailinfo.c [new file with mode: 0644]
mailinfo.h [new file with mode: 0644]
mailmap.c
merge-recursive.c
notes.c
pack-bitmap.c
pack-revindex.c
parse-options-cb.c
parse-options.h
path.c
pkt-line.c
progress.c
read-cache.c
ref-filter.c
ref-filter.h
reflog-walk.c
refs.c
refs.h
remote-curl.c
remote.c
rerere.c
rerere.h
revision.c
run-command.c
run-command.h
setup.c
sha1_file.c
sha1_name.c
shallow.c
sideband.c
strbuf.c
strbuf.h
submodule-config.c
submodule.c
t/README
t/t0001-init.sh
t/t0002-gitfile.sh
t/t0027-auto-crlf.sh
t/t0060-path-utils.sh
t/t1400-update-ref.sh
t/t1401-symbolic-ref.sh
t/t1430-bad-ref-name.sh
t/t2025-worktree-add.sh
t/t2027-worktree-list.sh [new file with mode: 0755]
t/t2200-add-update.sh
t/t2202-add-addremove.sh
t/t3001-ls-files-others-exclude.sh
t/t3030-merge-recursive.sh
t/t3203-branch-output.sh
t/t3210-pack-refs.sh
t/t3420-rebase-autostash.sh
t/t5505-remote.sh
t/t5700-clone-reference.sh
t/t5801-remote-helpers.sh
t/t5802-connect-helper.sh
t/t6030-bisect-porcelain.sh
t/t6031-merge-filemode.sh [new file with mode: 0755]
t/t6031-merge-recursive.sh [deleted file]
t/t6300-for-each-ref.sh
t/t6302-for-each-ref-filter.sh [new file with mode: 0755]
t/t7004-tag.sh
t/t7060-wtstatus.sh
t/t7610-mergetool.sh
t/t7800-difftool.sh
t/t8009-blame-vs-topicbranches.sh [new file with mode: 0755]
t/t9815-git-p4-submit-fail.sh
t/t9819-git-p4-case-folding.sh
t/t9822-git-p4-path-encoding.sh [new file with mode: 0755]
t/t9823-git-p4-mock-lfs.sh [new file with mode: 0755]
t/t9824-git-p4-git-lfs.sh [new file with mode: 0755]
t/test-lib-functions.sh
tag.c
test-dump-cache-tree.c
trace.c
transport.c
unpack-trees.c
upload-pack.c
url.c
utf8.c
utf8.h
walker.c
worktree.c [new file with mode: 0644]
worktree.h [new file with mode: 0644]
wrap-for-bin.sh
wrapper.c
wt-status.c
index 4fd81baf856669fb984a5a0b82a1115e840fc16d..1c2f8321386f89ef8c03d11159c97a0f194c4423 100644 (file)
 /git-status
 /git-stripspace
 /git-submodule
+/git-submodule--helper
 /git-svn
 /git-symbolic-ref
 /git-tag
diff --git a/Documentation/RelNotes/2.7.0.txt b/Documentation/RelNotes/2.7.0.txt
new file mode 100644 (file)
index 0000000..7facc02
--- /dev/null
@@ -0,0 +1,351 @@
+Git 2.7 Release Notes
+=====================
+
+Updates since v2.6
+------------------
+
+UI, Workflows & Features
+
+ * "git remote" learned "get-url" subcommand to show the URL for a
+   given remote name used for fetching and pushing.
+
+ * There was no way to defeat a configured rebase.autostash variable
+   from the command line, as "git rebase --no-autostash" was missing.
+
+ * "git log --date=local" used to only show the normal (default)
+   format in the local timezone.  The command learned to take 'local'
+   as an instruction to use the local timezone with other formats,
+
+ * The refs used during a "git bisect" session is now per-worktree so
+   that independent bisect sessions can be done in different worktrees
+   created with "git worktree add".
+
+ * Users who are too busy to type three extra keystrokes to ask for
+   "git stash show -p" can now set stash.showPatch configuration
+   varible to true to always see the actual patch, not just the list
+   of paths affected with feel for the extent of damage via diffstat.
+
+ * "quiltimport" allows to specify the series file by honoring the
+   $QUILT_SERIES environment and also --series command line option.
+
+ * The use of 'good/bad' in "git bisect" made it confusing to use when
+   hunting for a state change that is not a regression (e.g. bugfix).
+   The command learned 'old/new' and then allows the end user to
+   say e.g. "bisect start --term-old=fast --term-new=slow" to find a
+   performance regression.
+
+ * "git interpret-trailers" can now run outside of a Git repository.
+
+ * "git p4" learned to reencode the pathname it uses to communicate
+   with the p4 depot with a new option.
+
+ * Give progress meter to "git filter-branch".
+
+ * Allow a later "!/abc/def" to override an earlier "/abc" that
+   appears in the same .gitignore file to make it easier to express
+   "everything in /abc directory is ignored, except for ...".
+
+ * Teach "git p4" to send large blobs outside the repository by
+   talking to Git LFS.
+
+ * Prepare for Git on-disk repository representation to undergo
+   backward incompatible changes by introducing a new repository
+   format version "1", with an extension mechanism.
+   (merge 067fbd4 jk/repository-extension later to maint).
+
+ * "git worktree" learned a "list" subcommand.
+
+ * "git clone --dissociate" learned that it can be used even when
+   "--reference" was not used at the same time.
+
+ * "git blame" learnt to take "--first-parent" and "--reverse" at the
+   same time when it makes sense.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The infrastructure to rewrite "git submodule" in C is being built
+   incrementally.  Let's polish these early parts well enough and make
+   them graduate to 'next' and 'master', so that the more involved
+   follow-up can start cooking on a solid ground.
+
+ * Some features from "git tag -l" and "git branch -l" have been made
+   available to "git for-each-ref" so that eventually the unified
+   implementation can be shared across all three.  The version merged
+   to the 'master' branch earlier had a performance regression in "tag
+   --contains", which has since been corrected.
+
+ * Because "test_when_finished" in our test framework queues the
+   clean-up tasks to be done in a shell variable, it should not be
+   used inside a subshell.  Add a mechanism to allow 'bash' to catch
+   such uses, and fix the ones that were found.
+   (merge 0968f12 jk/test-lint-forbid-when-finished-in-subshell later to maint).
+
+ * The debugging infrastructure for pkt-line based communication has
+   been improved to mark the side-band communication specifically.
+   (merge fd89433 jk/async-pkt-line later to maint).
+
+ * Update "git branch" that list existing branches, using the
+   ref-filter API that is shared with "git tag" and "git
+   for-each-ref".
+
+ * The test for various line-ending conversions has been enhanced.
+
+ * A few test scripts around "git p4" have been improved for
+   portability.
+
+ * Many allocations that is manually counted (correctly) that are
+   followed by strcpy/sprintf have been replaced with a less error
+   prone constructs such as xstrfmt.
+
+ * The internal stripspace() function has been moved to where it
+   logically belongs to, i.e. strbuf API, and the command line parser
+   of "git stripspace" has been updated to use the parse_options API.
+   (merge bed4452 tk/stripspace later to maint).
+
+ * "git am" used to spawn "git mailinfo" via run_command() API once
+   per each patch, but learned to make a direct call to mailinfo()
+   instead.
+
+ * The implementation of "git mailinfo" was refactored so that a
+   mailinfo() function can be directly called from inside a process.
+
+ * With a "debug" helper, debugging of a single "git" invocation in
+   our test scripts has become a lot easier.
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.6
+----------------
+
+Unless otherwise noted, all the fixes since v2.6 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * Very small number of options take a parameter that is optional
+   (which is not a great UI element as they can only appear at the end
+   of the command line).  Add notice to documentation of each and
+   every one of them.
+   (merge 2b594bf mm/keyid-docs later to maint).
+
+ * "git blame --first-parent v1.0..v2.0" was not rejected but did not
+   limit the blame to commits on the first parent chain.
+   (merge 95a4fb0 jk/blame-first-parent later to maint).
+
+ * "git subtree" (in contrib/) now can take whitespaces in the
+   pathnames, not only in the in-tree pathname but the name of the
+   directory that the repository is in.  (merge 5b6ab38
+   as/subtree-with-spaces later to maint).
+
+ * The ssh transport, just like any other transport over the network,
+   did not clear GIT_* environment variables, but it is possible to
+   use SendEnv and AcceptEnv to leak them to the remote invocation of
+   Git, which is not a good idea at all.  Explicitly clear them just
+   like we do for the local transport.
+   (merge a48b409 jk/connect-clear-env later to maint).
+
+ * Correct "git p4 --detect-labels" so that it does not fail to create
+   a tag that points at a commit that is also being imported.
+   (merge b43702a ld/p4-import-labels later to maint).
+
+ * The Makefile always runs the library archiver with hardcoded "crs"
+   options, which was inconvenient for exotic platforms on which
+   people want to use programs with totally different set of command
+   line options.
+   (merge ac179b4 jw/make-arflags-customizable later to maint).
+
+ * Customization to change the behaviour with "make -w" and "make -s"
+   in our Makefile was broken when they were used together.
+   (merge ef49e05 jk/make-findstring-makeflags-fix later to maint).
+
+ * Allocation related functions and stdio are unsafe things to call
+   inside a signal handler, and indeed killing the pager can cause
+   glibc to deadlock waiting on allocation mutex as our signal handler
+   tries to free() some data structures in wait_for_pager().  Reduce
+   these unsafe calls.
+   (merge 507d780 ti/glibc-stdio-mutex-from-signal-handler later to maint).
+
+ * The way how --ref/--notes to specify the notes tree reference are
+   DWIMmed was not clearly documented.
+   (merge e14c92e jk/notes-dwim-doc later to maint).
+
+ * "git gc" used to barf when a symbolic ref has gone dangling
+   (e.g. the branch that used to be your upstream's default when you
+   cloned from it is now gone, and you did "fetch --prune").
+   (merge 14886b4 js/gc-with-stale-symref later to maint).
+
+ * "git clone --dissociate" runs a big "git repack" process at the
+   end, and it helps to close file descriptors that are open on the
+   packs and their idx files before doing so on filesystems that
+   cannot remove a file that is still open.
+   (merge 786b150 js/clone-dissociate later to maint).
+
+ * Description of the "log.follow" configuration variable in "git log"
+   documentation is now also copied to "git config" documentation.
+   (merge fd8d07e dt/log-follow-config later to maint).
+
+ * "git rebase -i" had a minor regression recently, which stopped
+   considering a line that begins with an indented '#' in its insn
+   sheet not a comment. Further, the code was still too picky on
+   Windows where CRLF left by the editor is turned into a trailing CR
+   on the line read via the "read" built-in command of bash.  Both of
+   these issues are now fixed.
+   (merge 39743cf gr/rebase-i-drop-warn later to maint).
+
+ * After "git checkout --detach", "git status" reported a fairly
+   useless "HEAD detached at HEAD", instead of saying at which exact
+   commit.
+   (merge 0eb8548 mm/detach-at-HEAD-reflog later to maint).
+
+ * When "git send-email" wanted to talk over Net::SMTP::SSL,
+   Net::Cmd::datasend() did not like to be fed too many bytes at the
+   same time and failed to send messages.  Send the payload one line
+   at a time to work around the problem.
+   (merge f60c483 sa/send-email-smtp-batch-data-limit later to maint).
+
+ * When "git am" was rewritten as a built-in, it stopped paying
+   attention to user.signingkey, which was fixed.
+   (merge 434c64d pt/am-builtin later to maint).
+
+ * It was not possible to use a repository-lookalike created by "git
+   worktree add" as a local source of "git clone".
+   (merge d78db84 nd/clone-linked-checkout later to maint).
+
+ * On a case insensitive filesystems, setting GIT_WORK_TREE variable
+   using a random cases that does not agree with what the filesystem
+   thinks confused Git that it wasn't inside the working tree.
+   (merge 63ec5e1 js/icase-wt-detection later to maint).
+
+ * Performance-measurement tests did not work without an installed Git.
+   (merge 31cd128 sb/perf-without-installed-git later to maint).
+
+ * A test script for the HTTP service had a timing dependent bug,
+   which was fixed.
+   (merge 362d8b6 sb/http-flaky-test-fix later to maint).
+
+ * There were some classes of errors that "git fsck" diagnosed to its
+   standard error that did not cause it to exit with non-zero status.
+   (merge 122f76f jc/fsck-dropped-errors later to maint).
+
+ * Work around "git p4" failing when the P4 depot records the contents
+   in UTF-16 without UTF-16 BOM.
+   (merge 1f5f390 ls/p4-translation-failure later to maint).
+
+ * When "git gc --auto" is backgrounded, its diagnosis message is
+   lost.  Save it to a file in $GIT_DIR and show it next time the "gc
+   --auto" is run.
+   (merge 329e6e8 nd/gc-auto-background-fix later to maint).
+
+ * The submodule code has been taught to work better with separate
+   work trees created via "git worktree add".
+   (merge 11f9dd7 mk/submodule-gitdir-path later to maint).
+
+ * "git gc" is safe to run anytime only because it has the built-in
+   grace period to protect young objects.  In order to run with no
+   grace period, the user must make sure that the repository is
+   quiescent.
+   (merge fae1a90 jc/doc-gc-prune-now later to maint).
+
+ * A recent "filter-branch --msg-filter" broke skipping of the commit
+   object header, which is fixed.
+   (merge a5a4b3f jk/filter-branch-use-of-sed-on-incomplete-line later to maint).
+
+ * The normalize_ceiling_entry() function does not muck with the end
+   of the path it accepts, and the real world callers do rely on that,
+   but a test insisted that the function drops a trailing slash.
+   (merge b2a7123 rd/test-path-utils later to maint).
+
+ * A test for interaction between untracked cache and sparse checkout
+   added in Git 2.5 days were flaky.
+   (merge 9b680fb dt/t7063-fix-flaky-test later to maint).
+
+ * A couple of commands still showed "[options]" in their usage string
+   to note where options should come on their command line, but we
+   spell that "[<options>]" in most places these days.
+   (merge d96a031 rt/placeholder-in-usage later to maint).
+
+ * The synopsis text and the usage string of subcommands that read
+   list of things from the standard input are often shown as if they
+   only take input from a file on a filesystem, which was misleading.
+   (merge 33e8fc8 jc/usage-stdin later to maint).
+
+ * "git am -3" had a small regression where it is aborted in its error
+   handling codepath when underlying merge-recursive failed in certain
+   ways, as it assumed that the internal call to merge-recursive will
+   never die, which is not the case (yet).
+   (merge c63d4b2 jc/am-3-fallback-regression-fix later to maint).
+
+ * The linkage order of libraries was wrong in places around libcurl.
+   (merge 7e91e8d rp/link-curl-before-ssl later to maint).
+
+ * The name-hash subsystem that is used to cope with case insensitive
+   filesystems keeps track of directories and their on-filesystem
+   cases for all the paths in the index by holding a pointer to a
+   randomly chosen cache entry that is inside the directory (for its
+   ce->ce_name component).  This pointer was not updated even when the
+   cache entry was removed from the index, leading to use after free.
+   This was fixed by recording the path for each directory instead of
+   borrowing cache entries and restructuring the API somewhat.
+   (merge 41284eb dt/name-hash-dir-entry-fix later to maint).
+
+ * "git merge-file" tried to signal how many conflicts it found, which
+   obviously would not work well when there are too many of them.
+   (merge e34f802 jk/merge-file-exit-code later to maint).
+
+ * The error message from "git blame --contents --reverse" incorrectly
+   talked about "--contents --children".
+   (merge 9526197 mk/blame-error-message later to maint).
+
+ * "git imap-send" did not compile well with older version of cURL library.
+   (merge 71d9257 js/imap-send-curl-compilation-fix later to maint).
+
+ * Merging a branch that removes a path and another that changes the
+   mode bits on the same path should have conflicted at the path, but
+   it didn't and silently favoured the removal.
+   (merge 72fac66 jk/delete-modechange-conflict later to maint).
+
+ * "git --literal-pathspecs add -u/-A" without any command line
+   argument misbehaved ever since Git 2.0.
+   (merge 29abb33 jc/add-u-A-default-to-top later to maint).
+
+ * "git daemon" uses "run_command()" without "finish_command()", so it
+   needs to release resources itself, which it forgot to do.
+   (merge b1b49ff rs/daemon-plug-child-leak later to maint).
+
+ * "git status --branch --short" accessed beyond the constant string
+   "HEAD", which has been corrected.
+   (merge c72b49d rs/wt-status-detached-branch-fix later to maint).
+
+ * We peek objects from submodule's object store by linking it to the
+   list of alternate object databases, but the code to do so forgot to
+   correctly initialize the list.
+   (merge 9a6e4f0 jk/initialization-fix-to-add-submodule-odb later to maint).
+
+ * The code to prepare the working tree side of temporary directory
+   for the "dir-diff" feature forgot that symbolic links need not be
+   copied (or symlinked) to the temporary area, as the code already
+   special cases and overwrites them.  Besides, it was wrong to try
+   computing the object name of the target of symbolic link, which may
+   not even exist or may be a directory.
+   (merge cfe2d4b da/difftool later to maint).
+
+ * Code clean-up, minor fixes etc.
+   (merge 15ed07d jc/rerere later to maint).
+   (merge e7a7401 pt/pull-builtin later to maint).
+   (merge 29bc480 nd/ls-remote-does-not-have-u-option later to maint).
+   (merge be510e0 jk/asciidoctor-section-heading-markup-fix later to maint).
+   (merge 83e6bda tk/typofix-connect-unknown-proto-error later to maint).
+   (merge a43eb67 tk/doc-interpret-trailers-grammo later to maint).
+   (merge ba128e2 es/worktree-add-cleanup later to maint).
+   (merge 44cd91e cc/quote-comments later to maint).
+   (merge 147875f sb/submodule-config-parse later to maint).
+   (merge ae9f274 es/worktree-add later to maint).
+   (merge 3b19dba jc/em-dash-in-doc later to maint).
+   (merge f3f38c7 jc/everyday-markup later to maint).
+   (merge 77d5f71 xf/user-manual-markup later to maint).
+   (merge b2af482 xf/user-manual-ff later to maint).
+   (merge e510ab8 rs/pop-commit later to maint).
+   (merge fdcdb77 js/misc-fixes later to maint).
+   (merge c949b00 rs/show-branch-argv-array later to maint).
index a09969ba086609af5d35bbb3c5f30dfea5db00b6..760eab7428357ad4006437ae3032cee958dce803 100644 (file)
@@ -63,11 +63,10 @@ include::line-range-format.txt[]
        `-` to make the command read from the standard input).
 
 --date <format>::
-       The value is one of the following alternatives:
-       {relative,local,default,iso,rfc,short}. If --date is not
+       Specifies the format used to output dates. If --date is not
        provided, the value of the blame.date config variable is
        used. If the blame.date config variable is also not set, the
-       iso format is used. For more information, See the discussion
+       iso format is used. For supported values, see the discussion
        of the --date option at linkgit:git-log[1].
 
 -M|<num>|::
index fec0dea5d03343b40caf35ea6de6753049f08d35..391a0c3c857081e0509ea5a1b2ac085e79439d8f 100644 (file)
@@ -1829,9 +1829,7 @@ log.abbrevCommit::
 log.date::
        Set the default date-time mode for the 'log' command.
        Setting a value for log.date is similar to using 'git log''s
-       `--date` option.  Possible values are `relative`, `local`,
-       `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
-       for details.
+       `--date` option.  See linkgit:git-log[1] for details.
 
 log.decorate::
        Print out the ref names of any commits that are shown by the log
@@ -2593,6 +2591,16 @@ status.submoduleSummary::
        submodule summary' command, which shows a similar output but does
        not honor these settings.
 
+stash.showPatch::
+       If this is set to true, the `git stash show` command without an
+       option will show the stash in patch form.  Defaults to false.
+       See description of 'show' command in linkgit:git-stash[1].
+
+stash.showStat::
+       If this is set to true, the `git stash show` command without an
+       option will show diffstat of the stash.  Defaults to true.
+       See description of 'show' command in linkgit:git-stash[1].
+
 submodule.<name>.path::
 submodule.<name>.url::
        The path within this project and URL for a submodule. These
index 1b7a97b1dbd9a7c0efec1dd07e294badc4904c08..7e79aaedeb58632bca9edb6c1f7715c493139bb6 100644 (file)
@@ -16,9 +16,11 @@ DESCRIPTION
 The command takes various subcommands, and different options depending
 on the subcommand:
 
- git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
- git bisect bad [<rev>]
- git bisect good [<rev>...]
+ git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                 [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
+ git bisect (bad|new) [<rev>]
+ git bisect (good|old) [<rev>...]
+ git bisect terms [--term-good | --term-bad]
  git bisect skip [(<rev>|<range>)...]
  git bisect reset [<commit>]
  git bisect visualize
@@ -36,6 +38,13 @@ whether the selected commit is "good" or "bad". It continues narrowing
 down the range until it finds the exact commit that introduced the
 change.
 
+In fact, `git bisect` can be used to find the commit that changed
+*any* property of your project; e.g., the commit that fixed a bug, or
+the commit that caused a benchmark's performance to improve. To
+support this more general usage, the terms "old" and "new" can be used
+in place of "good" and "bad", or you can choose your own terms. See
+section "Alternate terms" below for more information.
+
 Basic bisect commands: start, bad, good
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -111,6 +120,79 @@ bad revision, while `git bisect reset HEAD` will leave you on the
 current bisection commit and avoid switching commits at all.
 
 
+Alternate terms
+~~~~~~~~~~~~~~~
+
+Sometimes you are not looking for the commit that introduced a
+breakage, but rather for a commit that caused a change between some
+other "old" state and "new" state. For example, you might be looking
+for the commit that introduced a particular fix. Or you might be
+looking for the first commit in which the source-code filenames were
+finally all converted to your company's naming standard. Or whatever.
+
+In such cases it can be very confusing to use the terms "good" and
+"bad" to refer to "the state before the change" and "the state after
+the change". So instead, you can use the terms "old" and "new",
+respectively, in place of "good" and "bad". (But note that you cannot
+mix "good" and "bad" with "old" and "new" in a single session.)
+
+In this more general usage, you provide `git bisect` with a "new"
+commit has some property and an "old" commit that doesn't have that
+property. Each time `git bisect` checks out a commit, you test if that
+commit has the property. If it does, mark the commit as "new";
+otherwise, mark it as "old". When the bisection is done, `git bisect`
+will report which commit introduced the property.
+
+To use "old" and "new" instead of "good" and bad, you must run `git
+bisect start` without commits as argument and then run the following
+commands to add the commits:
+
+------------------------------------------------
+git bisect old [<rev>]
+------------------------------------------------
+
+to indicate that a commit was before the sought change, or
+
+------------------------------------------------
+git bisect new [<rev>...]
+------------------------------------------------
+
+to indicate that it was after.
+
+To get a reminder of the currently used terms, use
+
+------------------------------------------------
+git bisect terms
+------------------------------------------------
+
+You can get just the old (respectively new) term with `git bisect term
+--term-old` or `git bisect term --term-good`.
+
+If you would like to use your own terms instead of "bad"/"good" or
+"new"/"old", you can choose any names you like (except existing bisect
+subcommands like `reset`, `start`, ...) by starting the
+bisection using
+
+------------------------------------------------
+git bisect start --term-old <term-old> --term-new <term-new>
+------------------------------------------------
+
+For example, if you are looking for a commit that introduced a
+performance regression, you might use
+
+------------------------------------------------
+git bisect start --term-old fast --term-new slow
+------------------------------------------------
+
+Or if you are looking for the commit that fixed a bug, you might use
+
+------------------------------------------------
+git bisect start --term-new fixed --term-old broken
+------------------------------------------------
+
+Then, use `git bisect <term-old>` and `git bisect <term-new>` instead
+of `git bisect good` and `git bisect bad` to mark commits.
+
 Bisect visualize
 ~~~~~~~~~~~~~~~~
 
@@ -387,6 +469,21 @@ In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit
 has at least one parent whose reachable graph is fully traversable in the sense
 required by 'git pack objects'.
 
+* Look for a fix instead of a regression in the code
++
+------------
+$ git bisect start
+$ git bisect new HEAD    # current commit is marked as new
+$ git bisect old HEAD~10 # the tenth commit from now is marked as old
+------------
++
+or:
+------------
+$ git bisect start --term-old broken --term-new fixed
+$ git bisect fixed
+$ git bisect broken HEAD~10
+------------
+
 Getting help
 ~~~~~~~~~~~~
 
index bbbade4f5112645c13bde24b4dcbb929a4f8c4b3..4a7037f1c8eed24db05f00c4012aca420f98b73b 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
        [--column[=<options>] | --no-column]
-       [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
+       [(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>]
+       [--points-at <object>] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
@@ -231,6 +232,19 @@ start-point is either a local or remote-tracking branch.
        The new name for an existing branch. The same restrictions as for
        <branchname> apply.
 
+--sort=<key>::
+       Sort based on the key given. Prefix `-` to sort in descending
+       order of the value. You may use the --sort=<key> option
+       multiple times, in which case the last key becomes the primary
+       key. The keys supported are the same as those in `git
+       for-each-ref`. Sort order defaults to sorting based on the
+       full refname (including `refs/...` prefix). This lists
+       detached HEAD (if present) first, then local branches and
+       finally remote-tracking branches.
+
+
+--points-at <object>::
+       Only list branches of the given object.
 
 Examples
 --------
index f1f2a3f7ea92282756a0b2b72271469561b442df..6bf000dac348880cc2f5b8d3cb6825e7a1ca82b4 100644 (file)
@@ -104,8 +104,13 @@ objects from the source repository into a pack in the cloned repository.
 --dissociate::
        Borrow the objects from reference repositories specified
        with the `--reference` options only to reduce network
-       transfer and stop borrowing from them after a clone is made
-       by making necessary local copies of borrowed objects.
+       transfer, and stop borrowing from them after a clone is made
+       by making necessary local copies of borrowed objects.  This
+       option can also be used when cloning locally from a
+       repository that already borrows objects from another
+       repository--the new repository will borrow objects from the
+       same repository, and this option can be used to stop the
+       borrowing.
 
 --quiet::
 -q::
index 7f8d9a5b5f358baa50bdfe205f555ea904f9f629..c6f073cea42a2a91fba1aeb17505fe6cd7f46ac8 100644 (file)
@@ -10,6 +10,8 @@ SYNOPSIS
 [verse]
 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
                   [(--sort=<key>)...] [--format=<format>] [<pattern>...]
+                  [--points-at <object>] [(--merged | --no-merged) [<object>]]
+                  [--contains [<object>]]
 
 DESCRIPTION
 -----------
@@ -62,6 +64,20 @@ OPTIONS
        the specified host language.  This is meant to produce
        a scriptlet that can directly be `eval`ed.
 
+--points-at <object>::
+       Only list refs which points at the given object.
+
+--merged [<object>]::
+       Only list refs whose tips are reachable from the
+       specified commit (HEAD if not specified).
+
+--no-merged [<object>]::
+       Only list refs whose tips are not reachable from the
+       specified commit (HEAD if not specified).
+
+--contains [<object>]::
+       Only list tags which contain the specified commit (HEAD if not
+       specified).
 
 FIELD NAMES
 -----------
@@ -111,6 +127,17 @@ color::
        Change output color.  Followed by `:<colorname>`, where names
        are described in `color.branch.*`.
 
+align::
+       Left-, middle-, or right-align the content between
+       %(align:...) and %(end). The "align:" is followed by `<width>`
+       and `<position>` in any order separated by a comma, where the
+       `<position>` is either left, right or middle, default being
+       left and `<width>` is the total length of the content with
+       alignment. If the contents length is more than the width then
+       no alignment is performed. If used with '--quote' everything
+       in between %(align:...) and %(end) is quoted, but if nested
+       then only the topmost level performs quoting.
+
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
@@ -123,20 +150,23 @@ The complete message in a commit and tag object is `contents`.
 Its first line is `contents:subject`, where subject is the concatenation
 of all lines of the commit message up to the first blank line.  The next
 line is 'contents:body', where body is all of the lines after the first
-blank line.  Finally, the optional GPG signature is `contents:signature`.
+blank line.  The optional GPG signature is `contents:signature`.  The
+first `N` lines of the message is obtained using `contents:lines=N`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
 All other fields are used to sort in their byte-value order.
 
+There is also an option to sort by versions, this can be done by using
+the fieldname `version:refname` or its alias `v:refname`.
+
 In any case, a field name that refers to a field inapplicable to
 the object referred by the ref does not cause an error.  It
 returns an empty string instead.
 
 As a special case for the date-type fields, you may specify a format for
-the date by adding one of `:default`, `:relative`, `:short`, `:local`,
-`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g.
-`%(taggerdate:relative)`.
+the date by adding `:` followed by date format name (see the
+values the `--date` option to linkgit::git-rev-list[1] takes).
 
 
 EXAMPLES
index 82aa5d60736ccc75510c19ddec2da9eb4c5611a8..c3ff7d0d9b1a62709128c985bcc7e8943756949a 100644 (file)
@@ -510,6 +510,45 @@ git-p4.useClientSpec::
        option '--use-client-spec'.  See the "CLIENT SPEC" section above.
        This variable is a boolean, not the name of a p4 client.
 
+git-p4.pathEncoding::
+       Perforce keeps the encoding of a path as given by the originating OS.
+       Git expects paths encoded as UTF-8. Use this config to tell git-p4
+       what encoding Perforce had used for the paths. This encoding is used
+       to transcode the paths to UTF-8. As an example, Perforce on Windows
+       often uses “cp1252” to encode path names.
+
+git-p4.largeFileSystem::
+       Specify the system that is used for large (binary) files. Please note
+       that large file systems do not support the 'git p4 submit' command.
+       Only Git LFS [1] is implemented right now. Download
+       and install the Git LFS command line extension to use this option
+       and configure it like this:
++
+-------------
+git config       git-p4.largeFileSystem GitLFS
+-------------
++
+       [1] https://git-lfs.github.com/
+
+git-p4.largeFileExtensions::
+       All files matching a file extension in the list will be processed
+       by the large file system. Do not prefix the extensions with '.'.
+
+git-p4.largeFileThreshold::
+       All files with an uncompressed size exceeding the threshold will be
+       processed by the large file system. By default the threshold is
+       defined in bytes. Add the suffix k, m, or g to change the unit.
+
+git-p4.largeFileCompressedThreshold::
+       All files with a compressed size exceeding the threshold will be
+       processed by the large file system. This option might slow down
+       your clone/sync process. By default the threshold is defined in
+       bytes. Add the suffix k, m, or g to change the unit.
+
+git-p4.largeFilePush::
+       Boolean variable which defines if large files are automatically
+       pushed to a server.
+
 Submit variables
 ~~~~~~~~~~~~~~~~
 git-p4.detectRenames::
index d64388cb8e454be17e4c20caf95897a71619c11f..ff633b0db7d54d8db12c4af248fc2bd3939b6f9c 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>]
+               [--series <file>]
 
 
 DESCRIPTION
@@ -42,13 +43,19 @@ OPTIONS
        information can be found in the patch description.
 
 --patches <dir>::
-       The directory to find the quilt patches and the
-       quilt series file.
+       The directory to find the quilt patches.
 +
 The default for the patch directory is patches
 or the value of the $QUILT_PATCHES environment
 variable.
 
+--series <file>::
+       The quilt series file.
++
+The default for the series file is <patches>/series
+or the value of the $QUILT_SERIES environment
+variable.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index bccfdf7fde9af8c80203f85d98030f8f38db4994..6cca8bb51dcabd47474ad63d908d7ec5d06d8703 100644 (file)
@@ -434,7 +434,8 @@ If the '--autosquash' option is enabled by default using the
 configuration variable `rebase.autoSquash`, this option can be
 used to override and disable this setting.
 
---[no-]autostash::
+--autostash::
+--no-autostash::
        Automatically create a temporary stash before the operation
        begins, and apply it after the operation ends.  This means
        that you can run rebase on a dirty worktree.  However, use
index 8bd22aff8762f6ff480fe2007b65393eb12593cd..1d7eceaa9355bd4651bda867c998fe66ae069d1a 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
 'git remote remove' <name>
 'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>)
 'git remote set-branches' [--add] <name> <branch>...
+'git remote get-url' [--push] [--all] <name>
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
@@ -131,6 +132,15 @@ The named branches will be interpreted as if specified with the
 With `--add`, instead of replacing the list of currently tracked
 branches, adds to that list.
 
+'get-url'::
+
+Retrieves the URLs for a remote. Configurations for `insteadOf` and
+`pushInsteadOf` are expanded here. By default, only the first URL is listed.
++
+With '--push', push URLs are queried rather than fetch URLs.
++
+With '--all', all URLs for the remote will be listed.
+
 'set-url'::
 
 Changes URLs for the remote. Sets first URL for remote <name> that matches
index 7b49c85347ec583c4d9849a3f0bc878d776e9519..ef22f1775b634812a6d0595ca3d88ce0b0c51506 100644 (file)
@@ -45,7 +45,7 @@ SYNOPSIS
             [ --regexp-ignore-case | -i ]
             [ --extended-regexp | -E ]
             [ --fixed-strings | -F ]
-            [ --date=(local|relative|default|iso|iso-strict|rfc|short) ]
+            [ --date=<format>]
             [ [ --objects | --objects-edge | --objects-edge-aggressive ]
               [ --unpacked ] ]
             [ --pretty | --header ]
index 375213fe463fa3cd9cb7644cff60ee656c13dcd8..92df596e5fe9757fee399078e05e3ba44196fda3 100644 (file)
@@ -95,6 +95,8 @@ show [<stash>]::
        shows the latest one. By default, the command shows the diffstat, but
        it will accept any format known to 'git diff' (e.g., `git stash show
        -p stash@{1}` to view the second most recent stash in patch form).
+       You can use stash.showStat and/or stash.showPatch config variables
+       to change the default behavior.
 
 pop [--index] [-q|--quiet] [<stash>]::
 
index 08b4dfbf1b049a3fc3cd04edc573845e0c976a9c..7220e5eca1bddfcaf9828522c7f04ab78418874e 100644 (file)
@@ -13,7 +13,8 @@ SYNOPSIS
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
-       [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
+       [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
+       [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -94,14 +95,16 @@ OPTIONS
        using fnmatch(3)).  Multiple patterns may be given; if any of
        them matches, the tag is shown.
 
---sort=<type>::
-       Sort in a specific order. Supported type is "refname"
-       (lexicographic order), "version:refname" or "v:refname" (tag
+--sort=<key>::
+       Sort based on the key given.  Prefix `-` to sort in
+       descending order of the value. You may use the --sort=<key> option
+       multiple times, in which case the last key becomes the primary
+       key. Also supports "version:refname" or "v:refname" (tag
        names are treated as versions). The "version:refname" sort
        order can also be affected by the
-       "versionsort.prereleaseSuffix" configuration variable. Prepend
-       "-" to reverse sort order. When this option is not given, the
-       sort order defaults to the value configured for the 'tag.sort'
+       "versionsort.prereleaseSuffix" configuration variable.
+       The keys supported are the same as those in `git for-each-ref`.
+       Sort order defaults to the value configured for the 'tag.sort'
        variable if it exists, or lexicographic order otherwise. See
        linkgit:git-config[1].
 
@@ -156,6 +159,16 @@ This option is only applicable when listing tags without annotation lines.
        The object that the new tag will refer to, usually a commit.
        Defaults to HEAD.
 
+<format>::
+       A string that interpolates `%(fieldname)` from the object
+       pointed at by a ref being shown.  The format is the same as
+       that of linkgit:git-for-each-ref[1].  When unspecified,
+       defaults to `%(refname:short)`.
+
+--[no-]merged [<commit>]::
+       Only list tags whose tips are reachable, or not reachable
+       if '--no-merged' is used, from the specified commit ('HEAD'
+       if not specified).
 
 CONFIGURATION
 -------------
index fb68156cf8ad0695eb6e5a7548c9a2e56be5c3f2..5b9ad0429c84d2f11b0569f6d979858c833b3768 100644 (file)
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree list' [--porcelain]
 
 DESCRIPTION
 -----------
@@ -59,6 +60,13 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+list::
+
+List details of each worktree.  The main worktree is listed first, followed by
+each of the linked worktrees.  The output details include if the worktree is
+bare, the revision currently checked out, and the branch currently checked out
+(or 'detached HEAD' if none).
+
 OPTIONS
 -------
 
@@ -86,6 +94,11 @@ OPTIONS
        With `prune`, do not remove anything; just report what it would
        remove.
 
+--porcelain::
+       With `list`, output in an easy-to-parse format for scripts.
+       This format will remain stable across Git versions and regardless of user
+       configuration.  See below for details.
+
 -v::
 --verbose::
        With `prune`, report all removals.
@@ -134,6 +147,41 @@ to `/path/main/.git/worktrees/test-next` then a file named
 `test-next` entry from being pruned.  See
 linkgit:gitrepository-layout[5] for details.
 
+LIST OUTPUT FORMAT
+------------------
+The worktree list command has two output formats.  The default format shows the
+details on a single line with columns.  For example:
+
+------------
+S git worktree list
+/path/to/bare-source            (bare)
+/path/to/linked-worktree        abcd1234 [master]
+/path/to/other-linked-worktree  1234abc  (detached HEAD)
+------------
+
+Porcelain Format
+~~~~~~~~~~~~~~~~
+The porcelain format has a line per attribute.  Attributes are listed with a
+label and value separated by a single space.  Boolean attributes (like 'bare'
+and 'detached') are listed as a label only, and are only present if and only
+if the value is true.  An empty line indicates the end of a worktree.  For
+example:
+
+------------
+S git worktree list --porcelain
+worktree /path/to/bare-source
+bare
+
+worktree /path/to/linked-worktree
+HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
+branch refs/heads/master
+
+worktree /path/to/other-linked-worktree
+HEAD 1234abc1234abc1234abc1234abc1234abc1234a
+detached
+
+------------
+
 EXAMPLES
 --------
 You are in the middle of a refactoring session and your boss comes in and
@@ -167,7 +215,6 @@ performed manually, such as:
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
 - `mv` to move or rename a working tree and update its administrative files
-- `list` to list linked working trees
 - `lock` to prevent automatic pruning of administrative files (for instance,
   for a working tree on a portable device)
 
index 473623d6318a859c9ed2cf600222ea6cb4a25d4c..79a1948a0bd52c778dcfccb253f9f2508b99f27e 100644 (file)
@@ -82,12 +82,12 @@ PATTERN FORMAT
 
  - An optional prefix "`!`" which negates the pattern; any
    matching file excluded by a previous pattern will become
-   included again. It is not possible to re-include a file if a parent
-   directory of that file is excluded. Git doesn't list excluded
-   directories for performance reasons, so any patterns on contained
-   files have no effect, no matter where they are defined.
+   included again.
    Put a backslash ("`\`") in front of the first "`!`" for patterns
    that begin with a literal "`!`", for example, "`\!important!.txt`".
+   It is possible to re-include a file if a parent directory of that
+   file is excluded if certain conditions are met. See section NOTES
+   for detail.
 
  - If the pattern ends with a slash, it is removed for the
    purpose of the following description, but it would only find
@@ -141,6 +141,21 @@ not tracked by Git remain untracked.
 To stop tracking a file that is currently tracked, use
 'git rm --cached'.
 
+To re-include files or directories when their parent directory is
+excluded, the following conditions must be met:
+
+ - The rules to exclude a directory and re-include a subset back must
+   be in the same .gitignore file.
+
+ - The directory part in the re-include rules must be literal (i.e. no
+   wildcards)
+
+ - The rules to exclude the parent directory must not end with a
+   trailing slash.
+
+ - The rules to exclude the parent directory must have at least one
+   slash.
+
 EXAMPLES
 --------
 
index 8c6478b2f2cab195dcc07f47cfc59cbace286fda..e225974253833c97980e5ff190e291c8323757c9 100644 (file)
@@ -413,8 +413,9 @@ exclude;;
 
 [[def_per_worktree_ref]]per-worktree ref::
        Refs that are per-<<def_working_tree,worktree>>, rather than
-       global.  This is presently only <<def_HEAD,HEAD>>, but might
-       later include other unusual refs.
+       global.  This is presently only <<def_HEAD,HEAD>> and any refs
+       that start with `refs/bisect/`, but might later include other
+       unusual refs.
 
 [[def_pseudoref]]pseudoref::
        Pseudorefs are a class of files under `$GIT_DIR` which behave
index f1c52208f08c3dc9f50e8d18f546a96276a47fec..4f009d44240e3725e25bc8a9a3acd999d69cc487 100644 (file)
@@ -701,15 +701,19 @@ include::pretty-options.txt[]
 --relative-date::
        Synonym for `--date=relative`.
 
---date=(relative|local|default|iso|iso-strict|rfc|short|raw)::
+--date=<format>::
        Only takes effect for dates shown in human-readable format, such
        as when using `--pretty`. `log.date` config variable sets a default
-       value for the log command's `--date` option.
+       value for the log command's `--date` option. By default, dates
+       are shown in the original time zone (either committer's or
+       author's). If `-local` is appended to the format (e.g.,
+       `iso-local`), the user's local time zone is used instead.
 +
 `--date=relative` shows dates relative to the current time,
-e.g. ``2 hours ago''.
+e.g. ``2 hours ago''. The `-local` option cannot be used with
+`--raw` or `--relative`.
 +
-`--date=local` shows timestamps in user's local time zone.
+`--date=local` is an alias for `--date=default-local`.
 +
 `--date=iso` (or `--date=iso8601`) shows timestamps in a ISO 8601-like format.
 The differences to the strict ISO 8601 format are:
@@ -732,10 +736,15 @@ format, often found in email messages.
 `--date=format:...` feeds the format `...` to your system `strftime`.
 Use `--date=format:%c` to show the date in your system locale's
 preferred format.  See the `strftime` manual for a complete list of
-format placeholders.
+format placeholders. When using `-local`, the correct syntax is
+`--date=format-local:...`.
 +
-`--date=default` shows timestamps in the original time zone
-(either committer's or author's).
+`--date=default` is the default format, and is similar to
+`--date=rfc2822`, with a few exceptions:
+
+       - there is no comma after the day-of-week
+
+       - the time zone is omitted when the local time zone is used
 
 ifdef::git-rev-list[]
 --header::
index a9fdb45b93e514962716401cb22d9d079b107e40..8bf3e37f5375584e5c9f4543523a40377e08686f 100644 (file)
@@ -46,6 +46,13 @@ Functions
        The argument dir corresponds the member .dir. The argument env
        corresponds to the member .env.
 
+`child_process_clear`::
+
+       Release the memory associated with the struct child_process.
+       Most users of the run-command API don't need to call this
+       function explicitly because `start_command` invokes it on
+       failure and `finish_command` calls it automatically already.
+
 The functions above do the following:
 
 . If a system call failed, errno is set and -1 is returned. A diagnostic
index 787670926a1283864ccd4a04379a939ff36e51aa..94e40c400078794f48d36236bd105a2d3be59d7c 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.6.2
+DEF_VER=v2.6.0.GIT
 
 LF='
 '
index 04c223102346c209bad78550723027543cffe7d1..43ceeb966c51e4e0b684ac64c2c5cbf0aa943cbf 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,9 @@ all::
 # Define CURLDIR=/foo/bar if your curl header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
+# Define CURL_CONFIG to curl's configuration program that prints information
+# about the library (e.g., its version number).  The default is 'curl-config'.
+#
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
 # not built, and you cannot push using http:// and https:// transports (dumb).
 #
@@ -74,8 +77,6 @@ all::
 # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
 # it specifies.
 #
-# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
-#
 # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
 # d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7).
 #
@@ -428,6 +429,7 @@ TCL_PATH = tclsh
 TCLTK_PATH = wish
 XGETTEXT = xgettext
 MSGFMT = msgfmt
+CURL_CONFIG = curl-config
 PTHREAD_LIBS = -lpthread
 PTHREAD_CFLAGS =
 GCOV = gcov
@@ -729,6 +731,7 @@ LIB_OBJS += list-objects.o
 LIB_OBJS += ll-merge.o
 LIB_OBJS += lockfile.o
 LIB_OBJS += log-tree.o
+LIB_OBJS += mailinfo.o
 LIB_OBJS += mailmap.o
 LIB_OBJS += match-trees.o
 LIB_OBJS += merge.o
@@ -810,6 +813,7 @@ LIB_OBJS += version.o
 LIB_OBJS += versioncmp.o
 LIB_OBJS += walker.o
 LIB_OBJS += wildmatch.o
+LIB_OBJS += worktree.o
 LIB_OBJS += wrapper.o
 LIB_OBJS += write_or_die.o
 LIB_OBJS += ws.o
@@ -905,6 +909,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-ref.o
 BUILTIN_OBJS += builtin/stripspace.o
+BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
 BUILTIN_OBJS += builtin/tag.o
 BUILTIN_OBJS += builtin/unpack-file.o
@@ -1036,7 +1041,7 @@ ifdef HAVE_ALLOCA_H
 endif
 
 IMAP_SEND_BUILDDEPS =
-IMAP_SEND_LDFLAGS = $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
+IMAP_SEND_LDFLAGS =
 
 ifdef NO_CURL
        BASIC_CFLAGS += -DNO_CURL
@@ -1066,13 +1071,13 @@ else
        REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
        PROGRAM_OBJS += http-fetch.o
        PROGRAMS += $(REMOTE_CURL_NAMES)
-       curl_check := $(shell (echo 070908; curl-config --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
+       curl_check := $(shell (echo 070908; $(CURL_CONFIG) --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "070908"
                ifndef NO_EXPAT
                        PROGRAM_OBJS += http-push.o
                endif
        endif
-       curl_check := $(shell (echo 072200; curl-config --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
+       curl_check := $(shell (echo 072200; $(CURL_CONFIG) --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "072200"
                USE_CURL_FOR_IMAP_SEND = YesPlease
        endif
@@ -1093,6 +1098,7 @@ else
                endif
        endif
 endif
+IMAP_SEND_LDFLAGS += $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
 
 ifdef ZLIB_PATH
        BASIC_CFLAGS += -I$(ZLIB_PATH)/include
@@ -1163,9 +1169,6 @@ endif
 ifdef NO_D_TYPE_IN_DIRENT
        BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
 endif
-ifdef NO_D_INO_IN_DIRENT
-       BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
-endif
 ifdef NO_GECOS_IN_PWENT
        BASIC_CFLAGS += -DNO_GECOS_IN_PWENT
 endif
@@ -1978,10 +1981,10 @@ git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
 
 git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(CURL_LIBCURL)
+               $(CURL_LIBCURL) $(LIBS)
 git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+               $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
 
 git-remote-testsvn$X: remote-testsvn.o GIT-LDFLAGS $(GITLIBS) $(VCSSVN_LIB)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) \
@@ -1995,7 +1998,7 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)
 
 $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+               $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
 
 $(LIB_FILE): $(LIB_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
index 022358074dff1b5fee7d1d9c354b64a048041226..3ba13ce25351c60c93d0c8ed78f207d9a591d517 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.6.2.txt
\ No newline at end of file
+Documentation/RelNotes/2.7.0.txt
\ No newline at end of file
index 0d1e6bd7542dd7c76d2f349de0d0238a8d1b55af..501ca97760b1f62ada876d372bd1abbbc41c6cd0 100644 (file)
@@ -167,21 +167,21 @@ static void prepare_header(struct archiver_args *args,
                           struct ustar_header *header,
                           unsigned int mode, unsigned long size)
 {
-       sprintf(header->mode, "%07o", mode & 07777);
-       sprintf(header->size, "%011lo", S_ISREG(mode) ? size : 0);
-       sprintf(header->mtime, "%011lo", (unsigned long) args->time);
+       xsnprintf(header->mode, sizeof(header->mode), "%07o", mode & 07777);
+       xsnprintf(header->size, sizeof(header->size), "%011lo", S_ISREG(mode) ? size : 0);
+       xsnprintf(header->mtime, sizeof(header->mtime), "%011lo", (unsigned long) args->time);
 
-       sprintf(header->uid, "%07o", 0);
-       sprintf(header->gid, "%07o", 0);
+       xsnprintf(header->uid, sizeof(header->uid), "%07o", 0);
+       xsnprintf(header->gid, sizeof(header->gid), "%07o", 0);
        strlcpy(header->uname, "root", sizeof(header->uname));
        strlcpy(header->gname, "root", sizeof(header->gname));
-       sprintf(header->devmajor, "%07o", 0);
-       sprintf(header->devminor, "%07o", 0);
+       xsnprintf(header->devmajor, sizeof(header->devmajor), "%07o", 0);
+       xsnprintf(header->devminor, sizeof(header->devminor), "%07o", 0);
 
        memcpy(header->magic, "ustar", 6);
        memcpy(header->version, "00", 2);
 
-       sprintf(header->chksum, "%07o", ustar_header_chksum(header));
+       snprintf(header->chksum, sizeof(header->chksum), "%07o", ustar_header_chksum(header));
 }
 
 static int write_extended_header(struct archiver_args *args,
@@ -193,7 +193,7 @@ static int write_extended_header(struct archiver_args *args,
        memset(&header, 0, sizeof(header));
        *header.typeflag = TYPEFLAG_EXT_HEADER;
        mode = 0100666;
-       sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+       xsnprintf(header.name, sizeof(header.name), "%s.paxheader", sha1_to_hex(sha1));
        prepare_header(args, &header, mode, size);
        write_blocked(&header, sizeof(header));
        write_blocked(buffer, size);
@@ -233,10 +233,10 @@ static int write_tar_entry(struct archiver_args *args,
                size_t rest = pathlen - plen - 1;
                if (plen > 0 && rest <= sizeof(header.name)) {
                        memcpy(header.prefix, path, plen);
-                               memcpy(header.name, path + plen + 1, rest);
+                       memcpy(header.name, path + plen + 1, rest);
                } else {
-                       sprintf(header.name, "%s.data",
-                               sha1_to_hex(sha1));
+                       xsnprintf(header.name, sizeof(header.name), "%s.data",
+                                 sha1_to_hex(sha1));
                        strbuf_append_ext_header(&ext_header, "path",
                                                 path, pathlen);
                }
@@ -259,8 +259,8 @@ static int write_tar_entry(struct archiver_args *args,
 
        if (S_ISLNK(mode)) {
                if (size > sizeof(header.linkname)) {
-                       sprintf(header.linkname, "see %s.paxheader",
-                               sha1_to_hex(sha1));
+                       xsnprintf(header.linkname, sizeof(header.linkname),
+                                 "see %s.paxheader", sha1_to_hex(sha1));
                        strbuf_append_ext_header(&ext_header, "linkpath",
                                                 buffer, size);
                } else
@@ -301,7 +301,7 @@ static int write_global_extended_header(struct archiver_args *args)
        memset(&header, 0, sizeof(header));
        *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
        mode = 0100666;
-       strcpy(header.name, "pax_global_header");
+       xsnprintf(header.name, sizeof(header.name), "pax_global_header");
        prepare_header(args, &header, mode, ext_header.len);
        write_blocked(&header, sizeof(header));
        write_blocked(ext_header.buf, ext_header.len);
index 01b0899b3f9027c5a392bd58667ed0e216d082ed..4ac86c837384c45c33dae141c3ff53dd37eb0fd5 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -171,13 +171,14 @@ static void queue_directory(const unsigned char *sha1,
                unsigned mode, int stage, struct archiver_context *c)
 {
        struct directory *d;
-       d = xmallocz(sizeof(*d) + base->len + 1 + strlen(filename));
+       size_t len = base->len + 1 + strlen(filename) + 1;
+       d = xmalloc(sizeof(*d) + len);
        d->up      = c->bottom;
        d->baselen = base->len;
        d->mode    = mode;
        d->stage   = stage;
        c->bottom  = d;
-       d->len = sprintf(d->path, "%.*s%s/", (int)base->len, base->buf, filename);
+       d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
        hashcpy(d->oid.hash, sha1);
 }
 
index 041a13d093a21597c60d799c0f6943998f2d6a8a..053d1a2ab91a6c41a0fe957569ff905d12b00bee 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -730,6 +730,11 @@ static void handle_bad_merge_base(void)
                                "This means the bug has been fixed "
                                "between %s and [%s].\n",
                                bad_hex, bad_hex, good_hex);
+               } else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
+                       fprintf(stderr, "The merge base %s is new.\n"
+                               "The property has changed "
+                               "between %s and [%s].\n",
+                               bad_hex, bad_hex, good_hex);
                } else {
                        fprintf(stderr, "The merge base %s is %s.\n"
                                "This means the first '%s' commit is "
@@ -762,11 +767,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
 }
 
 /*
- * "check_merge_bases" checks that merge bases are not "bad".
+ * "check_merge_bases" checks that merge bases are not "bad" (or "new").
  *
- * - If one is "bad", it means the user assumed something wrong
+ * - If one is "bad" (or "new"), it means the user assumed something wrong
  * and we must exit with a non 0 error code.
- * - If one is "good", that's good, we have nothing to do.
+ * - If one is "good" (or "old"), that's good, we have nothing to do.
  * - If one is "skipped", we can't know but we should warn.
  * - If we don't know, we should check it out and ask the user to test.
  */
index d013374e5a0b5714836be77709b07a5e3f67ed2a..77d7f2a63ee38b81ffbbf4741cdf1ad205e0c054 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -4,6 +4,7 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
+#include "worktree.h"
 
 struct tracking {
        struct refspec spec;
@@ -311,84 +312,6 @@ void remove_branch_state(void)
        unlink(git_path_squash_msg());
 }
 
-static char *find_linked_symref(const char *symref, const char *branch,
-                               const char *id)
-{
-       struct strbuf sb = STRBUF_INIT;
-       struct strbuf path = STRBUF_INIT;
-       struct strbuf gitdir = STRBUF_INIT;
-       char *existing = NULL;
-
-       /*
-        * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside
-        * $GIT_DIR so resolve_ref_unsafe() won't work (it uses
-        * git_path). Parse the ref ourselves.
-        */
-       if (id)
-               strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref);
-       else
-               strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref);
-
-       if (!strbuf_readlink(&sb, path.buf, 0)) {
-               if (!starts_with(sb.buf, "refs/") ||
-                   check_refname_format(sb.buf, 0))
-                       goto done;
-       } else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
-           starts_with(sb.buf, "ref:")) {
-               strbuf_remove(&sb, 0, strlen("ref:"));
-               strbuf_trim(&sb);
-       } else
-               goto done;
-       if (strcmp(sb.buf, branch))
-               goto done;
-       if (id) {
-               strbuf_reset(&path);
-               strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
-               if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
-                       goto done;
-               strbuf_rtrim(&gitdir);
-       } else
-               strbuf_addstr(&gitdir, get_git_common_dir());
-       strbuf_strip_suffix(&gitdir, ".git");
-
-       existing = strbuf_detach(&gitdir, NULL);
-done:
-       strbuf_release(&path);
-       strbuf_release(&sb);
-       strbuf_release(&gitdir);
-
-       return existing;
-}
-
-char *find_shared_symref(const char *symref, const char *target)
-{
-       struct strbuf path = STRBUF_INIT;
-       DIR *dir;
-       struct dirent *d;
-       char *existing;
-
-       if ((existing = find_linked_symref(symref, target, NULL)))
-               return existing;
-
-       strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
-       dir = opendir(path.buf);
-       strbuf_release(&path);
-       if (!dir)
-               return NULL;
-
-       while ((d = readdir(dir)) != NULL) {
-               if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
-                       continue;
-               existing = find_linked_symref(symref, target, d->d_name);
-               if (existing)
-                       goto done;
-       }
-done:
-       closedir(dir);
-
-       return existing;
-}
-
 void die_if_checked_out(const char *branch)
 {
        char *existing;
index d3446ed73c0c209f0d6b3dc2a7412a643031ea08..58aa45fe72ca356a8bd3648782b36e63e207ee36 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -59,12 +59,4 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
  */
 extern void die_if_checked_out(const char *branch);
 
-/*
- * Check if a per-worktree symref points to a ref in the main worktree
- * or any linked worktree, and return the path to the exising worktree
- * if it is.  Returns NULL if there is no existing ref.  The caller is
- * responsible for freeing the returned path.
- */
-extern char *find_shared_symref(const char *symref, const char *target);
-
 #endif
index 79aaf0afe8912d154573df3baa6ea5c28f8097d6..6b95006a0a38d8bec522342fb72a5edeb0af7f07 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -120,6 +120,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
+extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
index b2a5c57f0afde85a7584e1edc7a45b67d12d143d..145f06ef97afd1e9e8d5cbe13da82cffa504e32c 100644 (file)
@@ -336,14 +336,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (!show_only && ignore_missing)
                die(_("Option --ignore-missing can only be used together with --dry-run"));
 
-       if ((0 < addremove_explicit || take_worktree_changes) && !argc) {
-               static const char *whole[2] = { ":/", NULL };
-               argc = 1;
-               argv = whole;
-       }
-
        add_new_files = !take_worktree_changes && !refresh_only;
-       require_pathspec = !take_worktree_changes;
+       require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
 
        hold_locked_index(&lock_file, 1);
 
index 4e396c832139aa3c4ae4e68e9d265b0107817aa9..f1a25ab6ad881fc903a533595792b1a46b40ea91 100644 (file)
@@ -27,6 +27,7 @@
 #include "notes-utils.h"
 #include "rerere.h"
 #include "prompt.h"
+#include "mailinfo.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -1258,58 +1259,61 @@ static void am_append_signoff(struct am_state *state)
 static int parse_mail(struct am_state *state, const char *mail)
 {
        FILE *fp;
-       struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf sb = STRBUF_INIT;
        struct strbuf msg = STRBUF_INIT;
        struct strbuf author_name = STRBUF_INIT;
        struct strbuf author_date = STRBUF_INIT;
        struct strbuf author_email = STRBUF_INIT;
        int ret = 0;
+       struct mailinfo mi;
 
-       cp.git_cmd = 1;
-       cp.in = xopen(mail, O_RDONLY, 0);
-       cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777);
+       setup_mailinfo(&mi);
 
-       argv_array_push(&cp.args, "mailinfo");
-       argv_array_push(&cp.args, state->utf8 ? "-u" : "-n");
+       if (state->utf8)
+               mi.metainfo_charset = get_commit_output_encoding();
+       else
+               mi.metainfo_charset = NULL;
 
        switch (state->keep) {
        case KEEP_FALSE:
                break;
        case KEEP_TRUE:
-               argv_array_push(&cp.args, "-k");
+               mi.keep_subject = 1;
                break;
        case KEEP_NON_PATCH:
-               argv_array_push(&cp.args, "-b");
+               mi.keep_non_patch_brackets_in_subject = 1;
                break;
        default:
                die("BUG: invalid value for state->keep");
        }
 
        if (state->message_id)
-               argv_array_push(&cp.args, "-m");
+               mi.add_message_id = 1;
 
        switch (state->scissors) {
        case SCISSORS_UNSET:
                break;
        case SCISSORS_FALSE:
-               argv_array_push(&cp.args, "--no-scissors");
+               mi.use_scissors = 0;
                break;
        case SCISSORS_TRUE:
-               argv_array_push(&cp.args, "--scissors");
+               mi.use_scissors = 1;
                break;
        default:
                die("BUG: invalid value for state->scissors");
        }
 
-       argv_array_push(&cp.args, am_path(state, "msg"));
-       argv_array_push(&cp.args, am_path(state, "patch"));
-
-       if (run_command(&cp) < 0)
+       mi.input = fopen(mail, "r");
+       if (!mi.input)
+               die("could not open input");
+       mi.output = fopen(am_path(state, "info"), "w");
+       if (!mi.output)
+               die("could not open output 'info'");
+       if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch")))
                die("could not parse patch");
 
-       close(cp.in);
-       close(cp.out);
+       fclose(mi.input);
+       fclose(mi.output);
 
        /* Extract message and author information */
        fp = xfopen(am_path(state, "info"), "r");
@@ -1341,8 +1345,7 @@ static int parse_mail(struct am_state *state, const char *mail)
        }
 
        strbuf_addstr(&msg, "\n\n");
-       if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0)
-               die_errno(_("could not read '%s'"), am_path(state, "msg"));
+       strbuf_addbuf(&msg, &mi.log_message);
        strbuf_stripspace(&msg, 0);
 
        if (state->signoff)
@@ -1366,6 +1369,7 @@ static int parse_mail(struct am_state *state, const char *mail)
        strbuf_release(&author_email);
        strbuf_release(&author_name);
        strbuf_release(&sb);
+       clear_mailinfo(&mi);
        return ret;
 }
 
index 4aa53f7fd87d5fd23c0929360b6746f48e8e3e73..deb1364fa81452d236a224109a99b28a034f9a9a 100644 (file)
@@ -77,8 +77,7 @@ static enum ws_ignore {
 
 
 static const char *patch_input_file;
-static const char *root;
-static int root_len;
+static struct strbuf root = STRBUF_INIT;
 static int read_stdin = 1;
 static int options;
 
@@ -494,8 +493,8 @@ static char *find_name_gnu(const char *line, const char *def, int p_value)
        }
 
        strbuf_remove(&name, 0, cp - name.buf);
-       if (root)
-               strbuf_insert(&name, 0, root, root_len);
+       if (root.len)
+               strbuf_insert(&name, 0, root.buf, root.len);
        return squash_slash(strbuf_detach(&name, NULL));
 }
 
@@ -697,11 +696,8 @@ static char *find_name_common(const char *line, const char *def,
                        return squash_slash(xstrdup(def));
        }
 
-       if (root) {
-               char *ret = xmalloc(root_len + len + 1);
-               strcpy(ret, root);
-               memcpy(ret + root_len, start, len);
-               ret[root_len + len] = '\0';
+       if (root.len) {
+               char *ret = xstrfmt("%s%.*s", root.buf, len, start);
                return squash_slash(ret);
        }
 
@@ -1277,8 +1273,8 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct
         * the default name from the header.
         */
        patch->def_name = git_header_name(line, len);
-       if (patch->def_name && root) {
-               char *s = xstrfmt("%s%s", root, patch->def_name);
+       if (patch->def_name && root.len) {
+               char *s = xstrfmt("%s%s", root.buf, patch->def_name);
                free(patch->def_name);
                patch->def_name = s;
        }
@@ -4501,14 +4497,9 @@ static int option_parse_whitespace(const struct option *opt,
 static int option_parse_directory(const struct option *opt,
                                  const char *arg, int unset)
 {
-       root_len = strlen(arg);
-       if (root_len && arg[root_len - 1] != '/') {
-               char *new_root;
-               root = new_root = xmalloc(root_len + 2);
-               strcpy(new_root, arg);
-               strcpy(new_root + root_len++, "/");
-       } else
-               root = arg;
+       strbuf_reset(&root);
+       strbuf_addstr(&root, arg);
+       strbuf_complete(&root, '/');
        return 0;
 }
 
index 6cac59c973bfcf1dc48e2a1af26375f914bb02c4..83612f5b646f1db7232cf69cf5a2ea6b30a6e833 100644 (file)
@@ -459,12 +459,13 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin,
 static struct origin *make_origin(struct commit *commit, const char *path)
 {
        struct origin *o;
-       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+       size_t pathlen = strlen(path) + 1;
+       o = xcalloc(1, sizeof(*o) + pathlen);
        o->commit = commit;
        o->refcnt = 1;
        o->next = commit->util;
        commit->util = o;
-       strcpy(o->path, path);
+       memcpy(o->path, path, pathlen); /* includes NUL */
        return o;
 }
 
@@ -1879,9 +1880,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
 
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.sha1);
        printf("%s %d %d %d\n",
               hex,
               ent->s_lno + 1,
@@ -1917,11 +1918,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
        const char *cp;
        struct origin *suspect = ent->suspect;
        struct commit_info ci;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
 
        get_commit_info(suspect->commit, &ci, 1);
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.sha1);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@ -2401,16 +2402,11 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        return commit;
 }
 
-static char *prepare_final(struct scoreboard *sb)
+static struct object_array_entry *find_single_final(struct rev_info *revs)
 {
        int i;
-       const char *final_commit_name = NULL;
-       struct rev_info *revs = sb->revs;
+       struct object_array_entry *found = NULL;
 
-       /*
-        * There must be one and only one positive commit in the
-        * revs->pending array.
-        */
        for (i = 0; i < revs->pending.nr; i++) {
                struct object *obj = revs->pending.objects[i].item;
                if (obj->flags & UNINTERESTING)
@@ -2419,14 +2415,24 @@ static char *prepare_final(struct scoreboard *sb)
                        obj = deref_tag(obj, NULL, 0);
                if (obj->type != OBJ_COMMIT)
                        die("Non commit %s?", revs->pending.objects[i].name);
-               if (sb->final)
+               if (found)
                        die("More than one commit to dig from %s and %s?",
                            revs->pending.objects[i].name,
-                           final_commit_name);
-               sb->final = (struct commit *) obj;
-               final_commit_name = revs->pending.objects[i].name;
+                           found->name);
+               found = &(revs->pending.objects[i]);
+       }
+       return found;
+}
+
+static char *prepare_final(struct scoreboard *sb)
+{
+       struct object_array_entry *found = find_single_final(sb->revs);
+       if (found) {
+               sb->final = (struct commit *) found->item;
+               return xstrdup(found->name);
+       } else {
+               return NULL;
        }
-       return xstrdup_or_null(final_commit_name);
 }
 
 static char *prepare_initial(struct scoreboard *sb)
@@ -2502,6 +2508,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        long dashdash_pos, lno;
        char *final_commit_name = NULL;
        enum object_type type;
+       struct commit *final_commit = NULL;
 
        static struct string_list range_list;
        static int output_option = 0, opt = 0;
@@ -2612,7 +2619,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                   fewer display columns. */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
-       case DATE_LOCAL:
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
@@ -2692,11 +2698,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
        else if (contents_from)
                die("--contents and --reverse do not blend well.");
-       else if (revs.first_parent_only)
-               die("combining --first-parent and --reverse is not supported");
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
+               if (revs.first_parent_only)
+                       revs.children.name = NULL;
        }
 
        if (!sb.final) {
@@ -2713,6 +2719,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        else if (contents_from)
                die("Cannot use --contents with final commit object name");
 
+       if (reverse && revs.first_parent_only) {
+               struct object_array_entry *entry = find_single_final(sb.revs);
+               if (!entry)
+                       die("--reverse and --first-parent together require specified latest commit");
+               else
+                       final_commit = (struct commit*) entry->item;
+       }
+
        /*
         * If we have bottom, this will mark the ancestors of the
         * bottom commits we would reach while traversing as
@@ -2721,6 +2735,25 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        if (prepare_revision_walk(&revs))
                die(_("revision walk setup failed"));
 
+       if (reverse && revs.first_parent_only) {
+               struct commit *c = final_commit;
+
+               sb.revs->children.name = "children";
+               while (c->parents &&
+                      hashcmp(c->object.sha1, sb.final->object.sha1)) {
+                       struct commit_list *l = xcalloc(1, sizeof(*l));
+
+                       l->item = c;
+                       if (add_decoration(&sb.revs->children,
+                                          &c->parents->item->object, l))
+                               die("BUG: not unique item in first-parent chain");
+                       c = c->parents->item;
+               }
+
+               if (hashcmp(c->object.sha1, sb.final->object.sha1))
+                       die("--reverse --first-parent together require range along first-parent chain");
+       }
+
        if (is_null_sha1(sb.final->object.sha1)) {
                o = sb.final->util;
                sb.final_buf = xmemdupz(o->file.ptr, o->file.size);
index baaa44c1ae23b6dbf761e358eab6ffb51a02b871..b99a436ef36ecfcc70636741642fdcf07b5a5810 100644 (file)
 #include "column.h"
 #include "utf8.h"
 #include "wt-status.h"
+#include "ref-filter.h"
 
 static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
        N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
        N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
        N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
+       N_("git branch [<options>] [-r | -a] [--points-at]"),
        NULL
 };
 
-#define REF_LOCAL_BRANCH    0x01
-#define REF_REMOTE_BRANCH   0x02
-
 static const char *head;
 static unsigned char head_sha1[20];
 
@@ -52,13 +51,6 @@ enum color_branch {
        BRANCH_COLOR_UPSTREAM = 5
 };
 
-static enum merge_filter {
-       NO_FILTER = 0,
-       SHOW_NOT_MERGED,
-       SHOW_MERGED
-} merge_filter;
-static unsigned char merge_filter_ref[20];
-
 static struct string_list output = STRING_LIST_INIT_DUP;
 static unsigned int colopts;
 
@@ -121,7 +113,7 @@ static int branch_merged(int kind, const char *name,
        void *reference_name_to_free = NULL;
        int merged;
 
-       if (kind == REF_LOCAL_BRANCH) {
+       if (kind == FILTER_REFS_BRANCHES) {
                struct branch *branch = branch_get(name);
                const char *upstream = branch_get_upstream(branch, NULL);
                unsigned char sha1[20];
@@ -199,14 +191,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        struct strbuf bname = STRBUF_INIT;
 
        switch (kinds) {
-       case REF_REMOTE_BRANCH:
+       case FILTER_REFS_REMOTES:
                fmt = "refs/remotes/%s";
                /* For subsequent UI messages */
                remote_branch = 1;
 
                force = 1;
                break;
-       case REF_LOCAL_BRANCH:
+       case FILTER_REFS_BRANCHES:
                fmt = "refs/heads/%s";
                break;
        default:
@@ -223,7 +215,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                int flags = 0;
 
                strbuf_branchname(&bname, argv[i]);
-               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
+               if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {
                        error(_("Cannot delete the branch '%s' "
                              "which you are currently on."), bname.buf);
                        ret = 1;
@@ -279,147 +271,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        return(ret);
 }
 
-struct ref_item {
-       char *name;
-       char *dest;
-       unsigned int kind, width;
-       struct commit *commit;
-       int ignore;
-};
-
-struct ref_list {
-       struct rev_info revs;
-       int index, alloc, maxwidth, verbose, abbrev;
-       struct ref_item *list;
-       struct commit_list *with_commit;
-       int kinds;
-};
-
-static char *resolve_symref(const char *src, const char *prefix)
-{
-       unsigned char sha1[20];
-       int flag;
-       const char *dst;
-
-       dst = resolve_ref_unsafe(src, 0, sha1, &flag);
-       if (!(dst && (flag & REF_ISSYMREF)))
-               return NULL;
-       if (prefix)
-               skip_prefix(dst, prefix, &dst);
-       return xstrdup(dst);
-}
-
-struct append_ref_cb {
-       struct ref_list *ref_list;
-       const char **pattern;
-       int ret;
-};
-
-static int match_patterns(const char **pattern, const char *refname)
-{
-       if (!*pattern)
-               return 1; /* no pattern always matches */
-       while (*pattern) {
-               if (!wildmatch(*pattern, refname, 0, NULL))
-                       return 1;
-               pattern++;
-       }
-       return 0;
-}
-
-static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
-{
-       struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
-       struct ref_list *ref_list = cb->ref_list;
-       struct ref_item *newitem;
-       struct commit *commit;
-       int kind, i;
-       const char *prefix, *orig_refname = refname;
-
-       static struct {
-               int kind;
-               const char *prefix;
-       } ref_kind[] = {
-               { REF_LOCAL_BRANCH, "refs/heads/" },
-               { REF_REMOTE_BRANCH, "refs/remotes/" },
-       };
-
-       /* Detect kind */
-       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
-               prefix = ref_kind[i].prefix;
-               if (skip_prefix(refname, prefix, &refname)) {
-                       kind = ref_kind[i].kind;
-                       break;
-               }
-       }
-       if (ARRAY_SIZE(ref_kind) <= i)
-               return 0;
-
-       /* Don't add types the caller doesn't want */
-       if ((kind & ref_list->kinds) == 0)
-               return 0;
-
-       if (!match_patterns(cb->pattern, refname))
-               return 0;
-
-       commit = NULL;
-       if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
-               commit = lookup_commit_reference_gently(oid->hash, 1);
-               if (!commit) {
-                       cb->ret = error(_("branch '%s' does not point at a commit"), refname);
-                       return 0;
-               }
-
-               /* Filter with with_commit if specified */
-               if (!is_descendant_of(commit, ref_list->with_commit))
-                       return 0;
-
-               if (merge_filter != NO_FILTER)
-                       add_pending_object(&ref_list->revs,
-                                          (struct object *)commit, refname);
-       }
-
-       ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
-
-       /* Record the new item */
-       newitem = &(ref_list->list[ref_list->index++]);
-       newitem->name = xstrdup(refname);
-       newitem->kind = kind;
-       newitem->commit = commit;
-       newitem->width = utf8_strwidth(refname);
-       newitem->dest = resolve_symref(orig_refname, prefix);
-       newitem->ignore = 0;
-       /* adjust for "remotes/" */
-       if (newitem->kind == REF_REMOTE_BRANCH &&
-           ref_list->kinds != REF_REMOTE_BRANCH)
-               newitem->width += 8;
-       if (newitem->width > ref_list->maxwidth)
-               ref_list->maxwidth = newitem->width;
-
-       return 0;
-}
-
-static void free_ref_list(struct ref_list *ref_list)
-{
-       int i;
-
-       for (i = 0; i < ref_list->index; i++) {
-               free(ref_list->list[i].name);
-               free(ref_list->list[i].dest);
-       }
-       free(ref_list->list);
-}
-
-static int ref_cmp(const void *r1, const void *r2)
-{
-       struct ref_item *c1 = (struct ref_item *)(r1);
-       struct ref_item *c2 = (struct ref_item *)(r2);
-
-       if (c1->kind != c2->kind)
-               return c1->kind - c2->kind;
-       return strcmp(c1->name, c2->name);
-}
-
 static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
                int show_upstream_ref)
 {
@@ -482,8 +333,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
        free(ref);
 }
 
-static void add_verbose_info(struct strbuf *out, struct ref_item *item,
-                            int verbose, int abbrev)
+static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
+                            struct ref_filter *filter, const char *refname)
 {
        struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
        const char *sub = _(" **** invalid ref ****");
@@ -494,32 +345,74 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item,
                sub = subject.buf;
        }
 
-       if (item->kind == REF_LOCAL_BRANCH)
-               fill_tracking_info(&stat, item->name, verbose > 1);
+       if (item->kind == FILTER_REFS_BRANCHES)
+               fill_tracking_info(&stat, refname, filter->verbose > 1);
 
        strbuf_addf(out, " %s %s%s",
-               find_unique_abbrev(item->commit->object.sha1, abbrev),
+               find_unique_abbrev(item->commit->object.sha1, filter->abbrev),
                stat.buf, sub);
        strbuf_release(&stat);
        strbuf_release(&subject);
 }
 
-static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
-                          int abbrev, int current, char *prefix)
+static char *get_head_description(void)
+{
+       struct strbuf desc = STRBUF_INIT;
+       struct wt_status_state state;
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, 1);
+       if (state.rebase_in_progress ||
+           state.rebase_interactive_in_progress)
+               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+                           state.branch);
+       else if (state.bisect_in_progress)
+               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+                           state.branch);
+       else if (state.detached_from) {
+               /* TRANSLATORS: make sure these match _("HEAD detached at ")
+                  and _("HEAD detached from ") in wt-status.c */
+               if (state.detached_at)
+                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
+                               state.detached_from);
+               else
+                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
+                               state.detached_from);
+       }
+       else
+               strbuf_addstr(&desc, _("(no branch)"));
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+       return strbuf_detach(&desc, NULL);
+}
+
+static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
+                                     struct ref_filter *filter, const char *remote_prefix)
 {
        char c;
+       int current = 0;
        int color;
        struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
-
-       if (item->ignore)
-               return;
+       const char *prefix = "";
+       const char *desc = item->refname;
+       char *to_free = NULL;
 
        switch (item->kind) {
-       case REF_LOCAL_BRANCH:
-               color = BRANCH_COLOR_LOCAL;
+       case FILTER_REFS_BRANCHES:
+               skip_prefix(desc, "refs/heads/", &desc);
+               if (!filter->detached && !strcmp(desc, head))
+                       current = 1;
+               else
+                       color = BRANCH_COLOR_LOCAL;
                break;
-       case REF_REMOTE_BRANCH:
+       case FILTER_REFS_REMOTES:
+               skip_prefix(desc, "refs/remotes/", &desc);
                color = BRANCH_COLOR_REMOTE;
+               prefix = remote_prefix;
+               break;
+       case FILTER_REFS_DETACHED_HEAD:
+               desc = to_free = get_head_description();
+               current = 1;
                break;
        default:
                color = BRANCH_COLOR_PLAIN;
@@ -532,8 +425,8 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
                color = BRANCH_COLOR_CURRENT;
        }
 
-       strbuf_addf(&name, "%s%s", prefix, item->name);
-       if (verbose) {
+       strbuf_addf(&name, "%s%s", prefix, desc);
+       if (filter->verbose) {
                int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
                strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
                            maxwidth + utf8_compensation, name.buf,
@@ -542,155 +435,82 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
                strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
                            name.buf, branch_get_color(BRANCH_COLOR_RESET));
 
-       if (item->dest)
-               strbuf_addf(&out, " -> %s", item->dest);
-       else if (verbose)
+       if (item->symref) {
+               skip_prefix(item->symref, "refs/remotes/", &desc);
+               strbuf_addf(&out, " -> %s", desc);
+       }
+       else if (filter->verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
-               add_verbose_info(&out, item, verbose, abbrev);
+               add_verbose_info(&out, item, filter, desc);
        if (column_active(colopts)) {
-               assert(!verbose && "--column and --verbose are incompatible");
+               assert(!filter->verbose && "--column and --verbose are incompatible");
                string_list_append(&output, out.buf);
        } else {
                printf("%s\n", out.buf);
        }
        strbuf_release(&name);
        strbuf_release(&out);
+       free(to_free);
 }
 
-static int calc_maxwidth(struct ref_list *refs)
-{
-       int i, w = 0;
-       for (i = 0; i < refs->index; i++) {
-               if (refs->list[i].ignore)
-                       continue;
-               if (refs->list[i].width > w)
-                       w = refs->list[i].width;
-       }
-       return w;
-}
-
-static char *get_head_description(void)
-{
-       struct strbuf desc = STRBUF_INIT;
-       struct wt_status_state state;
-       memset(&state, 0, sizeof(state));
-       wt_status_get_state(&state, 1);
-       if (state.rebase_in_progress ||
-           state.rebase_interactive_in_progress)
-               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
-                           state.branch);
-       else if (state.bisect_in_progress)
-               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
-                           state.branch);
-       else if (state.detached_from) {
-               /* TRANSLATORS: make sure these match _("HEAD detached at ")
-                  and _("HEAD detached from ") in wt-status.c */
-               if (state.detached_at)
-                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
-                               state.detached_from);
-               else
-                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
-                               state.detached_from);
-       }
-       else
-               strbuf_addstr(&desc, _("(no branch)"));
-       free(state.branch);
-       free(state.onto);
-       free(state.detached_from);
-       return strbuf_detach(&desc, NULL);
-}
-
-static void show_detached(struct ref_list *ref_list)
+static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
 {
-       struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
-
-       if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
-               struct ref_item item;
-               item.name = get_head_description();
-               item.width = utf8_strwidth(item.name);
-               item.kind = REF_LOCAL_BRANCH;
-               item.dest = NULL;
-               item.commit = head_commit;
-               item.ignore = 0;
-               if (item.width > ref_list->maxwidth)
-                       ref_list->maxwidth = item.width;
-               print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
-               free(item.name);
+       int i, max = 0;
+       for (i = 0; i < refs->nr; i++) {
+               struct ref_array_item *it = refs->items[i];
+               const char *desc = it->refname;
+               int w;
+
+               skip_prefix(it->refname, "refs/heads/", &desc);
+               skip_prefix(it->refname, "refs/remotes/", &desc);
+               w = utf8_strwidth(desc);
+
+               if (it->kind == FILTER_REFS_REMOTES)
+                       w += remote_bonus;
+               if (w > max)
+                       max = w;
        }
+       return max;
 }
 
-static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
+static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
 {
        int i;
-       struct append_ref_cb cb;
-       struct ref_list ref_list;
-
-       memset(&ref_list, 0, sizeof(ref_list));
-       ref_list.kinds = kinds;
-       ref_list.verbose = verbose;
-       ref_list.abbrev = abbrev;
-       ref_list.with_commit = with_commit;
-       if (merge_filter != NO_FILTER)
-               init_revisions(&ref_list.revs, NULL);
-       cb.ref_list = &ref_list;
-       cb.pattern = pattern;
-       cb.ret = 0;
-       for_each_rawref(append_ref, &cb);
-       if (merge_filter != NO_FILTER) {
-               struct commit *filter;
-               filter = lookup_commit_reference_gently(merge_filter_ref, 0);
-               if (!filter)
-                       die(_("object '%s' does not point to a commit"),
-                           sha1_to_hex(merge_filter_ref));
-
-               filter->object.flags |= UNINTERESTING;
-               add_pending_object(&ref_list.revs,
-                                  (struct object *) filter, "");
-               ref_list.revs.limited = 1;
-
-               if (prepare_revision_walk(&ref_list.revs))
-                       die(_("revision walk setup failed"));
-
-               for (i = 0; i < ref_list.index; i++) {
-                       struct ref_item *item = &ref_list.list[i];
-                       struct commit *commit = item->commit;
-                       int is_merged = !!(commit->object.flags & UNINTERESTING);
-                       item->ignore = is_merged != (merge_filter == SHOW_MERGED);
-               }
+       struct ref_array array;
+       int maxwidth = 0;
+       const char *remote_prefix = "";
 
-               for (i = 0; i < ref_list.index; i++) {
-                       struct ref_item *item = &ref_list.list[i];
-                       clear_commit_marks(item->commit, ALL_REV_FLAGS);
-               }
-               clear_commit_marks(filter, ALL_REV_FLAGS);
+       /*
+        * If we are listing more than just remote branches,
+        * then remote branches will have a "remotes/" prefix.
+        * We need to account for this in the width.
+        */
+       if (filter->kind != FILTER_REFS_REMOTES)
+               remote_prefix = "remotes/";
 
-               if (verbose)
-                       ref_list.maxwidth = calc_maxwidth(&ref_list);
-       }
+       memset(&array, 0, sizeof(array));
 
-       qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
-
-       detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached && match_patterns(pattern, "HEAD"))
-               show_detached(&ref_list);
-
-       for (i = 0; i < ref_list.index; i++) {
-               int current = !detached &&
-                       (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
-                       !strcmp(ref_list.list[i].name, head);
-               char *prefix = (kinds != REF_REMOTE_BRANCH &&
-                               ref_list.list[i].kind == REF_REMOTE_BRANCH)
-                               ? "remotes/" : "";
-               print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
-                              abbrev, current, prefix);
-       }
+       verify_ref_format("%(refname)%(symref)");
+       filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
 
-       free_ref_list(&ref_list);
+       if (filter->verbose)
+               maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
 
-       if (cb.ret)
-               error(_("some refs could not be read"));
+       /*
+        * If no sorting parameter is given then we default to sorting
+        * by 'refname'. This would give us an alphabetically sorted
+        * array with the 'HEAD' ref at the beginning followed by
+        * local branches 'refs/heads/...' and finally remote-tacking
+        * branches 'refs/remotes/...'.
+        */
+       if (!sorting)
+               sorting = ref_default_sorting();
+       ref_array_sort(sorting, &array);
+
+       for (i = 0; i < array.nr; i++)
+               format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
 
-       return cb.ret;
+       ref_array_clear(&array);
 }
 
 static void rename_branch(const char *oldname, const char *newname, int force)
@@ -746,20 +566,6 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        strbuf_release(&newsection);
 }
 
-static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
-{
-       merge_filter = ((opt->long_name[0] == 'n')
-                       ? SHOW_NOT_MERGED
-                       : SHOW_MERGED);
-       if (unset)
-               merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
-       if (!arg)
-               arg = "HEAD";
-       if (get_sha1(arg, merge_filter_ref))
-               die(_("malformed object name %s"), arg);
-       return 0;
-}
-
 static const char edit_description[] = "BRANCH_DESCRIPTION";
 
 static int edit_branch_description(const char *branch_name)
@@ -799,17 +605,16 @@ static int edit_branch_description(const char *branch_name)
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force = 0, list = 0;
-       int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
        int quiet = 0, unset_upstream = 0;
        const char *new_upstream = NULL;
        enum branch_track track;
-       int kinds = REF_LOCAL_BRANCH;
-       struct commit_list *with_commit = NULL;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 
        struct option options[] = {
                OPT_GROUP(N_("Generic options")),
-               OPT__VERBOSE(&verbose,
+               OPT__VERBOSE(&filter.verbose,
                        N_("show hash and subject, give twice for upstream branch")),
                OPT__QUIET(&quiet, N_("suppress informational messages")),
                OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
@@ -819,25 +624,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
                OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
-               OPT_SET_INT('r', "remotes",     &kinds, N_("act on remote-tracking branches"),
-                       REF_REMOTE_BRANCH),
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t) "HEAD",
-               },
-               OPT__ABBREV(&abbrev),
+               OPT_SET_INT('r', "remotes",     &filter.kind, N_("act on remote-tracking branches"),
+                       FILTER_REFS_REMOTES),
+               OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
+               OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
+               OPT__ABBREV(&filter.abbrev),
 
                OPT_GROUP(N_("Specific git-branch actions:")),
-               OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"),
-                       REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
+               OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"),
+                       FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES),
                OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1),
                OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
                OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
@@ -847,22 +642,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "edit-description", &edit_description,
                         N_("edit the description for the branch")),
                OPT__FORCE(&force, N_("force creation, move/rename, deletion")),
+               OPT_MERGED(&filter, N_("print only branches that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
+               OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
                {
-                       OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
-                       N_("commit"), N_("print only not merged branches"),
-                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
-                       opt_parse_merge_filter, (intptr_t) "HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
-                       N_("commit"), N_("print only merged branches"),
-                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
-                       opt_parse_merge_filter, (intptr_t) "HEAD",
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only branches of the object"), 0, parse_opt_object_name
                },
-               OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
                OPT_END(),
        };
 
+       memset(&filter, 0, sizeof(filter));
+       filter.kind = FILTER_REFS_BRANCHES;
+       filter.abbrev = -1;
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_branch_usage, options);
 
@@ -874,11 +669,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
-               detached = 1;
+               filter.detached = 1;
        else if (!skip_prefix(head, "refs/heads/", &head))
                die(_("HEAD not found below refs/heads!"));
-       hashcpy(merge_filter_ref, head_sha1);
-
 
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
@@ -886,17 +679,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
 
-       if (with_commit || merge_filter != NO_FILTER)
+       if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
                list = 1;
 
        if (!!delete + !!rename + !!new_upstream +
            list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
 
-       if (abbrev == -1)
-               abbrev = DEFAULT_ABBREV;
+       if (filter.abbrev == -1)
+               filter.abbrev = DEFAULT_ABBREV;
        finalize_colopts(&colopts, -1);
-       if (verbose) {
+       if (filter.verbose) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and --verbose are incompatible"));
                colopts = 0;
@@ -910,20 +703,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
-               return delete_branches(argc, argv, delete > 1, kinds, quiet);
+               return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
        } else if (list) {
-               int ret = print_ref_list(kinds, detached, verbose, abbrev,
-                                        with_commit, argv);
+               /*  git branch --local also shows HEAD when it is detached */
+               if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
+                       filter.kind |= FILTER_REFS_DETACHED_HEAD;
+               filter.name_patterns = argv;
+               print_ref_list(&filter, sorting);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
-               return ret;
+               return 0;
        }
        else if (edit_description) {
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
 
                if (!argc) {
-                       if (detached)
+                       if (filter.detached)
                                die(_("Cannot give description to detached HEAD"));
                        branch_name = head;
                } else if (argc == 1)
@@ -1011,7 +807,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
 
-               if (kinds != REF_LOCAL_BRANCH)
+               if (filter.kind != FILTER_REFS_BRANCHES)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
 
                if (track == BRANCH_TRACK_OVERRIDE)
index df53def63f29e295e09261b9bd56960cf27582b1..d7acb94a9594563b1473ecac29bd5de9a5e8a55a 100644 (file)
@@ -159,8 +159,7 @@ static int is_git_repository(struct strbuf *path)
        int gitfile_error;
        size_t orig_path_len = path->len;
        assert(orig_path_len != 0);
-       if (path->buf[orig_path_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
        strbuf_addstr(path, ".git");
        if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf))
                ret = 1;
@@ -206,8 +205,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                return res;
        }
 
-       if (path->buf[original_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
 
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
index cc896e22d16bab5fc46e02c7f164e1e512aeca62..caae43e7a390d8666cda259e995c14079103d20d 100644 (file)
@@ -294,9 +294,14 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
                char *ref_git_git = mkpathdup("%s/.git", ref_git);
                free(ref_git);
                ref_git = ref_git_git;
-       } else if (!is_directory(mkpath("%s/objects", ref_git)))
+       } else if (!is_directory(mkpath("%s/objects", ref_git))) {
+               struct strbuf sb = STRBUF_INIT;
+               if (get_common_dir(&sb, ref_git))
+                       die(_("reference repository '%s' as a linked checkout is not supported yet."),
+                           item->string);
                die(_("reference repository '%s' is not a local repository."),
                    item->string);
+       }
 
        if (!access(mkpath("%s/shallow", ref_git), F_OK))
                die(_("reference repository '%s' is shallow"), item->string);
@@ -424,8 +429,10 @@ static void clone_local(const char *src_repo, const char *dest_repo)
        } else {
                struct strbuf src = STRBUF_INIT;
                struct strbuf dest = STRBUF_INIT;
-               strbuf_addf(&src, "%s/objects", src_repo);
-               strbuf_addf(&dest, "%s/objects", dest_repo);
+               get_common_dir(&src, src_repo);
+               get_common_dir(&dest, dest_repo);
+               strbuf_addstr(&src, "/objects");
+               strbuf_addstr(&dest, "/objects");
                copy_or_link_directory(&src, &dest, src_repo, src.len);
                strbuf_release(&src);
                strbuf_release(&dest);
@@ -794,11 +801,15 @@ static void write_refspec_config(const char *src_ref_prefix,
 static void dissociate_from_references(void)
 {
        static const char* argv[] = { "repack", "-a", "-d", NULL };
+       char *alternates = git_pathdup("objects/info/alternates");
 
-       if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
-               die(_("cannot repack to clean up"));
-       if (unlink(git_path("objects/info/alternates")) && errno != ENOENT)
-               die_errno(_("cannot unlink temporary alternates file"));
+       if (!access(alternates, F_OK)) {
+               if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
+                       die(_("cannot repack to clean up"));
+               if (unlink(alternates) && errno != ENOENT)
+                       die_errno(_("cannot unlink temporary alternates file"));
+       }
+       free(alternates);
 }
 
 int cmd_clone(int argc, const char **argv, const char *prefix)
@@ -947,10 +958,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        if (option_reference.nr)
                setup_reference();
-       else if (option_dissociate) {
-               warning(_("--dissociate given, but there is no --reference"));
-               option_dissociate = 0;
-       }
 
        fetch_pattern = value.buf;
        refspec = parse_fetch_refspec(1, &fetch_pattern);
index 71acc4414352e8d097447993937db96a1cc6efaa..adc772786a7ddf9952aaccb7578b57d10396da93 100644 (file)
@@ -246,8 +246,6 @@ static int get_value(const char *key_, const char *regex_)
 
 static char *normalize_value(const char *key, const char *value)
 {
-       char *normalized;
-
        if (!value)
                return NULL;
 
@@ -258,27 +256,21 @@ static char *normalize_value(const char *key, const char *value)
                 * "~/foobar/" in the config file, and to expand the ~
                 * when retrieving the value.
                 */
-               normalized = xstrdup(value);
-       else {
-               normalized = xmalloc(64);
-               if (types == TYPE_INT) {
-                       int64_t v = git_config_int64(key, value);
-                       sprintf(normalized, "%"PRId64, v);
-               }
-               else if (types == TYPE_BOOL)
-                       sprintf(normalized, "%s",
-                               git_config_bool(key, value) ? "true" : "false");
-               else if (types == TYPE_BOOL_OR_INT) {
-                       int is_bool, v;
-                       v = git_config_bool_or_int(key, value, &is_bool);
-                       if (!is_bool)
-                               sprintf(normalized, "%d", v);
-                       else
-                               sprintf(normalized, "%s", v ? "true" : "false");
-               }
+               return xstrdup(value);
+       if (types == TYPE_INT)
+               return xstrfmt("%"PRId64, git_config_int64(key, value));
+       if (types == TYPE_BOOL)
+               return xstrdup(git_config_bool(key, value) ?  "true" : "false");
+       if (types == TYPE_BOOL_OR_INT) {
+               int is_bool, v;
+               v = git_config_bool_or_int(key, value, &is_bool);
+               if (!is_bool)
+                       return xstrfmt("%d", v);
+               else
+                       return xstrdup(v ? "true" : "false");
        }
 
-       return normalized;
+       die("BUG: cannot normalize type %d", types);
 }
 
 static int get_color_found;
index 9a3869f4ffd81f84f738e54c6405aaedb2514f18..ed84963a57b692ab36aecfed61606afcfaa6d2cd 100644 (file)
@@ -528,36 +528,38 @@ static int update_local_ref(struct ref *ref,
        }
 
        if (in_merge_bases(current, updated)) {
-               char quickref[83];
+               struct strbuf quickref = STRBUF_INIT;
                int r;
-               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-               strcat(quickref, "..");
-               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               strbuf_add_unique_abbrev(&quickref, current->object.sha1, DEFAULT_ABBREV);
+               strbuf_addstr(&quickref, "..");
+               strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV);
                if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("fast-forward", ref, 1);
                strbuf_addf(display, "%c %-*s %-*s -> %s%s",
                            r ? '!' : ' ',
-                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           TRANSPORT_SUMMARY_WIDTH, quickref.buf,
                            REFCOL_WIDTH, remote, pretty_ref,
                            r ? _("  (unable to update local ref)") : "");
+               strbuf_release(&quickref);
                return r;
        } else if (force || ref->force) {
-               char quickref[84];
+               struct strbuf quickref = STRBUF_INIT;
                int r;
-               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-               strcat(quickref, "...");
-               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               strbuf_add_unique_abbrev(&quickref, current->object.sha1, DEFAULT_ABBREV);
+               strbuf_addstr(&quickref, "...");
+               strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV);
                if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("forced-update", ref, 1);
                strbuf_addf(display, "%c %-*s %-*s -> %s  (%s)",
                            r ? '!' : '+',
-                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           TRANSPORT_SUMMARY_WIDTH, quickref.buf,
                            REFCOL_WIDTH, remote, pretty_ref,
                            r ? _("unable to update local ref") : _("forced update"));
+               strbuf_release(&quickref);
                return r;
        } else {
                strbuf_addf(display, "! %-*s %-*s -> %s  %s",
@@ -637,8 +639,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                continue;
 
                        if (rm->peer_ref) {
-                               ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
-                               strcpy(ref->name, rm->peer_ref->name);
+                               ref = alloc_ref(rm->peer_ref->name);
                                hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
                                hashcpy(ref->new_sha1, rm->old_sha1);
                                ref->force = rm->peer_ref->force;
@@ -1156,11 +1157,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                        die(_("--depth and --unshallow cannot be used together"));
                else if (!is_repository_shallow())
                        die(_("--unshallow on a complete repository does not make sense"));
-               else {
-                       static char inf_depth[12];
-                       sprintf(inf_depth, "%d", INFINITE_DEPTH);
-                       depth = inf_depth;
-               }
+               else
+                       depth = xstrfmt("%d", INFINITE_DEPTH);
        }
 
        /* no need to be strict, transport_set_option() will validate it again */
index 4ba7f282a5f6cd2e0af643f865a34c3048f2f591..846004b83358cd8adfd5f7de96b2d551700037a1 100644 (file)
@@ -537,7 +537,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
 static void find_merge_parents(struct merge_parents *result,
                               struct strbuf *in, unsigned char *head)
 {
-       struct commit_list *parents, *next;
+       struct commit_list *parents;
        struct commit *head_commit;
        int pos = 0, i, j;
 
@@ -576,13 +576,10 @@ static void find_merge_parents(struct merge_parents *result,
        parents = reduce_heads(parents);
 
        while (parents) {
+               struct commit *cmit = pop_commit(&parents);
                for (i = 0; i < result->nr; i++)
-                       if (!hashcmp(result->item[i].commit,
-                                    parents->item->object.sha1))
+                       if (!hashcmp(result->item[i].commit, cmit->object.sha1))
                                result->item[i].used = 1;
-               next = parents->next;
-               free(parents);
-               parents = next;
        }
 
        for (i = j = 0; i < result->nr; i++) {
index 7919206187c9968cc974c10bb9b5f75c7574e630..4e9f6c29bf1e0c1cc7b44548f49077c2e4a81ec8 100644 (file)
@@ -7,6 +7,9 @@
 
 static char const * const for_each_ref_usage[] = {
        N_("git for-each-ref [<options>] [<pattern>]"),
+       N_("git for-each-ref [--points-at <object>]"),
+       N_("git for-each-ref [(--merged | --no-merged) [<object>]]"),
+       N_("git for-each-ref [--contains [<object>]]"),
        NULL
 };
 
@@ -34,9 +37,18 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
                            N_("field name to sort on"), &parse_opt_ref_sorting),
+               OPT_CALLBACK(0, "points-at", &filter.points_at,
+                            N_("object"), N_("print only refs which points at the given object"),
+                            parse_opt_object_name),
+               OPT_MERGED(&filter, N_("print only refs that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
                OPT_END(),
        };
 
+       memset(&array, 0, sizeof(array));
+       memset(&filter, 0, sizeof(filter));
+
        parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
        if (maxcount < 0) {
                error("invalid --count argument: `%d'", maxcount);
@@ -55,9 +67,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
        /* for warn_ambiguous_refs */
        git_config(git_default_config, NULL);
 
-       memset(&array, 0, sizeof(array));
-       memset(&filter, 0, sizeof(filter));
        filter.name_patterns = argv;
+       filter.match_as_path = 1;
        filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
        ref_array_sort(sorting, &array);
 
index b9a74f0cf6e169073994e466110fd1bde9febba7..8b8bb42c51d13ce9263c0a6fd30fa36810fef78c 100644 (file)
@@ -40,14 +40,6 @@ static int show_dangling = 1;
 #define ERROR_PACK 04
 #define ERROR_REFS 010
 
-#ifdef NO_D_INO_IN_DIRENT
-#define SORT_DIRENT 0
-#define DIRENT_SORT_HINT(de) 0
-#else
-#define SORT_DIRENT 1
-#define DIRENT_SORT_HINT(de) ((de)->d_ino)
-#endif
-
 static int fsck_config(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "fsck.skiplist") == 0) {
@@ -374,102 +366,6 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
        return fsck_obj(obj);
 }
 
-/*
- * This is the sorting chunk size: make it reasonably
- * big so that we can sort well..
- */
-#define MAX_SHA1_ENTRIES (1024)
-
-struct sha1_entry {
-       unsigned long ino;
-       unsigned char sha1[20];
-};
-
-static struct {
-       unsigned long nr;
-       struct sha1_entry *entry[MAX_SHA1_ENTRIES];
-} sha1_list;
-
-static int ino_compare(const void *_a, const void *_b)
-{
-       const struct sha1_entry *a = _a, *b = _b;
-       unsigned long ino1 = a->ino, ino2 = b->ino;
-       return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
-}
-
-static void fsck_sha1_list(void)
-{
-       int i, nr = sha1_list.nr;
-
-       if (SORT_DIRENT)
-               qsort(sha1_list.entry, nr,
-                     sizeof(struct sha1_entry *), ino_compare);
-       for (i = 0; i < nr; i++) {
-               struct sha1_entry *entry = sha1_list.entry[i];
-               unsigned char *sha1 = entry->sha1;
-
-               sha1_list.entry[i] = NULL;
-               if (fsck_sha1(sha1))
-                       errors_found |= ERROR_OBJECT;
-               free(entry);
-       }
-       sha1_list.nr = 0;
-}
-
-static void add_sha1_list(unsigned char *sha1, unsigned long ino)
-{
-       struct sha1_entry *entry = xmalloc(sizeof(*entry));
-       int nr;
-
-       entry->ino = ino;
-       hashcpy(entry->sha1, sha1);
-       nr = sha1_list.nr;
-       if (nr == MAX_SHA1_ENTRIES) {
-               fsck_sha1_list();
-               nr = 0;
-       }
-       sha1_list.entry[nr] = entry;
-       sha1_list.nr = ++nr;
-}
-
-static inline int is_loose_object_file(struct dirent *de,
-                                      char *name, unsigned char *sha1)
-{
-       if (strlen(de->d_name) != 38)
-               return 0;
-       memcpy(name + 2, de->d_name, 39);
-       return !get_sha1_hex(name, sha1);
-}
-
-static void fsck_dir(int i, char *path)
-{
-       DIR *dir = opendir(path);
-       struct dirent *de;
-       char name[100];
-
-       if (!dir)
-               return;
-
-       if (verbose)
-               fprintf(stderr, "Checking directory %s\n", path);
-
-       sprintf(name, "%02x", i);
-       while ((de = readdir(dir)) != NULL) {
-               unsigned char sha1[20];
-
-               if (is_dot_or_dotdot(de->d_name))
-                       continue;
-               if (is_loose_object_file(de, name, sha1)) {
-                       add_sha1_list(sha1, DIRENT_SORT_HINT(de));
-                       continue;
-               }
-               if (starts_with(de->d_name, "tmp_obj_"))
-                       continue;
-               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
-       }
-       closedir(dir);
-}
-
 static int default_refs;
 
 static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
@@ -559,9 +455,28 @@ static void get_default_heads(void)
        }
 }
 
+static int fsck_loose(const unsigned char *sha1, const char *path, void *data)
+{
+       if (fsck_sha1(sha1))
+               errors_found |= ERROR_OBJECT;
+       return 0;
+}
+
+static int fsck_cruft(const char *basename, const char *path, void *data)
+{
+       if (!starts_with(basename, "tmp_obj_"))
+               fprintf(stderr, "bad sha1 file: %s\n", path);
+       return 0;
+}
+
+static int fsck_subdir(int nr, const char *path, void *progress)
+{
+       display_progress(progress, nr + 1);
+       return 0;
+}
+
 static void fsck_object_dir(const char *path)
 {
-       int i;
        struct progress *progress = NULL;
 
        if (verbose)
@@ -569,14 +484,11 @@ static void fsck_object_dir(const char *path)
 
        if (show_progress)
                progress = start_progress(_("Checking object directories"), 256);
-       for (i = 0; i < 256; i++) {
-               static char dir[4096];
-               sprintf(dir, "%s/%02x", path, i);
-               fsck_dir(i, dir);
-               display_progress(progress, i+1);
-       }
+
+       for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
+                                     progress);
+       display_progress(progress, 256);
        stop_progress(&progress);
-       fsck_sha1_list();
 }
 
 static int fsck_head_link(void)
@@ -688,16 +600,18 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        git_config(fsck_config, NULL);
 
        fsck_head_link();
-       if (!connectivity_only)
+       if (!connectivity_only) {
                fsck_object_dir(get_object_directory());
 
-       prepare_alt_odb();
-       for (alt = alt_odb_list; alt; alt = alt->next) {
-               char namebuf[PATH_MAX];
-               int namelen = alt->name - alt->base;
-               memcpy(namebuf, alt->base, namelen);
-               namebuf[namelen - 1] = 0;
-               fsck_object_dir(namebuf);
+               prepare_alt_odb();
+               for (alt = alt_odb_list; alt; alt = alt->next) {
+                       /* directory name, minus trailing slash */
+                       size_t namelen = alt->name - alt->base - 1;
+                       struct strbuf name = STRBUF_INIT;
+                       strbuf_add(&name, alt->base, namelen);
+                       fsck_object_dir(name.buf);
+                       strbuf_release(&name);
+               }
        }
 
        if (check_full) {
index 9216f7b9e262d62c49fa939377ba6c40254c47e7..df3e454447ea4e4e34c6a7eac7ef54df1d614df6 100644 (file)
@@ -217,7 +217,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
                return NULL;
 
        if (gethostname(my_host, sizeof(my_host)))
-               strcpy(my_host, "unknown");
+               xsnprintf(my_host, sizeof(my_host), "unknown");
 
        pidfile_path = git_pathdup("gc.pid");
        fd = hold_lock_file_for_update(&lock, pidfile_path,
@@ -240,7 +240,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
                         * running.
                         */
                        time(NULL) - st.st_mtime <= 12 * 3600 &&
-                       fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 &&
+                       fscanf(fp, "%"SCNuMAX" %127c", &pid, locking_host) == 2 &&
                        /* be gentle to concurrent "gc" on remote hosts */
                        (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
                if (fp != NULL)
index 3422e7307998b9da91f85512084aeae515022a60..1cd0c1ee44daf056befb8a26bba2f4fd343c8658 100644 (file)
@@ -140,17 +140,10 @@ static void exec_man_konqueror(const char *path, const char *page)
 
                /* It's simpler to launch konqueror using kfmclient. */
                if (path) {
-                       const char *file = strrchr(path, '/');
-                       if (file && !strcmp(file + 1, "konqueror")) {
-                               char *new = xstrdup(path);
-                               char *dest = strrchr(new, '/');
-
-                               /* strlen("konqueror") == strlen("kfmclient") */
-                               strcpy(dest + 1, "kfmclient");
-                               path = new;
-                       }
-                       if (file)
-                               filename = file;
+                       size_t len;
+                       if (strip_suffix(path, "/konqueror", &len))
+                               path = xstrfmt("%.*s/kfmclient", (int)len, path);
+                       filename = basename((char *)path);
                } else
                        path = "kfmclient";
                strbuf_addf(&man_page, "man:%s(1)", page);
@@ -183,7 +176,7 @@ static void add_man_viewer(const char *name)
        while (*p)
                p = &((*p)->next);
        *p = xcalloc(1, (sizeof(**p) + len + 1));
-       strncpy((*p)->name, name, len);
+       memcpy((*p)->name, name, len); /* NUL-terminated by xcalloc */
 }
 
 static int supported_man_viewer(const char *name, size_t len)
@@ -199,7 +192,7 @@ static void do_add_man_viewer_info(const char *name,
 {
        struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
 
-       strncpy(new->name, name, len);
+       memcpy(new->name, name, len); /* NUL-terminated by xcalloc */
        new->info = xstrdup(value);
        new->next = man_viewer_info_list;
        man_viewer_info_list = new;
@@ -295,16 +288,6 @@ static int is_git_command(const char *s)
                is_in_cmdlist(&other_cmds, s);
 }
 
-static const char *prepend(const char *prefix, const char *cmd)
-{
-       size_t pre_len = strlen(prefix);
-       size_t cmd_len = strlen(cmd);
-       char *p = xmalloc(pre_len + cmd_len + 1);
-       memcpy(p, prefix, pre_len);
-       strcpy(p + pre_len, cmd);
-       return p;
-}
-
 static const char *cmd_to_page(const char *git_cmd)
 {
        if (!git_cmd)
@@ -312,9 +295,9 @@ static const char *cmd_to_page(const char *git_cmd)
        else if (starts_with(git_cmd, "git"))
                return git_cmd;
        else if (is_git_command(git_cmd))
-               return prepend("git-", git_cmd);
+               return xstrfmt("git-%s", git_cmd);
        else
-               return prepend("git", git_cmd);
+               return xstrfmt("git%s", git_cmd);
 }
 
 static void setup_man_path(void)
index 3431de2362d981b1239d8269d17fb75d8de094fd..1ad1bde69600d3097d71ed67d230d2e2f916544e 100644 (file)
@@ -441,7 +441,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
        int hdrlen;
 
        if (!is_delta_type(type)) {
-               hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1;
+               hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1;
                git_SHA1_Init(&c);
                git_SHA1_Update(&c, hdr, hdrlen);
        } else
index 69323e186cda73fe02658a535d8eae8387ff5444..f59f40768e90cae98cf6541f4c383d762b68b9d1 100644 (file)
@@ -36,10 +36,11 @@ static void safe_create_dir(const char *dir, int share)
                die(_("Could not make %s writable by group"), dir);
 }
 
-static void copy_templates_1(char *path, int baselen,
-                            char *template, int template_baselen,
+static void copy_templates_1(struct strbuf *path, struct strbuf *template,
                             DIR *dir)
 {
+       size_t path_baselen = path->len;
+       size_t template_baselen = template->len;
        struct dirent *de;
 
        /* Note: if ".git/hooks" file exists in the repository being
@@ -49,77 +50,64 @@ static void copy_templates_1(char *path, int baselen,
         * with the way the namespace under .git/ is organized, should
         * be really carefully chosen.
         */
-       safe_create_dir(path, 1);
+       safe_create_dir(path->buf, 1);
        while ((de = readdir(dir)) != NULL) {
                struct stat st_git, st_template;
-               int namelen;
                int exists = 0;
 
+               strbuf_setlen(path, path_baselen);
+               strbuf_setlen(template, template_baselen);
+
                if (de->d_name[0] == '.')
                        continue;
-               namelen = strlen(de->d_name);
-               if ((PATH_MAX <= baselen + namelen) ||
-                   (PATH_MAX <= template_baselen + namelen))
-                       die(_("insanely long template name %s"), de->d_name);
-               memcpy(path + baselen, de->d_name, namelen+1);
-               memcpy(template + template_baselen, de->d_name, namelen+1);
-               if (lstat(path, &st_git)) {
+               strbuf_addstr(path, de->d_name);
+               strbuf_addstr(template, de->d_name);
+               if (lstat(path->buf, &st_git)) {
                        if (errno != ENOENT)
-                               die_errno(_("cannot stat '%s'"), path);
+                               die_errno(_("cannot stat '%s'"), path->buf);
                }
                else
                        exists = 1;
 
-               if (lstat(template, &st_template))
-                       die_errno(_("cannot stat template '%s'"), template);
+               if (lstat(template->buf, &st_template))
+                       die_errno(_("cannot stat template '%s'"), template->buf);
 
                if (S_ISDIR(st_template.st_mode)) {
-                       DIR *subdir = opendir(template);
-                       int baselen_sub = baselen + namelen;
-                       int template_baselen_sub = template_baselen + namelen;
+                       DIR *subdir = opendir(template->buf);
                        if (!subdir)
-                               die_errno(_("cannot opendir '%s'"), template);
-                       path[baselen_sub++] =
-                               template[template_baselen_sub++] = '/';
-                       path[baselen_sub] =
-                               template[template_baselen_sub] = 0;
-                       copy_templates_1(path, baselen_sub,
-                                        template, template_baselen_sub,
-                                        subdir);
+                               die_errno(_("cannot opendir '%s'"), template->buf);
+                       strbuf_addch(path, '/');
+                       strbuf_addch(template, '/');
+                       copy_templates_1(path, template, subdir);
                        closedir(subdir);
                }
                else if (exists)
                        continue;
                else if (S_ISLNK(st_template.st_mode)) {
-                       char lnk[256];
-                       int len;
-                       len = readlink(template, lnk, sizeof(lnk));
-                       if (len < 0)
-                               die_errno(_("cannot readlink '%s'"), template);
-                       if (sizeof(lnk) <= len)
-                               die(_("insanely long symlink %s"), template);
-                       lnk[len] = 0;
-                       if (symlink(lnk, path))
-                               die_errno(_("cannot symlink '%s' '%s'"), lnk, path);
+                       struct strbuf lnk = STRBUF_INIT;
+                       if (strbuf_readlink(&lnk, template->buf, 0) < 0)
+                               die_errno(_("cannot readlink '%s'"), template->buf);
+                       if (symlink(lnk.buf, path->buf))
+                               die_errno(_("cannot symlink '%s' '%s'"),
+                                         lnk.buf, path->buf);
+                       strbuf_release(&lnk);
                }
                else if (S_ISREG(st_template.st_mode)) {
-                       if (copy_file(path, template, st_template.st_mode))
-                               die_errno(_("cannot copy '%s' to '%s'"), template,
-                                         path);
+                       if (copy_file(path->buf, template->buf, st_template.st_mode))
+                               die_errno(_("cannot copy '%s' to '%s'"),
+                                         template->buf, path->buf);
                }
                else
-                       error(_("ignoring template %s"), template);
+                       error(_("ignoring template %s"), template->buf);
        }
 }
 
 static void copy_templates(const char *template_dir)
 {
-       char path[PATH_MAX];
-       char template_path[PATH_MAX];
-       int template_len;
+       struct strbuf path = STRBUF_INIT;
+       struct strbuf template_path = STRBUF_INIT;
+       size_t template_len;
        DIR *dir;
-       const char *git_dir = get_git_dir();
-       int len = strlen(git_dir);
        char *to_free = NULL;
 
        if (!template_dir)
@@ -132,26 +120,23 @@ static void copy_templates(const char *template_dir)
                free(to_free);
                return;
        }
-       template_len = strlen(template_dir);
-       if (PATH_MAX <= (template_len+strlen("/config")))
-               die(_("insanely long template path %s"), template_dir);
-       strcpy(template_path, template_dir);
-       if (template_path[template_len-1] != '/') {
-               template_path[template_len++] = '/';
-               template_path[template_len] = 0;
-       }
-       dir = opendir(template_path);
+
+       strbuf_addstr(&template_path, template_dir);
+       strbuf_complete(&template_path, '/');
+       template_len = template_path.len;
+
+       dir = opendir(template_path.buf);
        if (!dir) {
                warning(_("templates not found %s"), template_dir);
                goto free_return;
        }
 
        /* Make sure that template is from the correct vintage */
-       strcpy(template_path + template_len, "config");
+       strbuf_addstr(&template_path, "config");
        repository_format_version = 0;
        git_config_from_file(check_repository_format_version,
-                            template_path, NULL);
-       template_path[template_len] = 0;
+                            template_path.buf, NULL);
+       strbuf_setlen(&template_path, template_len);
 
        if (repository_format_version &&
            repository_format_version != GIT_REPO_VERSION) {
@@ -162,17 +147,15 @@ static void copy_templates(const char *template_dir)
                goto close_free_return;
        }
 
-       memcpy(path, git_dir, len);
-       if (len && path[len - 1] != '/')
-               path[len++] = '/';
-       path[len] = 0;
-       copy_templates_1(path, len,
-                        template_path, template_len,
-                        dir);
+       strbuf_addstr(&path, get_git_dir());
+       strbuf_complete(&path, '/');
+       copy_templates_1(&path, &template_path, dir);
 close_free_return:
        closedir(dir);
 free_return:
        free(to_free);
+       strbuf_release(&path);
+       strbuf_release(&template_path);
 }
 
 static int git_init_db_config(const char *k, const char *v, void *cb)
@@ -199,28 +182,20 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
 
 static int create_default_files(const char *template_path)
 {
-       const char *git_dir = get_git_dir();
-       unsigned len = strlen(git_dir);
-       static char path[PATH_MAX];
        struct stat st1;
+       struct strbuf buf = STRBUF_INIT;
+       char *path;
        char repo_version_string[10];
        char junk[2];
        int reinit;
        int filemode;
 
-       if (len > sizeof(path)-50)
-               die(_("insane git directory %s"), git_dir);
-       memcpy(path, git_dir, len);
-
-       if (len && path[len-1] != '/')
-               path[len++] = '/';
-
        /*
         * Create .git/refs/{heads,tags}
         */
-       safe_create_dir(git_path("refs"), 1);
-       safe_create_dir(git_path("refs/heads"), 1);
-       safe_create_dir(git_path("refs/tags"), 1);
+       safe_create_dir(git_path_buf(&buf, "refs"), 1);
+       safe_create_dir(git_path_buf(&buf, "refs/heads"), 1);
+       safe_create_dir(git_path_buf(&buf, "refs/tags"), 1);
 
        /* Just look for `init.templatedir` */
        git_config(git_init_db_config, NULL);
@@ -244,16 +219,16 @@ static int create_default_files(const char *template_path)
         */
        if (shared_repository) {
                adjust_shared_perm(get_git_dir());
-               adjust_shared_perm(git_path("refs"));
-               adjust_shared_perm(git_path("refs/heads"));
-               adjust_shared_perm(git_path("refs/tags"));
+               adjust_shared_perm(git_path_buf(&buf, "refs"));
+               adjust_shared_perm(git_path_buf(&buf, "refs/heads"));
+               adjust_shared_perm(git_path_buf(&buf, "refs/tags"));
        }
 
        /*
         * Create the default symlink from ".git/HEAD" to the "master"
         * branch, if it does not exist yet.
         */
-       strcpy(path + len, "HEAD");
+       path = git_path_buf(&buf, "HEAD");
        reinit = (!access(path, R_OK)
                  || readlink(path, junk, sizeof(junk)-1) != -1);
        if (!reinit) {
@@ -262,13 +237,12 @@ static int create_default_files(const char *template_path)
        }
 
        /* This forces creation of new config file */
-       sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
+       xsnprintf(repo_version_string, sizeof(repo_version_string),
+                 "%d", GIT_REPO_VERSION);
        git_config_set("core.repositoryformatversion", repo_version_string);
 
-       path[len] = 0;
-       strcpy(path + len, "config");
-
        /* Check filemode trustability */
+       path = git_path_buf(&buf, "config");
        filemode = TEST_FILEMODE;
        if (TEST_FILEMODE && !lstat(path, &st1)) {
                struct stat st2;
@@ -289,14 +263,13 @@ static int create_default_files(const char *template_path)
                /* allow template config file to override the default */
                if (log_all_ref_updates == -1)
                    git_config_set("core.logallrefupdates", "true");
-               if (needs_work_tree_config(git_dir, work_tree))
+               if (needs_work_tree_config(get_git_dir(), work_tree))
                        git_config_set("core.worktree", work_tree);
        }
 
        if (!reinit) {
                /* Check if symlink is supported in the work tree */
-               path[len] = 0;
-               strcpy(path + len, "tXXXXXX");
+               path = git_path_buf(&buf, "tXXXXXX");
                if (!close(xmkstemp(path)) &&
                    !unlink(path) &&
                    !symlink("testing", path) &&
@@ -307,31 +280,35 @@ static int create_default_files(const char *template_path)
                        git_config_set("core.symlinks", "false");
 
                /* Check if the filesystem is case-insensitive */
-               path[len] = 0;
-               strcpy(path + len, "CoNfIg");
+               path = git_path_buf(&buf, "CoNfIg");
                if (!access(path, F_OK))
                        git_config_set("core.ignorecase", "true");
-               probe_utf8_pathname_composition(path, len);
+               probe_utf8_pathname_composition();
        }
 
+       strbuf_release(&buf);
        return reinit;
 }
 
 static void create_object_directory(void)
 {
-       const char *object_directory = get_object_directory();
-       int len = strlen(object_directory);
-       char *path = xmalloc(len + 40);
+       struct strbuf path = STRBUF_INIT;
+       size_t baselen;
+
+       strbuf_addstr(&path, get_object_directory());
+       baselen = path.len;
+
+       safe_create_dir(path.buf, 1);
 
-       memcpy(path, object_directory, len);
+       strbuf_setlen(&path, baselen);
+       strbuf_addstr(&path, "/pack");
+       safe_create_dir(path.buf, 1);
 
-       safe_create_dir(object_directory, 1);
-       strcpy(path+len, "/pack");
-       safe_create_dir(path, 1);
-       strcpy(path+len, "/info");
-       safe_create_dir(path, 1);
+       strbuf_setlen(&path, baselen);
+       strbuf_addstr(&path, "/info");
+       safe_create_dir(path.buf, 1);
 
-       free(path);
+       strbuf_release(&path);
 }
 
 int set_git_dir_init(const char *git_dir, const char *real_git_dir,
@@ -414,13 +391,13 @@ int init_db(const char *template_dir, unsigned int flags)
                 */
                if (shared_repository < 0)
                        /* force to the mode value */
-                       sprintf(buf, "0%o", -shared_repository);
+                       xsnprintf(buf, sizeof(buf), "0%o", -shared_repository);
                else if (shared_repository == PERM_GROUP)
-                       sprintf(buf, "%d", OLD_PERM_GROUP);
+                       xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP);
                else if (shared_repository == PERM_EVERYBODY)
-                       sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+                       xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
                else
-                       die("oops");
+                       die("BUG: invalid value for shared_repository");
                git_config_set("core.sharedrepository", buf);
                git_config_set("receive.denyNonFastforwards", "true");
        }
index a491d3dea0e412624e7212060658e22a421cdfbf..dda671d975255494a6987e82e2ccf23f7f69f7ee 100644 (file)
@@ -796,8 +796,7 @@ static int reopen_stdout(struct commit *commit, const char *subject,
                if (filename.len >=
                    PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
                        return error(_("name of output directory is too long"));
-               if (filename.buf[filename.len - 1] != '/')
-                       strbuf_addch(&filename, '/');
+               strbuf_complete(&filename, '/');
        }
 
        if (rev->numbered_files)
index 5e9d5450b79713524eeeb981e4685725951cd9d2..a31024900b0a812c94dfa86024cdf45df36b96a1 100644 (file)
@@ -93,12 +93,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        if (argv[i]) {
                int j;
                pattern = xcalloc(argc - i + 1, sizeof(const char *));
-               for (j = i; j < argc; j++) {
-                       int len = strlen(argv[j]);
-                       char *p = xmalloc(len + 3);
-                       sprintf(p, "*/%s", argv[j]);
-                       pattern[j - i] = p;
-               }
+               for (j = i; j < argc; j++)
+                       pattern[j - i] = xstrfmt("*/%s", argv[j]);
        }
        remote = remote_get(dest);
        if (!remote) {
index 3b04a0f082a48c7b9c16f57a5bf4be94c159204c..0e30d862303b67ace2c7accf2e7c9346d8f63264 100644 (file)
@@ -96,12 +96,13 @@ static int show_tree(const unsigned char *sha1, struct strbuf *base,
                        if (!strcmp(type, blob_type)) {
                                unsigned long size;
                                if (sha1_object_info(sha1, &size) == OBJ_BAD)
-                                       strcpy(size_text, "BAD");
+                                       xsnprintf(size_text, sizeof(size_text),
+                                                 "BAD");
                                else
-                                       snprintf(size_text, sizeof(size_text),
-                                                "%lu", size);
+                                       xsnprintf(size_text, sizeof(size_text),
+                                                 "%lu", size);
                        } else
-                               strcpy(size_text, "-");
+                               xsnprintf(size_text, sizeof(size_text), "-");
                        printf("%06o %s %s %7s\t", mode, type,
                               find_unique_abbrev(sha1, abbrev),
                               size_text);
index 999a5250fbe7e6d38a2c8927fa3c37233142ef19..f6df2741112144a38d048ebfd1fe201c8576a040 100644 (file)
 #include "builtin.h"
 #include "utf8.h"
 #include "strbuf.h"
-
-static FILE *cmitmsg, *patchfile, *fin, *fout;
-
-static int keep_subject;
-static int keep_non_patch_brackets_in_subject;
-static const char *metainfo_charset;
-static struct strbuf line = STRBUF_INIT;
-static struct strbuf name = STRBUF_INIT;
-static struct strbuf email = STRBUF_INIT;
-static char *message_id;
-
-static enum  {
-       TE_DONTCARE, TE_QP, TE_BASE64
-} transfer_encoding;
-
-static struct strbuf charset = STRBUF_INIT;
-static int patch_lines;
-static struct strbuf **p_hdr_data, **s_hdr_data;
-static int use_scissors;
-static int add_message_id;
-static int use_inbody_headers = 1;
-
-#define MAX_HDR_PARSED 10
-#define MAX_BOUNDARIES 5
-
-static void cleanup_space(struct strbuf *sb);
-
-
-static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
-{
-       struct strbuf *src = name;
-       if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
-               strchr(name->buf, '<') || strchr(name->buf, '>'))
-               src = email;
-       else if (name == out)
-               return;
-       strbuf_reset(out);
-       strbuf_addbuf(out, src);
-}
-
-static void parse_bogus_from(const struct strbuf *line)
-{
-       /* John Doe <johndoe> */
-
-       char *bra, *ket;
-       /* This is fallback, so do not bother if we already have an
-        * e-mail address.
-        */
-       if (email.len)
-               return;
-
-       bra = strchr(line->buf, '<');
-       if (!bra)
-               return;
-       ket = strchr(bra, '>');
-       if (!ket)
-               return;
-
-       strbuf_reset(&email);
-       strbuf_add(&email, bra + 1, ket - bra - 1);
-
-       strbuf_reset(&name);
-       strbuf_add(&name, line->buf, bra - line->buf);
-       strbuf_trim(&name);
-       get_sane_name(&name, &name, &email);
-}
-
-static void handle_from(const struct strbuf *from)
-{
-       char *at;
-       size_t el;
-       struct strbuf f;
-
-       strbuf_init(&f, from->len);
-       strbuf_addbuf(&f, from);
-
-       at = strchr(f.buf, '@');
-       if (!at) {
-               parse_bogus_from(from);
-               return;
-       }
-
-       /*
-        * If we already have one email, don't take any confusing lines
-        */
-       if (email.len && strchr(at + 1, '@')) {
-               strbuf_release(&f);
-               return;
-       }
-
-       /* Pick up the string around '@', possibly delimited with <>
-        * pair; that is the email part.
-        */
-       while (at > f.buf) {
-               char c = at[-1];
-               if (isspace(c))
-                       break;
-               if (c == '<') {
-                       at[-1] = ' ';
-                       break;
-               }
-               at--;
-       }
-       el = strcspn(at, " \n\t\r\v\f>");
-       strbuf_reset(&email);
-       strbuf_add(&email, at, el);
-       strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
-
-       /* The remainder is name.  It could be
-        *
-        * - "John Doe <john.doe@xz>"                   (a), or
-        * - "john.doe@xz (John Doe)"                   (b), or
-        * - "John (zzz) Doe <john.doe@xz> (Comment)"   (c)
-        *
-        * but we have removed the email part, so
-        *
-        * - remove extra spaces which could stay after email (case 'c'), and
-        * - trim from both ends, possibly removing the () pair at the end
-        *   (cases 'a' and 'b').
-        */
-       cleanup_space(&f);
-       strbuf_trim(&f);
-       if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
-               strbuf_remove(&f, 0, 1);
-               strbuf_setlen(&f, f.len - 1);
-       }
-
-       get_sane_name(&name, &f, &email);
-       strbuf_release(&f);
-}
-
-static void handle_header(struct strbuf **out, const struct strbuf *line)
-{
-       if (!*out) {
-               *out = xmalloc(sizeof(struct strbuf));
-               strbuf_init(*out, line->len);
-       } else
-               strbuf_reset(*out);
-
-       strbuf_addbuf(*out, line);
-}
-
-/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
- * to have enough heuristics to grok MIME encoded patches often found
- * on our mailing lists.  For example, we do not even treat header lines
- * case insensitively.
- */
-
-static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
-{
-       const char *ends, *ap = strcasestr(line, name);
-       size_t sz;
-
-       strbuf_setlen(attr, 0);
-       if (!ap)
-               return 0;
-       ap += strlen(name);
-       if (*ap == '"') {
-               ap++;
-               ends = "\"";
-       }
-       else
-               ends = "; \t";
-       sz = strcspn(ap, ends);
-       strbuf_add(attr, ap, sz);
-       return 1;
-}
-
-static struct strbuf *content[MAX_BOUNDARIES];
-
-static struct strbuf **content_top = content;
-
-static void handle_content_type(struct strbuf *line)
-{
-       struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
-       strbuf_init(boundary, line->len);
-
-       if (slurp_attr(line->buf, "boundary=", boundary)) {
-               strbuf_insert(boundary, 0, "--", 2);
-               if (++content_top > &content[MAX_BOUNDARIES]) {
-                       fprintf(stderr, "Too many boundaries to handle\n");
-                       exit(1);
-               }
-               *content_top = boundary;
-               boundary = NULL;
-       }
-       slurp_attr(line->buf, "charset=", &charset);
-
-       if (boundary) {
-               strbuf_release(boundary);
-               free(boundary);
-       }
-}
-
-static void handle_message_id(const struct strbuf *line)
-{
-       if (add_message_id)
-               message_id = strdup(line->buf);
-}
-
-static void handle_content_transfer_encoding(const struct strbuf *line)
-{
-       if (strcasestr(line->buf, "base64"))
-               transfer_encoding = TE_BASE64;
-       else if (strcasestr(line->buf, "quoted-printable"))
-               transfer_encoding = TE_QP;
-       else
-               transfer_encoding = TE_DONTCARE;
-}
-
-static int is_multipart_boundary(const struct strbuf *line)
-{
-       return (((*content_top)->len <= line->len) &&
-               !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
-}
-
-static void cleanup_subject(struct strbuf *subject)
-{
-       size_t at = 0;
-
-       while (at < subject->len) {
-               char *pos;
-               size_t remove;
-
-               switch (subject->buf[at]) {
-               case 'r': case 'R':
-                       if (subject->len <= at + 3)
-                               break;
-                       if ((subject->buf[at + 1] == 'e' ||
-                            subject->buf[at + 1] == 'E') &&
-                           subject->buf[at + 2] == ':') {
-                               strbuf_remove(subject, at, 3);
-                               continue;
-                       }
-                       at++;
-                       break;
-               case ' ': case '\t': case ':':
-                       strbuf_remove(subject, at, 1);
-                       continue;
-               case '[':
-                       pos = strchr(subject->buf + at, ']');
-                       if (!pos)
-                               break;
-                       remove = pos - subject->buf + at + 1;
-                       if (!keep_non_patch_brackets_in_subject ||
-                           (7 <= remove &&
-                            memmem(subject->buf + at, remove, "PATCH", 5)))
-                               strbuf_remove(subject, at, remove);
-                       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;
-       }
-       strbuf_trim(subject);
-}
-
-static void cleanup_space(struct strbuf *sb)
-{
-       size_t pos, cnt;
-       for (pos = 0; pos < sb->len; pos++) {
-               if (isspace(sb->buf[pos])) {
-                       sb->buf[pos] = ' ';
-                       for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
-                       strbuf_remove(sb, pos + 1, cnt);
-               }
-       }
-}
-
-static void decode_header(struct strbuf *line);
-static const char *header[MAX_HDR_PARSED] = {
-       "From","Subject","Date",
-};
-
-static inline int cmp_header(const struct strbuf *line, const char *hdr)
-{
-       int len = strlen(hdr);
-       return !strncasecmp(line->buf, hdr, len) && line->len > len &&
-                       line->buf[len] == ':' && isspace(line->buf[len + 1]);
-}
-
-static int is_format_patch_separator(const char *line, int len)
-{
-       static const char SAMPLE[] =
-               "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
-       const char *cp;
-
-       if (len != strlen(SAMPLE))
-               return 0;
-       if (!skip_prefix(line, "From ", &cp))
-               return 0;
-       if (strspn(cp, "0123456789abcdef") != 40)
-               return 0;
-       cp += 40;
-       return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
-}
-
-static int check_header(const struct strbuf *line,
-                               struct strbuf *hdr_data[], int overwrite)
-{
-       int i, ret = 0, len;
-       struct strbuf sb = STRBUF_INIT;
-       /* search for the interesting parts */
-       for (i = 0; header[i]; i++) {
-               int len = strlen(header[i]);
-               if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
-                       /* Unwrap inline B and Q encoding, and optionally
-                        * normalize the meta information to utf8.
-                        */
-                       strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
-                       decode_header(&sb);
-                       handle_header(&hdr_data[i], &sb);
-                       ret = 1;
-                       goto check_header_out;
-               }
-       }
-
-       /* Content stuff */
-       if (cmp_header(line, "Content-Type")) {
-               len = strlen("Content-Type: ");
-               strbuf_add(&sb, line->buf + len, line->len - len);
-               decode_header(&sb);
-               strbuf_insert(&sb, 0, "Content-Type: ", len);
-               handle_content_type(&sb);
-               ret = 1;
-               goto check_header_out;
-       }
-       if (cmp_header(line, "Content-Transfer-Encoding")) {
-               len = strlen("Content-Transfer-Encoding: ");
-               strbuf_add(&sb, line->buf + len, line->len - len);
-               decode_header(&sb);
-               handle_content_transfer_encoding(&sb);
-               ret = 1;
-               goto check_header_out;
-       }
-       if (cmp_header(line, "Message-Id")) {
-               len = strlen("Message-Id: ");
-               strbuf_add(&sb, line->buf + len, line->len - len);
-               decode_header(&sb);
-               handle_message_id(&sb);
-               ret = 1;
-               goto check_header_out;
-       }
-
-       /* for inbody stuff */
-       if (starts_with(line->buf, ">From") && isspace(line->buf[5])) {
-               ret = is_format_patch_separator(line->buf + 1, line->len - 1);
-               goto check_header_out;
-       }
-       if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
-               for (i = 0; header[i]; i++) {
-                       if (!strcmp("Subject", header[i])) {
-                               handle_header(&hdr_data[i], line);
-                               ret = 1;
-                               goto check_header_out;
-                       }
-               }
-       }
-
-check_header_out:
-       strbuf_release(&sb);
-       return ret;
-}
-
-static int is_rfc2822_header(const struct strbuf *line)
-{
-       /*
-        * The section that defines the loosest possible
-        * field name is "3.6.8 Optional fields".
-        *
-        * optional-field = field-name ":" unstructured CRLF
-        * field-name = 1*ftext
-        * ftext = %d33-57 / %59-126
-        */
-       int ch;
-       char *cp = line->buf;
-
-       /* Count mbox From headers as headers */
-       if (starts_with(cp, "From ") || starts_with(cp, ">From "))
-               return 1;
-
-       while ((ch = *cp++)) {
-               if (ch == ':')
-                       return 1;
-               if ((33 <= ch && ch <= 57) ||
-                   (59 <= ch && ch <= 126))
-                       continue;
-               break;
-       }
-       return 0;
-}
-
-static int read_one_header_line(struct strbuf *line, FILE *in)
-{
-       /* Get the first part of the line. */
-       if (strbuf_getline(line, in, '\n'))
-               return 0;
-
-       /*
-        * Is it an empty line or not a valid rfc2822 header?
-        * If so, stop here, and return false ("not a header")
-        */
-       strbuf_rtrim(line);
-       if (!line->len || !is_rfc2822_header(line)) {
-               /* Re-add the newline */
-               strbuf_addch(line, '\n');
-               return 0;
-       }
-
-       /*
-        * Now we need to eat all the continuation lines..
-        * Yuck, 2822 header "folding"
-        */
-       for (;;) {
-               int peek;
-               struct strbuf continuation = STRBUF_INIT;
-
-               peek = fgetc(in); ungetc(peek, in);
-               if (peek != ' ' && peek != '\t')
-                       break;
-               if (strbuf_getline(&continuation, in, '\n'))
-                       break;
-               continuation.buf[0] = ' ';
-               strbuf_rtrim(&continuation);
-               strbuf_addbuf(line, &continuation);
-       }
-
-       return 1;
-}
-
-static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
-{
-       const char *in = q_seg->buf;
-       int c;
-       struct strbuf *out = xmalloc(sizeof(struct strbuf));
-       strbuf_init(out, q_seg->len);
-
-       while ((c = *in++) != 0) {
-               if (c == '=') {
-                       int d = *in++;
-                       if (d == '\n' || !d)
-                               break; /* drop trailing newline */
-                       strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
-                       continue;
-               }
-               if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
-                       c = 0x20;
-               strbuf_addch(out, c);
-       }
-       return out;
-}
-
-static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
-{
-       /* Decode in..ep, possibly in-place to ot */
-       int c, pos = 0, acc = 0;
-       const char *in = b_seg->buf;
-       struct strbuf *out = xmalloc(sizeof(struct strbuf));
-       strbuf_init(out, b_seg->len);
-
-       while ((c = *in++) != 0) {
-               if (c == '+')
-                       c = 62;
-               else if (c == '/')
-                       c = 63;
-               else if ('A' <= c && c <= 'Z')
-                       c -= 'A';
-               else if ('a' <= c && c <= 'z')
-                       c -= 'a' - 26;
-               else if ('0' <= c && c <= '9')
-                       c -= '0' - 52;
-               else
-                       continue; /* garbage */
-               switch (pos++) {
-               case 0:
-                       acc = (c << 2);
-                       break;
-               case 1:
-                       strbuf_addch(out, (acc | (c >> 4)));
-                       acc = (c & 15) << 4;
-                       break;
-               case 2:
-                       strbuf_addch(out, (acc | (c >> 2)));
-                       acc = (c & 3) << 6;
-                       break;
-               case 3:
-                       strbuf_addch(out, (acc | c));
-                       acc = pos = 0;
-                       break;
-               }
-       }
-       return out;
-}
-
-static void convert_to_utf8(struct strbuf *line, const char *charset)
-{
-       char *out;
-
-       if (!charset || !*charset)
-               return;
-
-       if (same_encoding(metainfo_charset, charset))
-               return;
-       out = reencode_string(line->buf, metainfo_charset, charset);
-       if (!out)
-               die("cannot convert from %s to %s",
-                   charset, metainfo_charset);
-       strbuf_attach(line, out, strlen(out), strlen(out));
-}
-
-static int decode_header_bq(struct strbuf *it)
-{
-       char *in, *ep, *cp;
-       struct strbuf outbuf = STRBUF_INIT, *dec;
-       struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
-       int rfc2047 = 0;
-
-       in = it->buf;
-       while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
-               int encoding;
-               strbuf_reset(&charset_q);
-               strbuf_reset(&piecebuf);
-               rfc2047 = 1;
-
-               if (in != ep) {
-                       /*
-                        * We are about to process an encoded-word
-                        * that begins at ep, but there is something
-                        * before the encoded word.
-                        */
-                       char *scan;
-                       for (scan = in; scan < ep; scan++)
-                               if (!isspace(*scan))
-                                       break;
-
-                       if (scan != ep || in == it->buf) {
-                               /*
-                                * We should not lose that "something",
-                                * unless we have just processed an
-                                * encoded-word, and there is only LWS
-                                * before the one we are about to process.
-                                */
-                               strbuf_add(&outbuf, in, ep - in);
-                       }
-               }
-               /* E.g.
-                * ep : "=?iso-2022-jp?B?GyR...?= foo"
-                * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
-                */
-               ep += 2;
-
-               if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
-                       goto decode_header_bq_out;
-
-               if (cp + 3 - it->buf > it->len)
-                       goto decode_header_bq_out;
-               strbuf_add(&charset_q, ep, cp - ep);
-
-               encoding = cp[1];
-               if (!encoding || cp[2] != '?')
-                       goto decode_header_bq_out;
-               ep = strstr(cp + 3, "?=");
-               if (!ep)
-                       goto decode_header_bq_out;
-               strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
-               switch (tolower(encoding)) {
-               default:
-                       goto decode_header_bq_out;
-               case 'b':
-                       dec = decode_b_segment(&piecebuf);
-                       break;
-               case 'q':
-                       dec = decode_q_segment(&piecebuf, 1);
-                       break;
-               }
-               if (metainfo_charset)
-                       convert_to_utf8(dec, charset_q.buf);
-
-               strbuf_addbuf(&outbuf, dec);
-               strbuf_release(dec);
-               free(dec);
-               in = ep + 2;
-       }
-       strbuf_addstr(&outbuf, in);
-       strbuf_reset(it);
-       strbuf_addbuf(it, &outbuf);
-decode_header_bq_out:
-       strbuf_release(&outbuf);
-       strbuf_release(&charset_q);
-       strbuf_release(&piecebuf);
-       return rfc2047;
-}
-
-static void decode_header(struct strbuf *it)
-{
-       if (decode_header_bq(it))
-               return;
-       /* otherwise "it" is a straight copy of the input.
-        * This can be binary guck but there is no charset specified.
-        */
-       if (metainfo_charset)
-               convert_to_utf8(it, "");
-}
-
-static void decode_transfer_encoding(struct strbuf *line)
-{
-       struct strbuf *ret;
-
-       switch (transfer_encoding) {
-       case TE_QP:
-               ret = decode_q_segment(line, 0);
-               break;
-       case TE_BASE64:
-               ret = decode_b_segment(line);
-               break;
-       case TE_DONTCARE:
-       default:
-               return;
-       }
-       strbuf_reset(line);
-       strbuf_addbuf(line, ret);
-       strbuf_release(ret);
-       free(ret);
-}
-
-static void handle_filter(struct strbuf *line);
-
-static int find_boundary(void)
-{
-       while (!strbuf_getline(&line, fin, '\n')) {
-               if (*content_top && is_multipart_boundary(&line))
-                       return 1;
-       }
-       return 0;
-}
-
-static int handle_boundary(void)
-{
-       struct strbuf newline = STRBUF_INIT;
-
-       strbuf_addch(&newline, '\n');
-again:
-       if (line.len >= (*content_top)->len + 2 &&
-           !memcmp(line.buf + (*content_top)->len, "--", 2)) {
-               /* we hit an end boundary */
-               /* pop the current boundary off the stack */
-               strbuf_release(*content_top);
-               free(*content_top);
-               *content_top = NULL;
-
-               /* technically won't happen as is_multipart_boundary()
-                  will fail first.  But just in case..
-                */
-               if (--content_top < content) {
-                       fprintf(stderr, "Detected mismatched boundaries, "
-                                       "can't recover\n");
-                       exit(1);
-               }
-               handle_filter(&newline);
-               strbuf_release(&newline);
-
-               /* skip to the next boundary */
-               if (!find_boundary())
-                       return 0;
-               goto again;
-       }
-
-       /* set some defaults */
-       transfer_encoding = TE_DONTCARE;
-       strbuf_reset(&charset);
-
-       /* slurp in this section's info */
-       while (read_one_header_line(&line, fin))
-               check_header(&line, p_hdr_data, 0);
-
-       strbuf_release(&newline);
-       /* replenish line */
-       if (strbuf_getline(&line, fin, '\n'))
-               return 0;
-       strbuf_addch(&line, '\n');
-       return 1;
-}
-
-static inline int patchbreak(const struct strbuf *line)
-{
-       size_t i;
-
-       /* Beginning of a "diff -" header? */
-       if (starts_with(line->buf, "diff -"))
-               return 1;
-
-       /* CVS "Index: " line? */
-       if (starts_with(line->buf, "Index: "))
-               return 1;
-
-       /*
-        * "--- <filename>" starts patches without headers
-        * "---<sp>*" is a manual separator
-        */
-       if (line->len < 4)
-               return 0;
-
-       if (starts_with(line->buf, "---")) {
-               /* space followed by a filename? */
-               if (line->buf[3] == ' ' && !isspace(line->buf[4]))
-                       return 1;
-               /* Just whitespace? */
-               for (i = 3; i < line->len; i++) {
-                       unsigned char c = line->buf[i];
-                       if (c == '\n')
-                               return 1;
-                       if (!isspace(c))
-                               break;
-               }
-               return 0;
-       }
-       return 0;
-}
-
-static int is_scissors_line(const struct strbuf *line)
-{
-       size_t i, len = line->len;
-       int scissors = 0, gap = 0;
-       int first_nonblank = -1;
-       int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
-       const char *buf = line->buf;
-
-       for (i = 0; i < len; i++) {
-               if (isspace(buf[i])) {
-                       if (in_perforation) {
-                               perforation++;
-                               gap++;
-                       }
-                       continue;
-               }
-               last_nonblank = i;
-               if (first_nonblank < 0)
-                       first_nonblank = i;
-               if (buf[i] == '-') {
-                       in_perforation = 1;
-                       perforation++;
-                       continue;
-               }
-               if (i + 1 < len &&
-                   (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) ||
-                    !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) {
-                       in_perforation = 1;
-                       perforation += 2;
-                       scissors += 2;
-                       i++;
-                       continue;
-               }
-               in_perforation = 0;
-       }
-
-       /*
-        * The mark must be at least 8 bytes long (e.g. "-- >8 --").
-        * Even though there can be arbitrary cruft on the same line
-        * (e.g. "cut here"), in order to avoid misidentification, the
-        * perforation must occupy more than a third of the visible
-        * width of the line, and dashes and scissors must occupy more
-        * than half of the perforation.
-        */
-
-       visible = last_nonblank - first_nonblank + 1;
-       return (scissors && 8 <= visible &&
-               visible < perforation * 3 &&
-               gap * 2 < perforation);
-}
-
-static int handle_commit_msg(struct strbuf *line)
-{
-       static int still_looking = 1;
-
-       if (!cmitmsg)
-               return 0;
-
-       if (still_looking) {
-               if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
-                       return 0;
-       }
-
-       if (use_inbody_headers && still_looking) {
-               still_looking = check_header(line, s_hdr_data, 0);
-               if (still_looking)
-                       return 0;
-       } else
-               /* Only trim the first (blank) line of the commit message
-                * when ignoring in-body headers.
-                */
-               still_looking = 0;
-
-       /* normalize the log message to UTF-8. */
-       if (metainfo_charset)
-               convert_to_utf8(line, charset.buf);
-
-       if (use_scissors && is_scissors_line(line)) {
-               int i;
-               if (fseek(cmitmsg, 0L, SEEK_SET))
-                       die_errno("Could not rewind output message file");
-               if (ftruncate(fileno(cmitmsg), 0))
-                       die_errno("Could not truncate output message file at scissors");
-               still_looking = 1;
-
-               /*
-                * We may have already read "secondary headers"; purge
-                * them to give ourselves a clean restart.
-                */
-               for (i = 0; header[i]; i++) {
-                       if (s_hdr_data[i])
-                               strbuf_release(s_hdr_data[i]);
-                       s_hdr_data[i] = NULL;
-               }
-               return 0;
-       }
-
-       if (patchbreak(line)) {
-               if (message_id)
-                       fprintf(cmitmsg, "Message-Id: %s\n", message_id);
-               fclose(cmitmsg);
-               cmitmsg = NULL;
-               return 1;
-       }
-
-       fputs(line->buf, cmitmsg);
-       return 0;
-}
-
-static void handle_patch(const struct strbuf *line)
-{
-       fwrite(line->buf, 1, line->len, patchfile);
-       patch_lines++;
-}
-
-static void handle_filter(struct strbuf *line)
-{
-       static int filter = 0;
-
-       /* filter tells us which part we left off on */
-       switch (filter) {
-       case 0:
-               if (!handle_commit_msg(line))
-                       break;
-               filter++;
-       case 1:
-               handle_patch(line);
-               break;
-       }
-}
-
-static void handle_body(void)
-{
-       struct strbuf prev = STRBUF_INIT;
-
-       /* Skip up to the first boundary */
-       if (*content_top) {
-               if (!find_boundary())
-                       goto handle_body_out;
-       }
-
-       do {
-               /* process any boundary lines */
-               if (*content_top && is_multipart_boundary(&line)) {
-                       /* flush any leftover */
-                       if (prev.len) {
-                               handle_filter(&prev);
-                               strbuf_reset(&prev);
-                       }
-                       if (!handle_boundary())
-                               goto handle_body_out;
-               }
-
-               /* Unwrap transfer encoding */
-               decode_transfer_encoding(&line);
-
-               switch (transfer_encoding) {
-               case TE_BASE64:
-               case TE_QP:
-               {
-                       struct strbuf **lines, **it, *sb;
-
-                       /* Prepend any previous partial lines */
-                       strbuf_insert(&line, 0, prev.buf, prev.len);
-                       strbuf_reset(&prev);
-
-                       /*
-                        * This is a decoded line that may contain
-                        * multiple new lines.  Pass only one chunk
-                        * at a time to handle_filter()
-                        */
-                       lines = strbuf_split(&line, '\n');
-                       for (it = lines; (sb = *it); it++) {
-                               if (*(it + 1) == NULL) /* The last line */
-                                       if (sb->buf[sb->len - 1] != '\n') {
-                                               /* Partial line, save it for later. */
-                                               strbuf_addbuf(&prev, sb);
-                                               break;
-                                       }
-                               handle_filter(sb);
-                       }
-                       /*
-                        * The partial chunk is saved in "prev" and will be
-                        * appended by the next iteration of read_line_with_nul().
-                        */
-                       strbuf_list_free(lines);
-                       break;
-               }
-               default:
-                       handle_filter(&line);
-               }
-
-       } while (!strbuf_getwholeline(&line, fin, '\n'));
-
-handle_body_out:
-       strbuf_release(&prev);
-}
-
-static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
-{
-       const char *sp = data->buf;
-       while (1) {
-               char *ep = strchr(sp, '\n');
-               int len;
-               if (!ep)
-                       len = strlen(sp);
-               else
-                       len = ep - sp;
-               fprintf(fout, "%s: %.*s\n", hdr, len, sp);
-               if (!ep)
-                       break;
-               sp = ep + 1;
-       }
-}
-
-static void handle_info(void)
-{
-       struct strbuf *hdr;
-       int i;
-
-       for (i = 0; header[i]; i++) {
-               /* only print inbody headers if we output a patch file */
-               if (patch_lines && s_hdr_data[i])
-                       hdr = s_hdr_data[i];
-               else if (p_hdr_data[i])
-                       hdr = p_hdr_data[i];
-               else
-                       continue;
-
-               if (!strcmp(header[i], "Subject")) {
-                       if (!keep_subject) {
-                               cleanup_subject(hdr);
-                               cleanup_space(hdr);
-                       }
-                       output_header_lines(fout, "Subject", hdr);
-               } else if (!strcmp(header[i], "From")) {
-                       cleanup_space(hdr);
-                       handle_from(hdr);
-                       fprintf(fout, "Author: %s\n", name.buf);
-                       fprintf(fout, "Email: %s\n", email.buf);
-               } else {
-                       cleanup_space(hdr);
-                       fprintf(fout, "%s: %s\n", header[i], hdr->buf);
-               }
-       }
-       fprintf(fout, "\n");
-}
-
-static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
-{
-       int peek;
-       fin = in;
-       fout = out;
-
-       cmitmsg = fopen(msg, "w");
-       if (!cmitmsg) {
-               perror(msg);
-               return -1;
-       }
-       patchfile = fopen(patch, "w");
-       if (!patchfile) {
-               perror(patch);
-               fclose(cmitmsg);
-               return -1;
-       }
-
-       p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
-       s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
-
-       do {
-               peek = fgetc(in);
-       } while (isspace(peek));
-       ungetc(peek, in);
-
-       /* process the email header */
-       while (read_one_header_line(&line, fin))
-               check_header(&line, p_hdr_data, 1);
-
-       handle_body();
-       handle_info();
-
-       return 0;
-}
-
-static int git_mailinfo_config(const char *var, const char *value, void *unused)
-{
-       if (!starts_with(var, "mailinfo."))
-               return git_default_config(var, value, unused);
-       if (!strcmp(var, "mailinfo.scissors")) {
-               use_scissors = git_config_bool(var, value);
-               return 0;
-       }
-       /* perhaps others here */
-       return 0;
-}
+#include "mailinfo.h"
 
 static const char mailinfo_usage[] =
        "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
@@ -1036,34 +14,36 @@ static const char mailinfo_usage[] =
 int cmd_mailinfo(int argc, const char **argv, const char *prefix)
 {
        const char *def_charset;
+       struct mailinfo mi;
+       int status;
 
        /* NEEDSWORK: might want to do the optional .git/ directory
         * discovery
         */
-       git_config(git_mailinfo_config, NULL);
+       setup_mailinfo(&mi);
 
        def_charset = get_commit_output_encoding();
-       metainfo_charset = def_charset;
+       mi.metainfo_charset = def_charset;
 
        while (1 < argc && argv[1][0] == '-') {
                if (!strcmp(argv[1], "-k"))
-                       keep_subject = 1;
+                       mi.keep_subject = 1;
                else if (!strcmp(argv[1], "-b"))
-                       keep_non_patch_brackets_in_subject = 1;
+                       mi.keep_non_patch_brackets_in_subject = 1;
                else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
-                       add_message_id = 1;
+                       mi.add_message_id = 1;
                else if (!strcmp(argv[1], "-u"))
-                       metainfo_charset = def_charset;
+                       mi.metainfo_charset = def_charset;
                else if (!strcmp(argv[1], "-n"))
-                       metainfo_charset = NULL;
+                       mi.metainfo_charset = NULL;
                else if (starts_with(argv[1], "--encoding="))
-                       metainfo_charset = argv[1] + 11;
+                       mi.metainfo_charset = argv[1] + 11;
                else if (!strcmp(argv[1], "--scissors"))
-                       use_scissors = 1;
+                       mi.use_scissors = 1;
                else if (!strcmp(argv[1], "--no-scissors"))
-                       use_scissors = 0;
+                       mi.use_scissors = 0;
                else if (!strcmp(argv[1], "--no-inbody-headers"))
-                       use_inbody_headers = 0;
+                       mi.use_inbody_headers = 0;
                else
                        usage(mailinfo_usage);
                argc--; argv++;
@@ -1072,5 +52,10 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
        if (argc != 3)
                usage(mailinfo_usage);
 
-       return !!mailinfo(stdin, stdout, argv[1], argv[2]);
+       mi.input = stdin;
+       mi.output = stdout;
+       status = !!mailinfo(&mi, argv[1], argv[2]);
+       clear_mailinfo(&mi);
+
+       return status;
 }
index 8e02ea109ac8f9fd739c4d54b7a709916f88022a..104277acc49a08d9472523edc8337c3eeeb345cd 100644 (file)
@@ -98,30 +98,37 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 {
        DIR *dir;
        struct dirent *dent;
-       char name[PATH_MAX];
+       char *name = NULL;
        char *subs[] = { "cur", "new", NULL };
        char **sub;
+       int ret = -1;
 
        for (sub = subs; *sub; ++sub) {
-               snprintf(name, sizeof(name), "%s/%s", path, *sub);
+               free(name);
+               name = xstrfmt("%s/%s", path, *sub);
                if ((dir = opendir(name)) == NULL) {
                        if (errno == ENOENT)
                                continue;
                        error("cannot opendir %s (%s)", name, strerror(errno));
-                       return -1;
+                       goto out;
                }
 
                while ((dent = readdir(dir)) != NULL) {
                        if (dent->d_name[0] == '.')
                                continue;
-                       snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+                       free(name);
+                       name = xstrfmt("%s/%s", *sub, dent->d_name);
                        string_list_insert(list, name);
                }
 
                closedir(dir);
        }
 
-       return 0;
+       ret = 0;
+
+out:
+       free(name);
+       return ret;
 }
 
 static int maildir_filename_cmp(const char *a, const char *b)
@@ -148,8 +155,8 @@ static int maildir_filename_cmp(const char *a, const char *b)
 static int split_maildir(const char *maildir, const char *dir,
        int nr_prec, int skip)
 {
-       char file[PATH_MAX];
-       char name[PATH_MAX];
+       char *file = NULL;
+       FILE *f = NULL;
        int ret = -1;
        int i;
        struct string_list list = STRING_LIST_INIT_DUP;
@@ -160,8 +167,11 @@ static int split_maildir(const char *maildir, const char *dir,
                goto out;
 
        for (i = 0; i < list.nr; i++) {
-               FILE *f;
-               snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
+               char *name;
+
+               free(file);
+               file = xstrfmt("%s/%s", maildir, list.items[i].string);
+
                f = fopen(file, "r");
                if (!f) {
                        error("cannot open mail %s (%s)", file, strerror(errno));
@@ -173,14 +183,19 @@ static int split_maildir(const char *maildir, const char *dir,
                        goto out;
                }
 
-               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
                split_one(f, name, 1);
+               free(name);
 
                fclose(f);
+               f = NULL;
        }
 
        ret = skip;
 out:
+       if (f)
+               fclose(f);
+       free(file);
        string_list_clear(&list, 1);
        return ret;
 }
@@ -188,7 +203,6 @@ static int split_maildir(const char *maildir, const char *dir,
 static int split_mbox(const char *file, const char *dir, int allow_bare,
                      int nr_prec, int skip)
 {
-       char name[PATH_MAX];
        int ret = -1;
        int peek;
 
@@ -215,8 +229,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
        }
 
        while (!file_done) {
-               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
                file_done = split_one(f, name, allow_bare);
+               free(name);
        }
 
        if (f != stdin)
index 1a1eafa6fdc2e9dc66c98d3a81b7ee50e44727da..1c3427c36caa40e8740b090c44a048201e81c706 100644 (file)
@@ -9,7 +9,7 @@ static int merge_entry(int pos, const char *path)
 {
        int found;
        const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
-       char hexbuf[4][60];
+       char hexbuf[4][GIT_SHA1_HEXSZ + 1];
        char ownbuf[4][60];
 
        if (pos >= active_nr)
@@ -22,8 +22,8 @@ static int merge_entry(int pos, const char *path)
                if (strcmp(ce->name, path))
                        break;
                found++;
-               strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
-               sprintf(ownbuf[stage], "%o", ce->ce_mode);
+               sha1_to_hex_r(hexbuf[stage], ce->sha1);
+               xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode);
                arguments[stage] = hexbuf[stage];
                arguments[stage + 4] = ownbuf[stage];
        } while (++pos < active_nr);
index a90f28f34d2072b7d40964a7fa3cf46d3bb98cdd..491efd556e87d0b263bb1029e0cabb9b7f6f1472 100644 (file)
@@ -14,7 +14,7 @@ static const char *better_branch_name(const char *branch)
 
        if (strlen(branch) != 40)
                return branch;
-       sprintf(githead_env, "GITHEAD_%s", branch);
+       xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
        name = getenv(githead_env);
        return name ? name : branch;
 }
index e6741f3380d30adaf7b5a16688a4ccb3e6d6dd99..bbf3110f88ae118065da4994de63f345cf414ba7 100644 (file)
@@ -1019,7 +1019,7 @@ static struct commit_list *reduce_parents(struct commit *head_commit,
                                          int *head_subsumed,
                                          struct commit_list *remoteheads)
 {
-       struct commit_list *parents, *next, **remotes = &remoteheads;
+       struct commit_list *parents, **remotes;
 
        /*
         * Is the current HEAD reachable from another commit being
@@ -1033,16 +1033,14 @@ static struct commit_list *reduce_parents(struct commit *head_commit,
        /* Find what parents to record by checking independent ones. */
        parents = reduce_heads(remoteheads);
 
-       for (remoteheads = NULL, remotes = &remoteheads;
-            parents;
-            parents = next) {
-               struct commit *commit = parents->item;
-               next = parents->next;
+       remoteheads = NULL;
+       remotes = &remoteheads;
+       while (parents) {
+               struct commit *commit = pop_commit(&parents);
                if (commit == head_commit)
                        *head_subsumed = 0;
                else
                        remotes = &commit_list_insert(commit, remotes)->next;
-               free(parents);
        }
        return remoteheads;
 }
@@ -1319,13 +1317,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (verify_signatures) {
                for (p = remoteheads; p; p = p->next) {
                        struct commit *commit = p->item;
-                       char hex[41];
+                       char hex[GIT_SHA1_HEXSZ + 1];
                        struct signature_check signature_check;
                        memset(&signature_check, 0, sizeof(signature_check));
 
                        check_commit_signature(commit, &signature_check);
 
-                       strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+                       find_unique_abbrev_r(hex, commit->object.sha1, DEFAULT_ABBREV);
                        switch (signature_check.result) {
                        case 'G':
                                break;
@@ -1415,15 +1413,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
                struct commit *commit;
-               char hex[41];
 
-               strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
-
-               if (verbosity >= 0)
-                       printf(_("Updating %s..%s\n"),
-                               hex,
-                               find_unique_abbrev(remoteheads->item->object.sha1,
-                               DEFAULT_ABBREV));
+               if (verbosity >= 0) {
+                       char from[GIT_SHA1_HEXSZ + 1], to[GIT_SHA1_HEXSZ + 1];
+                       find_unique_abbrev_r(from, head_commit->object.sha1,
+                                             DEFAULT_ABBREV);
+                       find_unique_abbrev_r(to, remoteheads->item->object.sha1,
+                                             DEFAULT_ABBREV);
+                       printf(_("Updating %s..%s\n"), from, to);
+               }
                strbuf_addstr(&msg, "Fast-forward");
                if (have_message)
                        strbuf_addstr(&msg,
index 248a3eb260cfa22a11d4b1b5420c42bc608f13c5..0377fc11428b8148b508658547987ea8954723e6 100644 (file)
@@ -55,20 +55,16 @@ static void name_rev(struct commit *commit,
                        parents;
                        parents = parents->next, parent_number++) {
                if (parent_number > 1) {
-                       int len = strlen(tip_name);
-                       char *new_name = xmalloc(len +
-                               1 + decimal_length(generation) +  /* ~<n> */
-                               1 + 2 +                           /* ^NN */
-                               1);
-
-                       if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
-                               len -= 2;
+                       size_t len;
+                       char *new_name;
+
+                       strip_suffix(tip_name, "^0", &len);
                        if (generation > 0)
-                               sprintf(new_name, "%.*s~%d^%d", len, tip_name,
-                                               generation, parent_number);
+                               new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name,
+                                                  generation, parent_number);
                        else
-                               sprintf(new_name, "%.*s^%d", len, tip_name,
-                                               parent_number);
+                               new_name = xstrfmt("%.*s^%d", (int)len, tip_name,
+                                                  parent_number);
 
                        name_rev(parents->item, new_name, 0,
                                distance + MERGE_TRAVERSAL_WEIGHT, 0);
index bb23d5501c9a00818b880217d748c5fd1bcfe5cc..515cebbeb8a33eb982a1ddccdc9d69e09beefb1d 100644 (file)
@@ -19,7 +19,7 @@
 #include "string-list.h"
 #include "notes-merge.h"
 #include "notes-utils.h"
-#include "branch.h"
+#include "worktree.h"
 
 static const char * const git_notes_usage[] = {
        N_("git notes [--ref <notes-ref>] [list [<object>]]"),
index 2379e11069ef223a82580a9451ede8fbdb1eb8d9..8c693e756852ef2ec3aac3bfc77d71c6258a8306 100644 (file)
@@ -90,7 +90,7 @@ static int debug_merge(const struct cache_entry * const *stages,
        debug_stage("index", stages[0], o);
        for (i = 1; i <= o->merge_size; i++) {
                char buf[24];
-               sprintf(buf, "ent#%d", i);
+               xsnprintf(buf, sizeof(buf), "ent#%d", i);
                debug_stage(buf, stages[i], o);
        }
        return 0;
index e6b93d026478dce208ab4c267d9b6e67fa856c8d..bcb624bc054833cd8f4d15e47ee96b31fa79f754 100644 (file)
@@ -280,10 +280,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2
 
 static void report_message(const char *prefix, const char *err, va_list params)
 {
-       int sz = strlen(prefix);
+       int sz;
        char msg[4096];
 
-       strncpy(msg, prefix, sz);
+       sz = xsnprintf(msg, sizeof(msg), "%s", prefix);
        sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
        if (sz > (sizeof(msg) - 1))
                sz = sizeof(msg) - 1;
@@ -1071,8 +1071,11 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
-       unsigned char sha1[20];
-       char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
+       unsigned char sha1[GIT_SHA1_RAWSZ];
+       char cmd_oldh[GIT_SHA1_HEXSZ + 1],
+            cmd_newh[GIT_SHA1_HEXSZ + 1],
+            dst_oldh[GIT_SHA1_HEXSZ + 1],
+            dst_newh[GIT_SHA1_HEXSZ + 1];
        int flag;
 
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
@@ -1103,10 +1106,10 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
 
        dst_cmd->skip_update = 1;
 
-       strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV));
-       strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV));
-       strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV));
-       strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
+       find_unique_abbrev_r(cmd_oldh, cmd->old_sha1, DEFAULT_ABBREV);
+       find_unique_abbrev_r(cmd_newh, cmd->new_sha1, DEFAULT_ABBREV);
+       find_unique_abbrev_r(dst_oldh, dst_cmd->old_sha1, DEFAULT_ABBREV);
+       find_unique_abbrev_r(dst_newh, dst_cmd->new_sha1, DEFAULT_ABBREV);
        rp_error("refusing inconsistent update between symref '%s' (%s..%s) and"
                 " its target '%s' (%s..%s)",
                 cmd->ref_name, cmd_oldh, cmd_newh,
@@ -1521,15 +1524,18 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                if (status)
                        return "unpack-objects abnormal exit";
        } else {
-               int s;
-               char keep_arg[256];
-
-               s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
-               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                       strcpy(keep_arg + s, "localhost");
+               char hostname[256];
 
                argv_array_pushl(&child.args, "index-pack",
-                                "--stdin", hdr_arg, keep_arg, NULL);
+                                "--stdin", hdr_arg, NULL);
+
+               if (gethostname(hostname, sizeof(hostname)))
+                       xsnprintf(hostname, sizeof(hostname), "localhost");
+               argv_array_pushf(&child.args,
+                                "--keep=receive-pack %"PRIuMAX" on %s",
+                                (uintmax_t)getpid(),
+                                hostname);
+
                if (fsck_objects)
                        argv_array_pushf(&child.args, "--strict%s",
                                fsck_msg_types.buf);
index f96ca2a27dcb9ac6daa3aa110ff657981296502a..cf1145e635c559206a6b0d61e41321fa914e5e42 100644 (file)
@@ -218,7 +218,6 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
  */
 static void mark_reachable(struct expire_reflog_policy_cb *cb)
 {
-       struct commit *commit;
        struct commit_list *pending;
        unsigned long expire_limit = cb->mark_limit;
        struct commit_list *leftover = NULL;
@@ -228,11 +227,8 @@ static void mark_reachable(struct expire_reflog_policy_cb *cb)
 
        pending = cb->mark_list;
        while (pending) {
-               struct commit_list *entry = pending;
                struct commit_list *parent;
-               pending = entry->next;
-               commit = entry->item;
-               free(entry);
+               struct commit *commit = pop_commit(&pending);
                if (commit->object.flags & REACHABLE)
                        continue;
                if (parse_commit(commit))
index 3b8c22cc75ed04eaf450f9d5a1e9e71b914339eb..e3cd25d580b0f1ee14b4a5037ceae8dc20b31f30 100644 (file)
@@ -1,6 +1,7 @@
 #include "builtin.h"
 #include "transport.h"
 #include "run-command.h"
+#include "pkt-line.h"
 
 /*
  * URL syntax:
@@ -142,36 +143,11 @@ static const char **parse_argv(const char *arg, const char *service)
 static void send_git_request(int stdin_fd, const char *serv, const char *repo,
        const char *vhost)
 {
-       size_t bufferspace;
-       size_t wpos = 0;
-       char *buffer;
-
-       /*
-        * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
-        * 6 bytes extra (xxxx \0) if there is no vhost.
-        */
-       if (vhost)
-               bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+       if (!vhost)
+               packet_write(stdin_fd, "%s %s%c", serv, repo, 0);
        else
-               bufferspace = strlen(serv) + strlen(repo) + 6;
-
-       if (bufferspace > 0xFFFF)
-               die("Request too large to send");
-       buffer = xmalloc(bufferspace);
-
-       /* Make the packet. */
-       wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
-               serv, repo, 0);
-
-       /* Add vhost if any. */
-       if (vhost)
-               sprintf(buffer + wpos, "host=%s%c", vhost, 0);
-
-       /* Send the request */
-       if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
-               die_errno("Failed to send request");
-
-       free(buffer);
+               packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
+                            vhost, 0);
 }
 
 static int run_child(const char *arg, const char *service)
index 181668dedddef9bf79ab91d9740607cf31115a16..e4c3ea130c98b01f87bed617a6386b03158b42d5 100644 (file)
@@ -18,6 +18,7 @@ static const char * const builtin_remote_usage[] = {
        N_("git remote prune [-n | --dry-run] <name>"),
        N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"),
        N_("git remote set-branches [--add] <name> <branch>..."),
+       N_("git remote get-url [--push] [--all] <name>"),
        N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
        N_("git remote set-url --add <name> <newurl>"),
        N_("git remote set-url --delete <name> <url>"),
@@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = {
        NULL
 };
 
+static const char * const builtin_remote_geturl_usage[] = {
+       N_("git remote get-url [--push] [--all] <name>"),
+       NULL
+};
+
 static const char * const builtin_remote_seturl_usage[] = {
        N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
        N_("git remote set-url --add <name> <newurl>"),
@@ -1467,6 +1473,57 @@ static int set_branches(int argc, const char **argv)
        return set_remote_branches(argv[0], argv + 1, add_mode);
 }
 
+static int get_url(int argc, const char **argv)
+{
+       int i, push_mode = 0, all_mode = 0;
+       const char *remotename = NULL;
+       struct remote *remote;
+       const char **url;
+       int url_nr;
+       struct option options[] = {
+               OPT_BOOL('\0', "push", &push_mode,
+                        N_("query push URLs rather than fetch URLs")),
+               OPT_BOOL('\0', "all", &all_mode,
+                        N_("return all URLs")),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0);
+
+       if (argc != 1)
+               usage_with_options(builtin_remote_geturl_usage, options);
+
+       remotename = argv[0];
+
+       if (!remote_is_configured(remotename))
+               die(_("No such remote '%s'"), remotename);
+       remote = remote_get(remotename);
+
+       url_nr = 0;
+       if (push_mode) {
+               url = remote->pushurl;
+               url_nr = remote->pushurl_nr;
+       }
+       /* else fetch mode */
+
+       /* Use the fetch URL when no push URLs were found or requested. */
+       if (!url_nr) {
+               url = remote->url;
+               url_nr = remote->url_nr;
+       }
+
+       if (!url_nr)
+               die(_("no URLs configured for remote '%s'"), remotename);
+
+       if (all_mode) {
+               for (i = 0; i < url_nr; i++)
+                       printf_ln("%s", url[i]);
+       } else {
+               printf_ln("%s", *url);
+       }
+
+       return 0;
+}
+
 static int set_url(int argc, const char **argv)
 {
        int i, push_mode = 0, add_mode = 0, delete_mode = 0;
@@ -1576,6 +1633,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = set_head(argc, argv);
        else if (!strcmp(argv[0], "set-branches"))
                result = set_branches(argc, argv);
+       else if (!strcmp(argv[0], "get-url"))
+               result = get_url(argc, argv);
        else if (!strcmp(argv[0], "set-url"))
                result = set_url(argc, argv);
        else if (!strcmp(argv[0], "show"))
index 88e1359ebcaad338d96dd942a5f88f54d6cce271..1bf72423bf72d7b7d224c14808b0652d78305d63 100644 (file)
@@ -104,9 +104,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                        return 0;
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
-                       const char *name = (const char *)merge_rr.items[i].util;
-                       if (diff_two(rerere_path(name, "preimage"), path, path, path))
-                               die("unable to generate diff for %s", name);
+                       const struct rerere_id *id = merge_rr.items[i].util;
+                       if (diff_two(rerere_path(id, "preimage"), path, path, path))
+                               die("unable to generate diff for %s", rerere_path(id, NULL));
                }
        } else
                usage_with_options(rerere_usage, options);
index d80d1ed35944144aa0aeeeb1b0e08c9efdec6523..491d298fa25cebb1afd9b33cbbd099efe72629bc 100644 (file)
@@ -217,7 +217,7 @@ static void print_var_int(const char *var, int val)
 static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
 {
        int cnt, flags = info->flags;
-       char hex[41] = "";
+       char hex[GIT_SHA1_HEXSZ + 1] = "";
        struct commit_list *tried;
        struct rev_info *revs = info->revs;
 
@@ -242,7 +242,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
                cnt = reaches;
 
        if (revs->commits)
-               strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+               sha1_to_hex_r(hex, revs->commits->item->object.sha1);
 
        if (flags & BISECT_SHOW_ALL) {
                traverse_commit_list(revs, show_commit, show_object, info);
index 02d747dcb1a318af98ec06a7ab2f4e489e7c2625..e92a782f7729f0e5522969238920be2b7e607366 100644 (file)
@@ -281,11 +281,8 @@ static int try_difference(const char *arg)
                        b = lookup_commit_reference(end);
                        exclude = get_merge_bases(a, b);
                        while (exclude) {
-                               struct commit_list *n = exclude->next;
-                               show_rev(REVERSED,
-                                        exclude->item->object.sha1,NULL);
-                               free(exclude);
-                               exclude = n;
+                               struct commit *commit = pop_commit(&exclude);
+                               show_rev(REVERSED, commit->object.sha1, NULL);
                        }
                }
                *dotdot = '.';
index 408ce7030731f765228de93066a815bd4db59e30..e17744bc0dad37b75865b5da8c8c3037bcc02167 100644 (file)
@@ -3,6 +3,7 @@
 #include "refs.h"
 #include "builtin.h"
 #include "color.h"
+#include "argv-array.h"
 #include "parse-options.h"
 
 static const char* show_branch_usage[] = {
@@ -16,9 +17,7 @@ static const char* show_branch_usage[] = {
 
 static int showbranch_use_color = -1;
 
-static int default_num;
-static int default_alloc;
-static const char **default_arg;
+static struct argv_array default_args = ARGV_ARRAY_INIT;
 
 #define UNINTERESTING  01
 
@@ -53,17 +52,6 @@ static struct commit *interesting(struct commit_list *list)
        return NULL;
 }
 
-static struct commit *pop_one_commit(struct commit_list **list_p)
-{
-       struct commit *commit;
-       struct commit_list *list;
-       list = *list_p;
-       commit = list->item;
-       *list_p = list->next;
-       free(list);
-       return commit;
-}
-
 struct commit_name {
        const char *head_name; /* which head's ancestor? */
        int generation; /* how many parents away from head_name */
@@ -213,7 +201,7 @@ static void join_revs(struct commit_list **list_p,
        while (*list_p) {
                struct commit_list *parents;
                int still_interesting = !!interesting(*list_p);
-               struct commit *commit = pop_one_commit(list_p);
+               struct commit *commit = pop_commit(list_p);
                int flags = commit->object.flags & all_mask;
 
                if (!still_interesting && extra <= 0)
@@ -504,7 +492,7 @@ static int show_merge_base(struct commit_list *seen, int num_rev)
        int exit_status = 1;
 
        while (seen) {
-               struct commit *commit = pop_one_commit(&seen);
+               struct commit *commit = pop_commit(&seen);
                int flags = commit->object.flags & all_mask;
                if (!(flags & UNINTERESTING) &&
                    ((flags & all_revs) == all_revs)) {
@@ -567,16 +555,9 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
                 * default_arg is now passed to parse_options(), so we need to
                 * mimic the real argv a bit better.
                 */
-               if (!default_num) {
-                       default_alloc = 20;
-                       default_arg = xcalloc(default_alloc, sizeof(*default_arg));
-                       default_arg[default_num++] = "show-branch";
-               } else if (default_alloc <= default_num + 1) {
-                       default_alloc = default_alloc * 3 / 2 + 20;
-                       REALLOC_ARRAY(default_arg, default_alloc);
-               }
-               default_arg[default_num++] = xstrdup(value);
-               default_arg[default_num] = NULL;
+               if (!default_args.argc)
+                       argv_array_push(&default_args, "show-branch");
+               argv_array_push(&default_args, value);
                return 0;
        }
 
@@ -696,9 +677,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        git_config(git_show_branch_config, NULL);
 
        /* If nothing is specified, try the default first */
-       if (ac == 1 && default_num) {
-               ac = default_num;
-               av = default_arg;
+       if (ac == 1 && default_args.argc) {
+               ac = default_args.argc;
+               av = default_args.argv;
        }
 
        ac = parse_options(ac, av, prefix, builtin_show_branch_options,
@@ -743,6 +724,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        fake_av[1] = NULL;
                        av = fake_av;
                        ac = 1;
+                       if (!*av)
+                               die("no branches given, and HEAD is not valid");
                }
                if (ac != 1)
                        die("--reflog option needs one branch name");
@@ -927,7 +910,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
 
        while (seen) {
-               struct commit *commit = pop_one_commit(&seen);
+               struct commit *commit = pop_commit(&seen);
                int this_flag = commit->object.flags;
                int is_merge_point = ((this_flag & all_revs) == all_revs);
 
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
new file mode 100644 (file)
index 0000000..f4c3eff
--- /dev/null
@@ -0,0 +1,282 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "dir.h"
+#include "utf8.h"
+#include "submodule.h"
+#include "submodule-config.h"
+#include "string-list.h"
+#include "run-command.h"
+
+struct module_list {
+       const struct cache_entry **entries;
+       int alloc, nr;
+};
+#define MODULE_LIST_INIT { NULL, 0, 0 }
+
+static int module_list_compute(int argc, const char **argv,
+                              const char *prefix,
+                              struct pathspec *pathspec,
+                              struct module_list *list)
+{
+       int i, result = 0;
+       char *max_prefix, *ps_matched = NULL;
+       int max_prefix_len;
+       parse_pathspec(pathspec, 0,
+                      PATHSPEC_PREFER_FULL |
+                      PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+                      prefix, argv);
+
+       /* Find common prefix for all pathspec's */
+       max_prefix = common_prefix(pathspec);
+       max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
+
+       if (pathspec->nr)
+               ps_matched = xcalloc(pathspec->nr, 1);
+
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
+
+       for (i = 0; i < active_nr; i++) {
+               const struct cache_entry *ce = active_cache[i];
+
+               if (!S_ISGITLINK(ce->ce_mode) ||
+                   !match_pathspec(pathspec, ce->name, ce_namelen(ce),
+                                   max_prefix_len, ps_matched, 1))
+                       continue;
+
+               ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+               list->entries[list->nr++] = ce;
+               while (i + 1 < active_nr &&
+                      !strcmp(ce->name, active_cache[i + 1]->name))
+                       /*
+                        * Skip entries with the same name in different stages
+                        * to make sure an entry is returned only once.
+                        */
+                       i++;
+       }
+       free(max_prefix);
+
+       if (ps_matched && report_path_error(ps_matched, pathspec, prefix))
+               result = -1;
+
+       free(ps_matched);
+
+       return result;
+}
+
+static int module_list(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+
+       struct option module_list_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper list [--prefix=<path>] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_list_options,
+                            git_submodule_helper_usage, 0);
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) {
+               printf("#unmatched\n");
+               return 1;
+       }
+
+       for (i = 0; i < list.nr; i++) {
+               const struct cache_entry *ce = list.entries[i];
+
+               if (ce_stage(ce))
+                       printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1));
+               else
+                       printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce));
+
+               utf8_fprintf(stdout, "%s\n", ce->name);
+       }
+       return 0;
+}
+
+static int module_name(int argc, const char **argv, const char *prefix)
+{
+       const struct submodule *sub;
+
+       if (argc != 2)
+               usage(_("git submodule--helper name <path>"));
+
+       gitmodules_config();
+       sub = submodule_from_path(null_sha1, argv[1]);
+
+       if (!sub)
+               die(_("no submodule mapping found in .gitmodules for path '%s'"),
+                   argv[1]);
+
+       printf("%s\n", sub->name);
+
+       return 0;
+}
+static int clone_submodule(const char *path, const char *gitdir, const char *url,
+                          const char *depth, const char *reference, int quiet)
+{
+       struct child_process cp;
+       child_process_init(&cp);
+
+       argv_array_push(&cp.args, "clone");
+       argv_array_push(&cp.args, "--no-checkout");
+       if (quiet)
+               argv_array_push(&cp.args, "--quiet");
+       if (depth && *depth)
+               argv_array_pushl(&cp.args, "--depth", depth, NULL);
+       if (reference && *reference)
+               argv_array_pushl(&cp.args, "--reference", reference, NULL);
+       if (gitdir && *gitdir)
+               argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+
+       argv_array_push(&cp.args, url);
+       argv_array_push(&cp.args, path);
+
+       cp.git_cmd = 1;
+       cp.env = local_repo_env;
+       cp.no_stdin = 1;
+
+       return run_command(&cp);
+}
+
+static int module_clone(int argc, const char **argv, const char *prefix)
+{
+       const char *path = NULL, *name = NULL, *url = NULL;
+       const char *reference = NULL, *depth = NULL;
+       int quiet = 0;
+       FILE *submodule_dot_git;
+       char *sm_gitdir, *cwd, *p;
+       struct strbuf rel_path = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT;
+
+       struct option module_clone_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT_STRING(0, "path", &path,
+                          N_("path"),
+                          N_("where the new submodule will be cloned to")),
+               OPT_STRING(0, "name", &name,
+                          N_("string"),
+                          N_("name of the new submodule")),
+               OPT_STRING(0, "url", &url,
+                          N_("string"),
+                          N_("url where to clone the submodule from")),
+               OPT_STRING(0, "reference", &reference,
+                          N_("string"),
+                          N_("reference repository")),
+               OPT_STRING(0, "depth", &depth,
+                          N_("string"),
+                          N_("depth for shallow clones")),
+               OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
+                  "[--reference <repository>] [--name <name>] [--url <url>]"
+                  "[--depth <depth>] [--] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_clone_options,
+                            git_submodule_helper_usage, 0);
+
+       strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
+       sm_gitdir = strbuf_detach(&sb, NULL);
+
+       if (!file_exists(sm_gitdir)) {
+               if (safe_create_leading_directories_const(sm_gitdir) < 0)
+                       die(_("could not create directory '%s'"), sm_gitdir);
+               if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet))
+                       die(_("clone of '%s' into submodule path '%s' failed"),
+                           url, path);
+       } else {
+               if (safe_create_leading_directories_const(path) < 0)
+                       die(_("could not create directory '%s'"), path);
+               strbuf_addf(&sb, "%s/index", sm_gitdir);
+               unlink_or_warn(sb.buf);
+               strbuf_reset(&sb);
+       }
+
+       /* Write a .git file in the submodule to redirect to the superproject. */
+       if (safe_create_leading_directories_const(path) < 0)
+               die(_("could not create directory '%s'"), path);
+
+       if (path && *path)
+               strbuf_addf(&sb, "%s/.git", path);
+       else
+               strbuf_addstr(&sb, ".git");
+
+       if (safe_create_leading_directories_const(sb.buf) < 0)
+               die(_("could not create leading directories of '%s'"), sb.buf);
+       submodule_dot_git = fopen(sb.buf, "w");
+       if (!submodule_dot_git)
+               die_errno(_("cannot open file '%s'"), sb.buf);
+
+       fprintf(submodule_dot_git, "gitdir: %s\n",
+               relative_path(sm_gitdir, path, &rel_path));
+       if (fclose(submodule_dot_git))
+               die(_("could not close file %s"), sb.buf);
+       strbuf_reset(&sb);
+       strbuf_reset(&rel_path);
+
+       cwd = xgetcwd();
+       /* Redirect the worktree of the submodule in the superproject's config */
+       if (!is_absolute_path(sm_gitdir)) {
+               strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir);
+               free(sm_gitdir);
+               sm_gitdir = strbuf_detach(&sb, NULL);
+       }
+
+       strbuf_addf(&sb, "%s/%s", cwd, path);
+       p = git_pathdup_submodule(path, "config");
+       if (!p)
+               die(_("could not get submodule directory for '%s'"), path);
+       git_config_set_in_file(p, "core.worktree",
+                              relative_path(sb.buf, sm_gitdir, &rel_path));
+       strbuf_release(&sb);
+       strbuf_release(&rel_path);
+       free(sm_gitdir);
+       free(cwd);
+       free(p);
+       return 0;
+}
+
+struct cmd_struct {
+       const char *cmd;
+       int (*fn)(int, const char **, const char *);
+};
+
+static struct cmd_struct commands[] = {
+       {"list", module_list},
+       {"name", module_name},
+       {"clone", module_clone},
+};
+
+int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       if (argc < 2)
+               die(_("fatal: submodule--helper subcommand must be "
+                     "called with a subcommand"));
+
+       for (i = 0; i < ARRAY_SIZE(commands); i++)
+               if (!strcmp(argv[1], commands[i].cmd))
+                       return commands[i].fn(argc - 1, argv + 1, prefix);
+
+       die(_("fatal: '%s' is not a valid submodule--helper "
+             "subcommand"), argv[1]);
+}
index f048cae0e0593866105574e685b97eb71023fbd2..8db8c87e57ef05edadce8de19316572498c40753 100644 (file)
 #include "gpg-interface.h"
 #include "sha1-array.h"
 #include "column.h"
+#include "ref-filter.h"
 
 static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-               "\n\t\t[<pattern>...]"),
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
 };
 
-#define STRCMP_SORT     0      /* must be zero */
-#define VERCMP_SORT     1
-#define SORT_MASK       0x7fff
-#define REVERSE_SORT    0x8000
-
-static int tag_sort;
-
-struct tag_filter {
-       const char **patterns;
-       int lines;
-       int sort;
-       struct string_list tags;
-       struct commit_list *with_commit;
-};
-
-static struct sha1_array points_at;
 static unsigned int colopts;
 
-static int match_pattern(const char **patterns, const char *ref)
-{
-       /* no pattern means match everything */
-       if (!*patterns)
-               return 1;
-       for (; *patterns; patterns++)
-               if (!wildmatch(*patterns, ref, 0, NULL))
-                       return 1;
-       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)
-               if (!hashcmp(want->item->object.sha1, c->object.sha1))
-                       return 1;
-       return 0;
-}
-
-enum contains_result {
-       CONTAINS_UNKNOWN = -1,
-       CONTAINS_NO = 0,
-       CONTAINS_YES = 1
-};
-
-/*
- * Test whether the candidate or one of its parents is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
- */
-static enum contains_result contains_test(struct commit *candidate,
-                           const struct commit_list *want)
-{
-       /* was it previously marked as containing a want commit? */
-       if (candidate->object.flags & TMP_MARK)
-               return 1;
-       /* or marked as not possibly containing a want commit? */
-       if (candidate->object.flags & UNINTERESTING)
-               return 0;
-       /* or are we it? */
-       if (in_commit_list(want, candidate)) {
-               candidate->object.flags |= TMP_MARK;
-               return 1;
-       }
-
-       if (parse_commit(candidate) < 0)
-               return 0;
-
-       return -1;
-}
-
-/*
- * Mimicking the real stack, this stack lives on the heap, avoiding stack
- * overflows.
- *
- * At each recursion step, the stack items points to the commits whose
- * ancestors are to be inspected.
- */
-struct stack {
-       int nr, alloc;
-       struct stack_entry {
-               struct commit *commit;
-               struct commit_list *parents;
-       } *stack;
-};
-
-static void push_to_stack(struct commit *candidate, struct stack *stack)
-{
-       int index = stack->nr++;
-       ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
-       stack->stack[index].commit = candidate;
-       stack->stack[index].parents = candidate->parents;
-}
-
-static enum contains_result contains(struct commit *candidate,
-               const struct commit_list *want)
-{
-       struct stack stack = { 0, 0, NULL };
-       int result = contains_test(candidate, want);
-
-       if (result != CONTAINS_UNKNOWN)
-               return result;
-
-       push_to_stack(candidate, &stack);
-       while (stack.nr) {
-               struct stack_entry *entry = &stack.stack[stack.nr - 1];
-               struct commit *commit = entry->commit;
-               struct commit_list *parents = entry->parents;
-
-               if (!parents) {
-                       commit->object.flags |= UNINTERESTING;
-                       stack.nr--;
-               }
-               /*
-                * If we just popped the stack, parents->item has been marked,
-                * therefore contains_test will return a meaningful 0 or 1.
-                */
-               else switch (contains_test(parents->item, want)) {
-               case CONTAINS_YES:
-                       commit->object.flags |= TMP_MARK;
-                       stack.nr--;
-                       break;
-               case CONTAINS_NO:
-                       entry->parents = parents->next;
-                       break;
-               case CONTAINS_UNKNOWN:
-                       push_to_stack(parents->item, &stack);
-                       break;
-               }
-       }
-       free(stack.stack);
-       return contains_test(candidate, want);
-}
-
-static void show_tag_lines(const struct object_id *oid, int lines)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
+       struct ref_array array;
+       char *to_free = NULL;
        int i;
-       unsigned long size;
-       enum object_type type;
-       char *buf, *sp, *eol;
-       size_t len;
-
-       buf = read_sha1_file(oid->hash, &type, &size);
-       if (!buf)
-               die_errno("unable to read object %s", oid_to_hex(oid));
-       if (type != OBJ_COMMIT && type != OBJ_TAG)
-               goto free_return;
-       if (!size)
-               die("an empty %s object %s?",
-                   typename(type), oid_to_hex(oid));
-
-       /* skip header */
-       sp = strstr(buf, "\n\n");
-       if (!sp)
-               goto free_return;
-
-       /* only take up to "lines" lines, and strip the signature from a tag */
-       if (type == OBJ_TAG)
-               size = parse_signature(buf, size);
-       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
-               if (i)
-                       printf("\n    ");
-               eol = memchr(sp, '\n', size - (sp - buf));
-               len = eol ? eol - sp : size - (sp - buf);
-               fwrite(sp, len, 1, stdout);
-               if (!eol)
-                       break;
-               sp = eol + 1;
-       }
-free_return:
-       free(buf);
-}
-
-static int show_reference(const char *refname, const struct object_id *oid,
-                         int flag, void *cb_data)
-{
-       struct tag_filter *filter = cb_data;
 
-       if (match_pattern(filter->patterns, refname)) {
-               if (filter->with_commit) {
-                       struct commit *commit;
+       memset(&array, 0, sizeof(array));
 
-                       commit = lookup_commit_reference_gently(oid->hash, 1);
-                       if (!commit)
-                               return 0;
-                       if (!contains(commit, filter->with_commit))
-                               return 0;
-               }
-
-               if (points_at.nr && !match_points_at(refname, oid->hash))
-                       return 0;
+       if (filter->lines == -1)
+               filter->lines = 0;
 
-               if (!filter->lines) {
-                       if (filter->sort)
-                               string_list_append(&filter->tags, refname);
-                       else
-                               printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-               show_tag_lines(oid, filter->lines);
-               putchar('\n');
+       if (!format) {
+               if (filter->lines) {
+                       to_free = xstrfmt("%s %%(contents:lines=%d)",
+                                         "%(align:15)%(refname:short)%(end)",
+                                         filter->lines);
+                       format = to_free;
+               } else
+                       format = "%(refname:short)";
        }
 
-       return 0;
-}
+       verify_ref_format(format);
+       filter->with_commit_tag_algo = 1;
+       filter_refs(&array, filter, FILTER_REFS_TAGS);
+       ref_array_sort(sorting, &array);
 
-static int sort_by_version(const void *a_, const void *b_)
-{
-       const struct string_list_item *a = a_;
-       const struct string_list_item *b = b_;
-       return versioncmp(a->string, b->string);
-}
+       for (i = 0; i < array.nr; i++)
+               show_ref_array_item(array.items[i], format, 0);
+       ref_array_clear(&array);
+       free(to_free);
 
-static int list_tags(const char **patterns, int lines,
-                    struct commit_list *with_commit, int sort)
-{
-       struct tag_filter filter;
-
-       filter.patterns = patterns;
-       filter.lines = lines;
-       filter.sort = sort;
-       filter.with_commit = with_commit;
-       memset(&filter.tags, 0, sizeof(filter.tags));
-       filter.tags.strdup_strings = 1;
-
-       for_each_tag_ref(show_reference, (void *)&filter);
-       if (sort) {
-               int i;
-               if ((sort & SORT_MASK) == VERCMP_SORT)
-                       qsort(filter.tags.items, filter.tags.nr,
-                             sizeof(struct string_list_item), sort_by_version);
-               if (sort & REVERSE_SORT)
-                       for (i = filter.tags.nr - 1; i >= 0; i--)
-                               printf("%s\n", filter.tags.items[i].string);
-               else
-                       for (i = 0; i < filter.tags.nr; i++)
-                               printf("%s\n", filter.tags.items[i].string);
-               string_list_clear(&filter.tags, 0);
-       }
        return 0;
 }
 
@@ -348,35 +127,26 @@ static const char tag_template_nocleanup[] =
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
 
-/*
- * Parse a sort string, and return 0 if parsed successfully. Will return
- * non-zero when the sort string does not parse into a known type. If var is
- * given, the error message becomes a warning and includes information about
- * the configuration value.
- */
-static int parse_sort_string(const char *var, const char *arg, int *sort)
+/* Parse arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
 {
-       int type = 0, flags = 0;
+       struct ref_sorting *s;
+       int len;
 
-       if (skip_prefix(arg, "-", &arg))
-               flags |= REVERSE_SORT;
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
 
-       if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
-               type = VERCMP_SORT;
-       else
-               type = STRCMP_SORT;
-
-       if (strcmp(arg, "refname")) {
-               if (!var)
-                       return error(_("unsupported sort specification '%s'"), arg);
-               else {
-                       warning(_("unsupported sort specification '%s' in variable '%s'"),
-                               var, arg);
-                       return -1;
-               }
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
 
-       *sort = (type | flags);
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
 
        return 0;
 }
@@ -384,11 +154,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
        int status;
+       struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
 
        if (!strcmp(var, "tag.sort")) {
                if (!value)
                        return config_error_nonbool(var);
-               parse_sort_string(var, value, &tag_sort);
+               parse_sorting_string(value, sorting_tail);
                return 0;
        }
 
@@ -546,30 +317,6 @@ 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;
-}
-
-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
-{
-       int *sort = opt->value;
-
-       return parse_sort_string(NULL, arg, sort);
-}
-
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -578,17 +325,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        const char *object_ref, *tag;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
-       int annotate = 0, force = 0, lines = -1;
        int create_reflog = 0;
+       int annotate = 0, force = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
-       struct commit_list *with_commit = NULL;
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+       const char *format = NULL;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
-               { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+               { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
@@ -610,32 +359,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_MERGED(&filter, N_("print only tags that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
                {
-                       OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
-                       PARSE_OPT_NONEG, parse_opt_sort
-               },
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
-                       N_("print only tags of the object"), 0, parse_opt_points_at
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only tags of the object"), 0, parse_opt_object_name
                },
+               OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
 
-       git_config(git_tag_config, NULL);
+       git_config(git_tag_config, sorting_tail);
 
        memset(&opt, 0, sizeof(opt));
+       memset(&filter, 0, sizeof(filter));
+       filter.lines = -1;
 
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
@@ -652,11 +394,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                usage_with_options(git_tag_usage, options);
 
        finalize_colopts(&colopts, -1);
-       if (cmdmode == 'l' && lines != -1) {
+       if (cmdmode == 'l' && filter.lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
+       if (!sorting)
+               sorting = ref_default_sorting();
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
@@ -665,19 +409,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               if (lines != -1 && tag_sort)
-                       die(_("--sort and -n are incompatible"));
-               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+               filter.name_patterns = argv;
+               ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        }
-       if (lines != -1)
+       if (filter.lines != -1)
                die(_("-n option is only allowed with -l."));
-       if (with_commit)
+       if (filter.with_commit)
                die(_("--contains option is only allowed with -l."));
-       if (points_at.nr)
+       if (filter.points_at.nr)
                die(_("--points-at option is only allowed with -l."));
+       if (filter.merge_commit)
+               die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
        if (cmdmode == 'v')
index 19200291a2632554223b1a675d5324b4682248ab..6fc6bcdf7f014a52703fcd44677a705c824bc0b0 100644 (file)
@@ -12,7 +12,7 @@ static char *create_temp_file(unsigned char *sha1)
        if (!buf || type != OBJ_BLOB)
                die("unable to read blob object %s", sha1_to_hex(sha1));
 
-       strcpy(path, ".merge_file_XXXXXX");
+       xsnprintf(path, sizeof(path), ".merge_file_XXXXXX");
        fd = xmkstemp(path);
        if (write_in_full(fd, buf, size) != size)
                die_errno("unable to write temp-file");
index 32ab94cd06b43cd05316f9a16981adc883c0d066..dbfe14f3fec3f8122e4855727bd7d1c3e4b3073e 100644 (file)
@@ -49,15 +49,14 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
 __attribute__((format (printf, 1, 2)))
 static void error_clnt(const char *fmt, ...)
 {
-       char buf[1024];
+       struct strbuf buf = STRBUF_INIT;
        va_list params;
-       int len;
 
        va_start(params, fmt);
-       len = vsprintf(buf, fmt, params);
+       strbuf_vaddf(&buf, fmt, params);
        va_end(params);
-       send_sideband(1, 3, buflen, LARGE_PACKET_MAX);
-       die("sent error to the client: %s", buf);
+       send_sideband(1, 3, buf.buf, buf.len, LARGE_PACKET_MAX);
+       die("sent error to the client: %s", buf.buf);
 }
 
 static ssize_t process_input(int child_fd, int band)
index 33d2d3705afacfb49973141a546f281d871e8eb6..d281f6d887691fb43ebb052091f84e78aabe6d01 100644 (file)
@@ -8,10 +8,13 @@
 #include "run-command.h"
 #include "sigchain.h"
 #include "refs.h"
+#include "utf8.h"
+#include "worktree.h"
 
 static const char * const worktree_usage[] = {
        N_("git worktree add [<options>] <path> [<branch>]"),
        N_("git worktree prune [<options>]"),
+       N_("git worktree list [<options>]"),
        NULL
 };
 
@@ -359,6 +362,89 @@ static int add(int ac, const char **av, const char *prefix)
        return add_worktree(path, branch, &opts);
 }
 
+static void show_worktree_porcelain(struct worktree *wt)
+{
+       printf("worktree %s\n", wt->path);
+       if (wt->is_bare)
+               printf("bare\n");
+       else {
+               printf("HEAD %s\n", sha1_to_hex(wt->head_sha1));
+               if (wt->is_detached)
+                       printf("detached\n");
+               else
+                       printf("branch %s\n", wt->head_ref);
+       }
+       printf("\n");
+}
+
+static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
+{
+       struct strbuf sb = STRBUF_INIT;
+       int cur_path_len = strlen(wt->path);
+       int path_adj = cur_path_len - utf8_strwidth(wt->path);
+
+       strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
+       if (wt->is_bare)
+               strbuf_addstr(&sb, "(bare)");
+       else {
+               strbuf_addf(&sb, "%-*s ", abbrev_len,
+                               find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV));
+               if (!wt->is_detached)
+                       strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0));
+               else
+                       strbuf_addstr(&sb, "(detached HEAD)");
+       }
+       printf("%s\n", sb.buf);
+
+       strbuf_release(&sb);
+}
+
+static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
+{
+       int i;
+
+       for (i = 0; wt[i]; i++) {
+               int sha1_len;
+               int path_len = strlen(wt[i]->path);
+
+               if (path_len > *maxlen)
+                       *maxlen = path_len;
+               sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev));
+               if (sha1_len > *abbrev)
+                       *abbrev = sha1_len;
+       }
+}
+
+static int list(int ac, const char **av, const char *prefix)
+{
+       int porcelain = 0;
+
+       struct option options[] = {
+               OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
+               OPT_END()
+       };
+
+       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+       if (ac)
+               usage_with_options(worktree_usage, options);
+       else {
+               struct worktree **worktrees = get_worktrees();
+               int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
+
+               if (!porcelain)
+                       measure_widths(worktrees, &abbrev, &path_maxlen);
+
+               for (i = 0; worktrees[i]; i++) {
+                       if (porcelain)
+                               show_worktree_porcelain(worktrees[i]);
+                       else
+                               show_worktree(worktrees[i], path_maxlen, abbrev);
+               }
+               free_worktrees(worktrees);
+       }
+       return 0;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
        struct option options[] = {
@@ -371,5 +457,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
                return add(ac - 1, av + 1, prefix);
        if (!strcmp(av[1], "prune"))
                return prune(ac - 1, av + 1, prefix);
+       if (!strcmp(av[1], "list"))
+               return list(ac - 1, av + 1, prefix);
        usage_with_options(worktree_usage, options);
 }
index 7cffc3a579e4a02c1422284092cef18cd4b7d059..4347f5c76aa72873eb5ccb090584428399f3159c 100644 (file)
@@ -200,8 +200,8 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
        if (seekback == (off_t) -1)
                return error("cannot find the current offset");
 
-       header_len = sprintf((char *)obuf, "%s %" PRIuMAX,
-                            typename(type), (uintmax_t)size) + 1;
+       header_len = xsnprintf((char *)obuf, sizeof(obuf), "%s %" PRIuMAX,
+                              typename(type), (uintmax_t)size) + 1;
        git_SHA1_Init(&ctx);
        git_SHA1_Update(&ctx, obuf, header_len);
 
diff --git a/cache.h b/cache.h
index a9aaa0396a893c2669789ea18ee14ecafac965b0..3ba0b8f3d7a86eac8839bb175b0291b76013d263 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -732,6 +732,8 @@ extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
 extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
                                      const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
@@ -792,7 +794,24 @@ extern char *sha1_pack_name(const unsigned char *sha1);
  */
 extern char *sha1_pack_index_name(const unsigned char *sha1);
 
-extern const char *find_unique_abbrev(const unsigned char *sha1, int);
+/*
+ * Return an abbreviated sha1 unique within this repository's object database.
+ * The result will be at least `len` characters long, and will be NUL
+ * terminated.
+ *
+ * The non-`_r` version returns a static buffer which will be overwritten by
+ * subsequent calls.
+ *
+ * The `_r` variant writes to a buffer supplied by the caller, which must be at
+ * least `GIT_SHA1_HEXSZ + 1` bytes. The return value is the number of bytes
+ * written (excluding the NUL terminator).
+ *
+ * Note that while this version avoids the static buffer, it is not fully
+ * reentrant, as it calls into other non-reentrant git code.
+ */
+extern const char *find_unique_abbrev(const unsigned char *sha1, int len);
+extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len);
+
 extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
 
 static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
@@ -1074,6 +1093,18 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern int get_oid_hex(const char *hex, struct object_id *sha1);
 
+/*
+ * Convert a binary sha1 to its hex equivalent. The `_r` variant is reentrant,
+ * and writes the NUL-terminated output to the buffer `out`, which must be at
+ * least `GIT_SHA1_HEXSZ + 1` bytes, and returns a pointer to out for
+ * convenience.
+ *
+ * The non-`_r` variant returns a static buffer, but uses a ring of 4
+ * buffers, making it safe to make multiple calls for a single statement, like:
+ *
+ *   printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two));
+ */
+extern char *sha1_to_hex_r(char *out, const unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern char *oid_to_hex(const struct object_id *oid);  /* same static buffer as sha1_to_hex */
 
@@ -1100,7 +1131,6 @@ struct date_mode {
                DATE_NORMAL = 0,
                DATE_RELATIVE,
                DATE_SHORT,
-               DATE_LOCAL,
                DATE_ISO8601,
                DATE_ISO8601_STRICT,
                DATE_RFC2822,
@@ -1108,6 +1138,7 @@ struct date_mode {
                DATE_RAW
        } type;
        const char *strftime_fmt;
+       int local;
 };
 
 /*
@@ -1288,7 +1319,7 @@ extern void close_all_packs(void);
 extern void unuse_pack(struct pack_window **);
 extern void free_pack_by_name(const char *);
 extern void clear_delta_base_cache(void);
-extern struct packed_git *add_packed_git(const char *, int, int);
+extern struct packed_git *add_packed_git(const char *path, size_t path_len, int local);
 
 /*
  * Return the SHA-1 of the nth object within the specified packfile.
diff --git a/color.c b/color.c
index 9027352ad7786746537adea9007bd46f46edbb4e..8f85153d0d2c1f4bf9340ced9371797eb3c92de9 100644 (file)
--- a/color.c
+++ b/color.c
@@ -145,27 +145,34 @@ int color_parse(const char *value, char *dst)
        return color_parse_mem(value, strlen(value), dst);
 }
 
+void color_set(char *dst, const char *color_bytes)
+{
+       xsnprintf(dst, COLOR_MAXLEN, "%s", color_bytes);
+}
+
 /*
  * Write the ANSI color codes for "c" to "out"; the string should
  * already have the ANSI escape code in it. "out" should have enough
  * space in it to fit any color.
  */
-static char *color_output(char *out, const struct color *c, char type)
+static char *color_output(char *out, int len, const struct color *c, char type)
 {
        switch (c->type) {
        case COLOR_UNSPECIFIED:
        case COLOR_NORMAL:
                break;
        case COLOR_ANSI:
+               if (len < 2)
+                       die("BUG: color parsing ran out of space");
                *out++ = type;
                *out++ = '0' + c->value;
                break;
        case COLOR_256:
-               out += sprintf(out, "%c8;5;%d", type, c->value);
+               out += xsnprintf(out, len, "%c8;5;%d", type, c->value);
                break;
        case COLOR_RGB:
-               out += sprintf(out, "%c8;2;%d;%d;%d", type,
-                              c->red, c->green, c->blue);
+               out += xsnprintf(out, len, "%c8;2;%d;%d;%d", type,
+                                c->red, c->green, c->blue);
                break;
        }
        return out;
@@ -180,12 +187,13 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 {
        const char *ptr = value;
        int len = value_len;
+       char *end = dst + COLOR_MAXLEN;
        unsigned int attr = 0;
        struct color fg = { COLOR_UNSPECIFIED };
        struct color bg = { COLOR_UNSPECIFIED };
 
        if (!strncasecmp(value, "reset", len)) {
-               strcpy(dst, GIT_COLOR_RESET);
+               xsnprintf(dst, end - dst, GIT_COLOR_RESET);
                return 0;
        }
 
@@ -224,12 +232,19 @@ int color_parse_mem(const char *value, int value_len, char *dst)
                        goto bad;
        }
 
+#undef OUT
+#define OUT(x) do { \
+       if (dst == end) \
+               die("BUG: color parsing ran out of space"); \
+       *dst++ = (x); \
+} while(0)
+
        if (attr || !color_empty(&fg) || !color_empty(&bg)) {
                int sep = 0;
                int i;
 
-               *dst++ = '\033';
-               *dst++ = '[';
+               OUT('\033');
+               OUT('[');
 
                for (i = 0; attr; i++) {
                        unsigned bit = (1 << i);
@@ -237,27 +252,28 @@ int color_parse_mem(const char *value, int value_len, char *dst)
                                continue;
                        attr &= ~bit;
                        if (sep++)
-                               *dst++ = ';';
-                       dst += sprintf(dst, "%d", i);
+                               OUT(';');
+                       dst += xsnprintf(dst, end - dst, "%d", i);
                }
                if (!color_empty(&fg)) {
                        if (sep++)
-                               *dst++ = ';';
+                               OUT(';');
                        /* foreground colors are all in the 3x range */
-                       dst = color_output(dst, &fg, '3');
+                       dst = color_output(dst, end - dst, &fg, '3');
                }
                if (!color_empty(&bg)) {
                        if (sep++)
-                               *dst++ = ';';
+                               OUT(';');
                        /* background colors are all in the 4x range */
-                       dst = color_output(dst, &bg, '4');
+                       dst = color_output(dst, end - dst, &bg, '4');
                }
-               *dst++ = 'm';
+               OUT('m');
        }
-       *dst = 0;
+       OUT(0);
        return 0;
 bad:
        return error(_("invalid color value: %.*s"), value_len, value);
+#undef OUT
 }
 
 int git_config_colorbool(const char *var, const char *value)
diff --git a/color.h b/color.h
index 7fe77fb55edac6aa8a284f6c6671c54da79e2591..e155d13f784362a5fdd993e1e383e660fa324c30 100644 (file)
--- a/color.h
+++ b/color.h
@@ -75,6 +75,13 @@ extern int color_stdout_is_tty;
 int git_color_config(const char *var, const char *value, void *cb);
 int git_color_default_config(const char *var, const char *value, void *cb);
 
+/*
+ * Set the color buffer (which must be COLOR_MAXLEN bytes)
+ * to the raw color bytes; this is useful for initializing
+ * default color variables.
+ */
+void color_set(char *dst, const char *color_bytes);
+
 int git_config_colorbool(const char *var, const char *value);
 int want_color(int var);
 int color_parse(const char *value, char *dst);
index 494615d6ff15af5eb95e78053b2799e4c55577fc..d1810c940b3cb354437e86eee38e2c2499375e80 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -455,11 +455,8 @@ struct commit_list *copy_commit_list(struct commit_list *list)
 
 void free_commit_list(struct commit_list *list)
 {
-       while (list) {
-               struct commit_list *temp = list;
-               list = temp->next;
-               free(temp);
-       }
+       while (list)
+               pop_commit(&list);
 }
 
 struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
@@ -505,12 +502,8 @@ void commit_list_sort_by_date(struct commit_list **list)
 struct commit *pop_most_recent_commit(struct commit_list **list,
                                      unsigned int mark)
 {
-       struct commit *ret = (*list)->item;
+       struct commit *ret = pop_commit(list);
        struct commit_list *parents = ret->parents;
-       struct commit_list *old = *list;
-
-       *list = (*list)->next;
-       free(old);
 
        while (parents) {
                struct commit *commit = parents->item;
@@ -861,11 +854,9 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
        list = paint_down_to_common(one, n, twos);
 
        while (list) {
-               struct commit_list *next = list->next;
-               if (!(list->item->object.flags & STALE))
-                       commit_list_insert_by_date(list->item, &result);
-               free(list);
-               list = next;
+               struct commit *commit = pop_commit(&list);
+               if (!(commit->object.flags & STALE))
+                       commit_list_insert_by_date(commit, &result);
        }
        return result;
 }
@@ -1546,13 +1537,9 @@ int commit_tree_extended(const char *msg, size_t msg_len,
         * if everything else stays the same.
         */
        while (parents) {
-               struct commit_list *next = parents->next;
-               struct commit *parent = parents->item;
-
+               struct commit *parent = pop_commit(&parents);
                strbuf_addf(&buffer, "parent %s\n",
                            sha1_to_hex(parent->object.sha1));
-               free(parents);
-               parents = next;
        }
 
        /* Person/date information */
index 069c555da47ea168eea937fcc2d788294bf92ef5..b85a2fa9561f48657bc976a063560ddf7e00d449 100644 (file)
@@ -16,6 +16,6 @@ const char *githstrerror(int err)
        case TRY_AGAIN:
                return "Non-authoritative \"host not found\", or SERVERFAIL";
        }
-       sprintf(buffer, "Name resolution error %d", err);
+       snprintf(buffer, sizeof(buffer), "Name resolution error %d", err);
        return buffer;
 }
index 90b7cc45f33411bbc39cd731ca3e43288cb40b69..68307262be04955993590e32daecc08fcf7eda60 100644 (file)
@@ -53,11 +53,11 @@ inet_ntop4(const u_char *src, char *dst, size_t size)
        nprinted = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
        if (nprinted < 0)
                return (NULL);  /* we assume "errno" was set by "snprintf()" */
-       if ((size_t)nprinted > size) {
+       if ((size_t)nprinted >= size) {
                errno = ENOSPC;
                return (NULL);
        }
-       strcpy(dst, tmp);
+       strlcpy(dst, tmp, size);
        return (dst);
 }
 
@@ -154,7 +154,7 @@ inet_ntop6(const u_char *src, char *dst, size_t size)
                errno = ENOSPC;
                return (NULL);
        }
-       strcpy(dst, tmp);
+       strlcpy(dst, tmp, size);
        return (dst);
 }
 #endif
index f74da235f598d8b0346fac8b48c4155f55ae5b81..90bdb1edde15ccd39055c376f5d920c29af013c0 100644 (file)
@@ -2131,11 +2131,13 @@ void mingw_startup()
 
 int uname(struct utsname *buf)
 {
-       DWORD v = GetVersion();
+       unsigned v = (unsigned)GetVersion();
        memset(buf, 0, sizeof(*buf));
-       strcpy(buf->sysname, "Windows");
-       sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff);
+       xsnprintf(buf->sysname, sizeof(buf->sysname), "Windows");
+       xsnprintf(buf->release, sizeof(buf->release),
+                "%u.%u", v & 0xff, (v >> 8) & 0xff);
        /* assuming NT variants only.. */
-       sprintf(buf->version, "%u", (v >> 16) & 0x7fff);
+       xsnprintf(buf->version, sizeof(buf->version),
+                 "%u", (v >> 16) & 0x7fff);
        return 0;
 }
index 609ebba1258eac47983f2b9da5d582a5d5af5482..a0a16eb1bbfb22c13f29c15d2fd68ccc9c7e01ad 100644 (file)
@@ -957,8 +957,9 @@ char *strdup(const char *s1)
 {
        char *s2 = 0;
        if (s1) {
-               s2 = malloc(strlen(s1) + 1);
-               strcpy(s2, s1);
+               size_t len = strlen(s1) + 1;
+               s2 = malloc(len);
+               memcpy(s2, s1, len);
        }
        return s2;
 }
index 95fe849e42d3b9e1f7bc9590fcfac93016f38146..079070ff1d4bd629712eff5f6f6e239898640963 100644 (file)
@@ -36,24 +36,26 @@ static size_t has_non_ascii(const char *s, size_t maxlen, size_t *strlen_c)
 }
 
 
-void probe_utf8_pathname_composition(char *path, int len)
+void probe_utf8_pathname_composition(void)
 {
+       struct strbuf path = STRBUF_INIT;
        static const char *auml_nfc = "\xc3\xa4";
        static const char *auml_nfd = "\x61\xcc\x88";
        int output_fd;
        if (precomposed_unicode != -1)
                return; /* We found it defined in the global config, respect it */
-       strcpy(path + len, auml_nfc);
-       output_fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600);
+       git_path_buf(&path, "%s", auml_nfc);
+       output_fd = open(path.buf, O_CREAT|O_EXCL|O_RDWR, 0600);
        if (output_fd >= 0) {
                close(output_fd);
-               strcpy(path + len, auml_nfd);
-               precomposed_unicode = access(path, R_OK) ? 0 : 1;
+               git_path_buf(&path, "%s", auml_nfd);
+               precomposed_unicode = access(path.buf, R_OK) ? 0 : 1;
                git_config_set("core.precomposeunicode", precomposed_unicode ? "true" : "false");
-               strcpy(path + len, auml_nfc);
-               if (unlink(path))
-                       die_errno(_("failed to unlink '%s'"), path);
+               git_path_buf(&path, "%s", auml_nfc);
+               if (unlink(path.buf))
+                       die_errno(_("failed to unlink '%s'"), path.buf);
        }
+       strbuf_release(&path);
 }
 
 
@@ -139,9 +141,8 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir)
                                size_t inleft = namelenz;
                                char *outpos = &prec_dir->dirent_nfc->d_name[0];
                                size_t outsz = prec_dir->dirent_nfc->max_name_len;
-                               size_t cnt;
                                errno = 0;
-                               cnt = iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
+                               iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
                                if (errno || inleft) {
                                        /*
                                         * iconv() failed and errno could be E2BIG, EILSEQ, EINVAL, EBADF
index 3b73585fc5d8c1f40f1c467829b67f439bca3483..a94e7c43422d87ae062842ae2c2aad430c4b13e4 100644 (file)
@@ -27,7 +27,7 @@ typedef struct {
 } PREC_DIR;
 
 void precompose_argv(int argc, const char **argv);
-void probe_utf8_pathname_composition(char *, int);
+void probe_utf8_pathname_composition(void);
 
 PREC_DIR *precompose_utf8_opendir(const char *dirname);
 struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *dirp);
index 06f30887080bc293994ed470ff9b313ae048dcfb..fba5986399ed20174d37c39bf6d243bcbfddd3a7 100644 (file)
@@ -18,6 +18,8 @@
    Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301 USA.  */
 
+#include <stdint.h>
+
 static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern,
                                          size_t length, reg_syntax_t syntax);
 static void re_compile_fastmap_iter (regex_t *bufp,
@@ -2577,7 +2579,7 @@ parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
     old_tree = NULL;
 
   if (elem->token.type == SUBEXP)
-    postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx);
+    postorder (elem, mark_opt_subexp, (void *) (intptr_t) elem->token.opr.idx);
 
   tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT));
   if (BE (tree == NULL, 0))
@@ -3806,7 +3808,7 @@ create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
 static reg_errcode_t
 mark_opt_subexp (void *extra, bin_tree_t *node)
 {
-  int idx = (int) (long) extra;
+  int idx = (int) (intptr_t) extra;
   if (node->token.type == SUBEXP && node->token.opr.idx == idx)
     node->token.opt_subexp = 1;
 
index efc5bb3a4b63166eccb33f6ec6a8ad57e4c9ac36..ceff55bd67696403f374f68ae6d3db97f603aebe 100644 (file)
@@ -539,7 +539,7 @@ void winansi_init(void)
                return;
 
        /* create a named pipe to communicate with the console thread */
-       sprintf(name, "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId());
+       xsnprintf(name, sizeof(name), "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId());
        hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND,
                PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
        if (hwrite == INVALID_HANDLE_VALUE)
index 943c43965e68fe39fa6e0ea266c3ac376f6e0125..f34dcaad20d66954c0c13a50bdcbd0f0312cc982 100644 (file)
@@ -166,7 +166,6 @@ endif
 ifeq ($(uname_O),Cygwin)
        ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4)
                NO_D_TYPE_IN_DIRENT = YesPlease
-               NO_D_INO_IN_DIRENT = YesPlease
                NO_STRCASESTR = YesPlease
                NO_MEMMEM = YesPlease
                NO_MKSTEMPS = YesPlease
@@ -370,7 +369,6 @@ ifeq ($(uname_S),Windows)
        NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
        DEFAULT_HELP_FORMAT = html
-       NO_D_INO_IN_DIRENT = YesPlease
 
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
@@ -520,7 +518,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_INET_NTOP = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
-       NO_D_INO_IN_DIRENT = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -D_USE_32BIT_TIME_T -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
index 14012fad720c1fdade1f305b1a7092abf2146ac1..76170ad06d1e8a591af209b91c622d3073c691dd 100644 (file)
@@ -521,10 +521,33 @@ AC_CHECK_LIB([curl], [curl_global_init],
 [NO_CURL=],
 [NO_CURL=YesPlease])
 
+if test -z "${NO_CURL}" && test -z "${NO_OPENSSL}"; then
+
+AC_CHECK_LIB([curl], [Curl_ssl_init],
+[NEEDS_SSL_WITH_CURL=YesPlease],
+[NEEDS_SSL_WITH_CURL=])
+
+GIT_CONF_SUBST([NEEDS_SSL_WITH_CURL])
+
+fi
+
 GIT_UNSTASH_FLAGS($CURLDIR)
 
 GIT_CONF_SUBST([NO_CURL])
 
+if test -z "$NO_CURL"; then
+
+AC_CHECK_PROG([CURL_CONFIG], [curl-config],
+[curl-config],
+[no])
+
+if test $CURL_CONFIG != no; then
+    GIT_CONF_SUBST([CURL_CONFIG])
+fi
+
+fi
+
+
 #
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
 # not built, and you cannot push using http:// and https:// transports.
@@ -767,13 +790,6 @@ elif test x$ac_cv_member_struct_stat_st_mtim_tv_nsec != xyes; then
        GIT_CONF_SUBST([NO_NSEC])
 fi
 #
-# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
-AC_CHECK_MEMBER(struct dirent.d_ino,
-[NO_D_INO_IN_DIRENT=],
-[NO_D_INO_IN_DIRENT=YesPlease],
-[#include <dirent.h>])
-GIT_CONF_SUBST([NO_D_INO_IN_DIRENT])
-#
 # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
 # d_type in struct dirent (latest Cygwin -- will be fixed soonish).
 AC_CHECK_MEMBER(struct dirent.d_type,
index d3283b8a4f3e8ed8451e723ce8b39d468aaa3dd3..108f5ab60e0ab122f0a86612ba9e4875ded29f26 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -333,7 +333,7 @@ static const char *ai_name(const struct addrinfo *ai)
        static char addr[NI_MAXHOST];
        if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
                        NI_NUMERICHOST) != 0)
-               strcpy(addr, "(unknown)");
+               xsnprintf(addr, sizeof(addr), "(unknown)");
 
        return addr;
 }
index 6bb12306b8210ea2d7ae58d6e997801ec44e1604..bc77e66b8555679ed479929f2e756152cdee19fc 100644 (file)
@@ -1,3 +1,44 @@
+Release 1.2.0
+=============
+
+* It is now possible to exclude some refs (e.g. exclude some branches
+  or tags). See refFilterDoSendRegex, refFilterDontSendRegex,
+  refFilterInclusionRegex and refFilterExclusionRegex.
+
+* New commitEmailFormat option which can be set to "html" to generate
+  simple colorized diffs using HTML for the commit emails.
+
+* git-multimail can now be ran as a Gerrit ref-updated hook, or from
+  Atlassian BitBucket Server (formerly known as Atlassian Stash).
+
+* The From: field is now more customizeable. It can be set
+  independently for refchange emails and commit emails (see
+  fromCommit, fromRefChange). The special values pusher and author can
+  be used in these configuration variable.
+
+* A new command-line option, --version, was added. The version is also
+  available in the X-Git-Multimail-Version header of sent emails.
+
+* Set X-Git-NotificationType header to differentiate the various types
+  of notifications. Current values are: diff, ref_changed_plus_diff,
+  ref_changed.
+
+* Preliminary support for Python 3. The testsuite passes with Python 3,
+  but it has not received as much testing as the Python 2 version yet.
+
+* Several encoding-related fixes. UTF-8 characters work in more
+  situations (but non-ascii characters in email address are still not
+  supported).
+
+* The testsuite and its documentation has been greatly improved.
+
+Plus all the bugfixes from version 1.1.1.
+
+This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
+v1.7.10-406-gdc801e7, git-1.8.2.3 and 2.6.0. Git versions prior to
+v1.7.10-406-gdc801e7 probably work, but cannot run the testsuite
+properly.
+
 Release 1.1.1 (bugfix-only release)
 ===================================
 
diff --git a/contrib/hooks/multimail/CONTRIBUTING.rst b/contrib/hooks/multimail/CONTRIBUTING.rst
new file mode 100644 (file)
index 0000000..09efdb0
--- /dev/null
@@ -0,0 +1,30 @@
+git-multimail is an open-source project, built by volunteers. We would
+welcome your help!
+
+The current maintainers are Michael Haggerty <mhagger@alum.mit.edu>
+and Matthieu Moy <matthieu.moy@grenoble-inp.fr>.
+
+Please note that although a copy of git-multimail is distributed in
+the "contrib" section of the main Git project, development takes place
+in a separate git-multimail repository on GitHub:
+
+    https://github.com/git-multimail/git-multimail
+
+Whenever enough changes to git-multimail have accumulated, a new
+code-drop of git-multimail will be submitted for inclusion in the Git
+project.
+
+We use the GitHub issue tracker to keep track of bugs and feature
+requests, and we use GitHub pull requests to exchange patches (though,
+if you prefer, you can send patches via the Git mailing list with CC
+to the maintainers). Please sign off your patches as per the `Git
+project practice
+<https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
+
+General discussion of git-multimail can take place on the main Git
+mailing list,
+
+    git@vger.kernel.org
+
+Please CC emails regarding git-multimail to the maintainers so that we
+don't overlook them.
index e552c90c4511bfb0b60ede3d1dbe8e063c6fecf1..55120685f04674daed7c0c32b8c9f33b0c8b79e3 100644 (file)
@@ -1,5 +1,5 @@
-git-multimail Version 1.1.1
-===========================
+git-multimail (version 1.2.0)
+=============================
 
 .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
     :target: https://travis-ci.org/git-multimail/git-multimail
@@ -53,11 +53,13 @@ By default, for each push received by the repository, git-multimail:
      + [git] 07/08: Merge branch 'mm/api-credentials-doc'
      + [git] 08/08: Git 1.7.11-rc2
 
-   Each commit appears in exactly one commit email, the first time
-   that it is pushed to the repository.  If a commit is later merged
-   into another branch, then a one-line summary of the commit is
-   included in the reference change email (as usual), but no
-   additional commit email is generated.
+   By default, each commit appears in exactly one commit email, the
+   first time that it is pushed to the repository.  If a commit is later
+   merged into another branch, then a one-line summary of the commit
+   is included in the reference change email (as usual), but no
+   additional commit email is generated. See
+   `multimailhook.refFilter(Inclusion|Exclusion|DoSend|DontSend)Regex`
+   below to configure which branches and tags are watched by the hook.
 
    By default, reference change emails have their "Reply-To" field set
    to the person who pushed the change, and commit emails have their
@@ -73,21 +75,8 @@ Requirements
 ------------
 
 * Python 2.x, version 2.4 or later.  No non-standard Python modules
-  are required.  git-multimail does *not* currently work with Python
-  3.x.
-
-  The example scripts invoke Python using the following shebang line
-  (following PEP 394 [1]_)::
-
-      #! /usr/bin/env python2
-
-  If your system's Python2 interpreter is not in your PATH or is not
-  called ``python2``, you can change the lines accordingly.  Or you can
-  invoke the Python interpreter explicitly, for example via a tiny
-  shell script like::
-
-      #! /bin/sh
-      /usr/local/bin/python /path/to/git_multimail.py "$@"
+  are required.  git-multimail has preliminary support for Python 3
+  (but it has been better tested with Python 2).
 
 * The ``git`` command must be in your PATH.  git-multimail is known to
   work with Git versions back to 1.7.1.  (Earlier versions have not
@@ -146,7 +135,9 @@ following ``git config`` settings:
 
 multimailhook.environment
 
-    This describes the general environment of the repository.
+    This describes the general environment of the repository. In most
+    cases, you do not need to specify a value for this variable:
+    `git-multimail` will autodetect which environment to use.
     Currently supported values:
 
     * generic
@@ -161,18 +152,57 @@ multimailhook.environment
       optionally read from gitolite.conf (see multimailhook.from).
 
       For more information about gitolite and git-multimail, read
-      doc/gitolite.rst
+      `<doc/gitolite.rst>`__
+
+    * stash
+
+      Environment to use when ``git-multimail`` is ran as an Atlassian
+      BitBucket Server (formerly known as Atlassian Stash) hook.
+
+      **Warning:** this mode was provided by a third-party contributor
+      and never tested by the git-multimail maintainers. It is
+      provided as-is and may or may not work for you.
+
+      This value is automatically assumed when the stash-specific
+      flags (``--stash-user`` and ``--stash-repo``) are specified on
+      the command line. When this environment is active, the username
+      and repo come from these two command line flags, which must be
+      specified.
+
+    * gerrit
+
+      Environment to use when ``git-multimail`` is ran as a
+      ``ref-updated`` Gerrit hook.
+
+      This value is used when the gerrit-specific command line flags
+      (``--oldrev``, ``--newrev``, ``--refname``, ``--project``) for
+      gerrit's ref-updated hook are present. When this environment is
+      active, the username of the pusher is taken from the
+      ``--submitter`` argument if that command line option is passed,
+      otherwise 'Gerrit' is used. The repository name is taken from
+      the ``--project`` option on the command line, which must be passed.
+
+      For more information about gerrit and git-multimail, read
+      `<doc/gerrit.rst>`__
 
-    If neither of these environments is suitable for your setup, then
-    you can implement a Python class that inherits from Environment
-    and instantiate it via a script that looks like the example
+    If none of these environments is suitable for your setup, then you
+    can implement a Python class that inherits from Environment and
+    instantiate it via a script that looks like the example
     post-receive script.
 
     The environment value can be specified on the command line using
-    the --environment option.  If it is not specified on the command
-    line or by multimailhook.environment, then it defaults to
-    ``gitolite`` if the environment contains variables $GL_USER and
-    $GL_REPO; otherwise ``generic``.
+    the ``--environment`` option. If it is not specified on the
+    command line or by ``multimailhook.environment``, the value is
+    guessed as follows:
+
+    * If stash-specific (respectively gerrit-specific) command flags
+      are present on the command-line, then ``stash`` (respectively
+      ``gerrit``) is used.
+
+    * If the environment variables $GL_USER and $GL_REPO are set, then
+      ``gitolite`` is used.
+
+    * If none of the above apply, then ``generic`` is used.
 
 multimailhook.repoName
 
@@ -196,8 +226,8 @@ multimailhook.refchangeList
     reference changes should be sent, as RFC 2822 email addresses
     separated by commas.  This configuration option can be
     multivalued.  The default is the value in
-    multimailhook.mailingList.  Set this value to the empty string to
-    prevent reference change emails from being sent even if
+    multimailhook.mailingList.  Set this value to "none" (or the empty
+    string) to prevent reference change emails from being sent even if
     multimailhook.mailingList is set.
 
 multimailhook.announceList
@@ -206,9 +236,9 @@ multimailhook.announceList
     tags should be sent, as RFC 2822 email addresses separated by
     commas.  This configuration option can be multivalued.  The
     default is the value in multimailhook.refchangeList or
-    multimailhook.mailingList.  Set this value to the empty string to
-    prevent annotated tag announcement emails from being sent even if
-    one of the other values is set.
+    multimailhook.mailingList.  Set this value to "none" (or the empty
+    string) to prevent annotated tag announcement emails from being sent
+    even if one of the other values is set.
 
 multimailhook.commitList
 
@@ -216,7 +246,7 @@ multimailhook.commitList
     commits should be sent, as RFC 2822 email addresses separated by
     commas.  This configuration option can be multivalued.  The
     default is the value in multimailhook.mailingList.  Set this value
-    to the empty string to prevent notification emails about
+    to "none" (or the empty string) to prevent notification emails about
     individual commits from being sent even if
     multimailhook.mailingList is set.
 
@@ -230,6 +260,20 @@ multimailhook.announceShortlog
     not so straightforward, then the shortlog might be confusing
     rather than useful.  Default is false.
 
+multimailhook.commitEmailFormat
+
+    The format of email messages for the individual commits, can be "text" or
+    "html". In the latter case, the emails will include diffs using colorized
+    HTML instead of plain text used by default. Note that this  currently the
+    ref change emails are always sent in plain text.
+
+    Note that when using "html", the formatting is done by parsing the
+    output of ``git log`` with ``-p``. When using
+    ``multimailhook.commitLogOpts`` to specify a ``--format`` for
+    ``git log``, one may get false positive (e.g. lines in the body of
+    the message starting with ``+++`` or ``---`` colored in red or
+    green).
+
 multimailhook.refchangeShowGraph
 
     If this option is set to true, then summary emails about reference
@@ -305,7 +349,7 @@ multimailhook.mailer
 
       * multimailhook.smtpEncryption
 
-        Set the security type. Allowed values: none, ssl.
+        Set the security type. Allowed values: none, ssl, tls.
         Default=none.
 
       * multimailhook.smtpServerDebugLevel
@@ -313,9 +357,26 @@ multimailhook.mailer
         Integer number. Set to greater than 0 to activate debugging.
 
 multimailhook.from
+multimailhook.fromCommit
+multimailhook.fromRefchange
+
+    If set, use this value in the From: field of generated emails.
+    ``fromCommit`` is used for commit emails, ``fromRefchange`` is
+    used for refchange emails, and ``from`` is used as fall-back in
+    all cases.
+
+    The value for these variables can be either:
+
+    - An email address, which will be used directly.
+
+    - The value ``pusher``, in which case the pusher's address (if
+      available) will be used.
 
-    If set, use this value in the From: field of generated emails.  If
-    unset, the value of the From: header is determined as follows:
+    - The value ``author`` (meaningful only for replyToCommit), in which
+      case the commit author's address will be used.
+
+    If config values are unset, the value of the From: header is
+    determined as follows:
 
     1. (gitolite environment only) Parse gitolite.conf, looking for a
        block of comments that looks like this::
@@ -425,6 +486,15 @@ multimailhook.commitLogOpts
     --stat -p --cc``.  Shell quoting is allowed; see
     multimailhook.logOpts for details.
 
+multimailhook.dateSubstitute
+
+    String to use as a substitute for ``Date:`` in the output of ``git
+    log`` while formatting commit messages. This is usefull to avoid
+    emitting a line that can be interpreted by mailers as the start of
+    a cited message (Zimbra webmail in particular). Defaults to
+    ``CommitDate: ``. Set to an empty string or ``none`` to deactivate
+    the behavior.
+
 multimailhook.emailDomain
 
     Domain name appended to the username of the person doing the push
@@ -440,21 +510,13 @@ multimailhook.replyToRefchange
     Addresses to use in the Reply-To: field for commit emails
     (replyToCommit) and refchange emails (replyToRefchange).
     multimailhook.replyTo is used as default when replyToCommit or
-    replyToRefchange is not set.  The value for these variables can be
-    either:
-
-    - An email address, which will be used directly.
-
-    - The value `pusher`, in which case the pusher's address (if
-      available) will be used.  This is the default for refchange
-      emails.
+    replyToRefchange is not set. The shortcuts ``pusher`` and
+    ``author`` are allowed with the same semantics as for
+    ``multimailhook.from``. In addition, the value ``none`` can be
+    used to omit the ``Reply-To:`` field.
 
-    - The value `author` (meaningful only for replyToCommit), in which
-      case the commit author's address will be used.  This is the
-      default for commit emails.
-
-    - The value `none`, in which case the Reply-To: field will be
-      omitted.
+    The default is ``pusher`` for refchange emails, and ``author`` for
+    commit emails.
 
 multimailhook.quiet
 
@@ -478,6 +540,63 @@ multimailhook.combineWhenSingleCommit
     single email.
     Default: true
 
+multimailhook.refFilterInclusionRegex
+multimailhook.refFilterExclusionRegex
+multimailhook.refFilterDoSendRegex
+multimailhook.refFilterDontSendRegex
+
+    **Warning:** these options are experimental. They should work, but
+    the user-interface is not stable yet (in particular, the option
+    names may change). If you want to participate in stabilizing the
+    feature, please contact the maintainers and/or send pull-requests.
+
+    Regular expressions that can be used to limit refs for which email
+    updates will be sent.  It is an error to specify both an inclusion
+    and an exclusion regex.  If a ``refFilterInclusionRegex`` is
+    specified, emails will only be sent for refs which match this
+    regex.  If a ``refFilterExclusionRegex`` regex is specified,
+    emails will be sent for all refs except those that match this
+    regex (or that match a predefined regex specific to the
+    environment, such as "^refs/notes" for most environments and
+    "^refs/notes|^refs/changes" for the gerrit environment).
+
+    The expressions are matched against the complete refname, and is
+    considered to match if any substring matches. For example, to
+    filter-out all tags, set ``refFilterExclusionRegex`` to
+    ``^refs/tags/`` (note the leading ``^`` but no trailing ``$``). If
+    you set ``refFilterExclusionRegex`` to ``master``, then any ref
+    containing ``master`` will be excluded (the ``master`` branch, but
+    also ``refs/tags/master`` or ``refs/heads/foo-master-bar``).
+
+    ``refFilterDoSendRegex`` and ``refFilterDontSendRegex`` are
+    analogous to ``refFilterInclusionRegex`` and
+    ``refFilterExclusionRegex`` with one difference: with
+    ``refFilterDoSendRegex`` and ``refFilterDontSendRegex``, commits
+    introduced by one excluded ref will not be considered as new when
+    they reach an included ref. Typically, if you add a branch ``foo``
+    to  ``refFilterDontSendRegex``, push commits to this branch, and
+    later merge branch ``foo`` into ``master``, then the notification
+    email for ``master`` will contain a commit email only for the
+    merge commit. If you include ``foo`` in
+    ``refFilterExclusionRegex``, then at the time of merge, you will
+    receive one commit email per commit in the branch.
+
+    These variables can be multi-valued, like::
+
+      [multimailhook]
+              refFilterExclusionRegex = ^refs/tags/
+              refFilterExclusionRegex = ^refs/heads/master$
+
+    You can also provide a whitespace-separated list like::
+
+      [multimailhook]
+              refFilterExclusionRegex = ^refs/tags/ ^refs/heads/master$
+
+    Both examples exclude tags and the master branch, and are
+    equivalent to::
+
+      [multimailhook]
+              refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
 
 Email filtering aids
 --------------------
@@ -547,35 +666,8 @@ consider sharing them with the community!
 Getting involved
 ----------------
 
-git-multimail is an open-source project, built by volunteers. We would
-welcome your help!
-
-The current maintainers are Michael Haggerty <mhagger@alum.mit.edu>
-and Matthieu Moy <matthieu.moy@grenoble-inp.fr>.
-
-Please note that although a copy of git-multimail is distributed in
-the "contrib" section of the main Git project, development takes place
-in a separate git-multimail repository on GitHub:
-
-    https://github.com/git-multimail/git-multimail
-
-Whenever enough changes to git-multimail have accumulated, a new
-code-drop of git-multimail will be submitted for inclusion in the Git
-project.
-
-We use the GitHub issue tracker to keep track of bugs and feature
-requests, and we use GitHub pull requests to exchange patches (though,
-if you prefer, you can send patches via the Git mailing list with CC
-to the maintainers). Please sign off your patches as per the Git
-project practice.
-
-General discussion of git-multimail can take place on the main Git
-mailing list,
-
-    git@vger.kernel.org
-
-Please CC emails regarding git-multimail to the maintainers so that we
-don't overlook them.
+Please, read `<CONTRIBUTING.rst>`__ for instructions on how to
+contribute to git-multimail.
 
 
 Footnotes
index f5d59a8d313a3114c1fd0bdfa0faaea64a7b71d9..300a2a4d2d479b3ab002a5e6480c3e9a83e36fb7 100644 (file)
@@ -6,10 +6,10 @@ website:
     https://github.com/git-multimail/git-multimail
 
 The version in this directory was obtained from the upstream project
-on July 03 2015 and consists of the "git-multimail" subdirectory from
+on October 11 2015 and consists of the "git-multimail" subdirectory from
 revision
 
-    6d6c9eb62a054143322cfaecde3949189c065b46 refs/tags/1.1.1
+    c0791b9ef5821a746fc3475c25765e640452eaae refs/tags/1.2.0
 
 Please see the README file in this directory for information about how
 to report bugs or contribute to git-multimail.
diff --git a/contrib/hooks/multimail/doc/gerrit.rst b/contrib/hooks/multimail/doc/gerrit.rst
new file mode 100644 (file)
index 0000000..8011d05
--- /dev/null
@@ -0,0 +1,56 @@
+Setting up git-multimail on Gerrit
+==================================
+
+Gerrit has its own email-sending system, but you may prefer using
+``git-multimail`` instead. It supports Gerrit natively as a Gerrit
+``ref-updated`` hook (Warning: `Gerrit hooks
+<https://gerrit-review.googlesource.com/Documentation/config-hooks.html>`__
+are distinct from Git hooks). Setting up ``git-multimail`` on a Gerrit
+installation can be done following the instructions below.
+
+The explanations show an easy way to set up ``git-multimail``,
+but leave ``git-multimail`` installed and unconfigured for a while. If
+you run Gerrit on a production server, it is advised that you
+execute the step "Set up the hook" last to avoid confusing your users
+in the meantime.
+
+Set up the hook
+---------------
+
+Create a directory ``$site_path/hooks/`` if it does not exist (if you
+don't know what ``$site_path`` is, run ``gerrit.sh status`` and look
+for a ``GERRIT_SITE`` line). Either copy ``git_multimail.py`` to
+``$site_path/hooks/ref-updated`` or create a wrapper script like
+this::
+
+  #! /bin/sh
+  exec /path/to/git_multimail.py "$@"
+
+In both cases, make sure the file is named exactly
+``$site_path/hooks/ref-updated`` and is executable.
+
+(Alternatively, you may configure the ``[hooks]`` section of
+gerrit.config)
+
+Configuration
+-------------
+
+Log on the gerrit server and edit ``$site_path/git/$project/config``
+to configure ``git-multimail``.
+
+Troubleshooting
+---------------
+
+Warning: this will disable ``git-multimail`` during the debug, and
+could confuse your users. Don't run on a production server.
+
+To debug configuration issues with ``git-multimail``, you can add the
+``--stdout`` option when calling ``git_multimail.py`` like this::
+
+  #!/bin/sh
+  exec /path/to/git-multimail/git-multimail/git_multimail.py \
+    --stdout "$@" >> /tmp/log.txt
+
+and try pushing from a test repository. You should see the source of
+the email that would have been sent in the output of ``git push`` in
+the file ``/tmp/log.txt``.
diff --git a/contrib/hooks/multimail/doc/gitolite.rst b/contrib/hooks/multimail/doc/gitolite.rst
new file mode 100644 (file)
index 0000000..00aedd9
--- /dev/null
@@ -0,0 +1,109 @@
+Setting up git-multimail on gitolite
+====================================
+
+``git-multimail`` supports gitolite 3 natively.
+The explanations below show an easy way to set up ``git-multimail``,
+but leave ``git-multimail`` installed and unconfigured for a while. If
+you run gitolite on a production server, it is advised that you
+execute the step "Set up the hook" last to avoid confusing your users
+in the meantime.
+
+Set up the hook
+---------------
+
+Log in as your gitolite user.
+
+Create a file ``.gitolite/hooks/common/post-receive`` on your gitolite
+account containing (adapt the path, obviously)::
+
+  #!/bin/sh
+  exec /path/to/git-multimail/git-multimail/git_multimail.py "$@"
+
+Make sure it's executable (``chmod +x``). Record the hook in
+gitolite::
+
+  gitolite setup
+
+Configuration
+-------------
+
+First, you have to allow the admin to set Git configuration variables.
+
+As gitolite user, edit the line containing ``GIT_CONFIG_KEYS`` in file
+``.gitolite.rc``, to make it look like::
+
+  GIT_CONFIG_KEYS                 =>  'multimailhook\..*',
+
+You can now log out and return to your normal user.
+
+In the ``gitolite-admin`` clone, edit the file ``conf/gitolite.conf``
+and add::
+
+  repo @all
+      # Not strictly needed as git_multimail.py will chose gitolite if
+      # $GL_USER is set.
+      config multimailhook.environment = gitolite
+      config multimailhook.mailingList = # Where emails should be sent
+      config multimailhook.from = # From address to use
+
+Obviously, you can customize all parameters on a per-repository basis by
+adding these ``config multimailhook.*`` lines in the section
+corresponding to a repository or set of repositories.
+
+To activate ``git-multimail`` on a per-repository basis, do not set
+``multimailhook.mailingList`` in the ``@all`` section and set it only
+for repositories for which you want ``git-multimail``.
+
+Alternatively, you can set up the ``From:`` field on a per-user basis
+by adding a ``BEGIN USER EMAILS``/``END USER EMAILS`` section (see
+``../README``).
+
+Specificities of Gitolite for Configuration
+-------------------------------------------
+
+Empty configuration variables
+.............................
+
+With gitolite, the syntax ``config multimailhook.commitList = ""``
+unsets the variable instead of setting it to an empty string (see
+`here
+<http://gitolite.com/gitolite/git-config.html#an-important-warning-about-deleting-a-config-line>`__).
+As a result, there is no way to set a variable to the empty string.
+In all most places where an empty value is required, git-multimail
+now allows to specify special ``"none"`` value (case-sensitive) to
+mean the same.
+
+Alternatively, one can use ``" "`` (a single space) instead of ``""``.
+In most cases (in particular ``multimailhook.*List`` variables), this
+will be equivalent to an empty string.
+
+If you have a use-case where ``"none"`` is not an acceptable value and
+you need ``" "`` or  ``""`` instead, please report it as a bug to
+git-multimail.
+
+Allowing Regular Expressions in Configuration
+.............................................
+
+gitolite has a mechanism to prevent unsafe configuration variable
+values, which prevent characters like ``|`` commonly used in regular
+expressions. If you do not need the safety feature of gitolite and
+need to use regular expressions in your configuration (e.g. for
+``multimailhook.refFilter*`` variables), set
+`UNSAFE_PATT
+<http://gitolite.com/gitolite/git-config.html#unsafe-patt>`__ to a
+less restrictive value.
+
+Troubleshooting
+---------------
+
+Warning: this will disable ``git-multimail`` during the debug, and
+could confuse your users. Don't run on a production server.
+
+To debug configuration issues with ``git-multimail``, you can add the
+``--stdout`` option when calling ``git_multimail.py`` like this::
+
+  #!/bin/sh
+  exec /path/to/git-multimail/git-multimail/git_multimail.py --stdout "$@"
+
+and try pushing from a test repository. You should see the source of
+the email that would have been sent in the output of ``git push``.
index c06ce7a5158175b684a70e6a148031593a12fb47..0180dba43126df209bde82e3e5aea3616f402c96 100755 (executable)
@@ -1,4 +1,6 @@
-#! /usr/bin/env python2
+#! /usr/bin/env python
+
+__version__ = '1.2.0'
 
 # Copyright (c) 2015 Matthieu Moy and others
 # Copyright (c) 2012-2014 Michael Haggerty and others
 import optparse
 import smtplib
 import time
+import cgi
+
+PYTHON3 = sys.version_info >= (3, 0)
+
+if sys.version_info <= (2, 5):
+    def all(iterable):
+        for element in iterable:
+            if not element:
+                return False
+            return True
+
+
+def is_ascii(s):
+    return all(ord(c) < 128 and ord(c) > 0 for c in s)
+
+
+if PYTHON3:
+    def str_to_bytes(s):
+        return s.encode(ENCODING)
+
+    def bytes_to_str(s):
+        return s.decode(ENCODING)
+
+    unicode = str
+
+    def write_str(f, msg):
+        # Try outputing with the default encoding. If it fails,
+        # try UTF-8.
+        try:
+            f.buffer.write(msg.encode(sys.getdefaultencoding()))
+        except UnicodeEncodeError:
+            f.buffer.write(msg.encode(ENCODING))
+else:
+    def str_to_bytes(s):
+        return s
+
+    def bytes_to_str(s):
+        return s
+
+    def write_str(f, msg):
+        f.write(msg)
+
+    def next(it):
+        return it.next()
+
 
 try:
+    from email.charset import Charset
     from email.utils import make_msgid
     from email.utils import getaddresses
     from email.utils import formataddr
     from email.header import Header
 except ImportError:
     # Prior to Python 2.5, the email module used different names:
+    from email.Charset import Charset
     from email.Utils import make_msgid
     from email.Utils import getaddresses
     from email.Utils import formataddr
 To: %(recipients)s
 Subject: %(subject)s
 MIME-Version: 1.0
-Content-Type: text/plain; charset=%(charset)s
+Content-Type: text/%(contenttype)s; charset=%(charset)s
 Content-Transfer-Encoding: 8bit
 Message-ID: %(msgid)s
 From: %(fromaddr)s
 X-Git-Reftype: %(refname_type)s
 X-Git-Oldrev: %(oldrev)s
 X-Git-Newrev: %(newrev)s
+X-Git-NotificationType: ref_changed
+X-Git-Multimail-Version: %(multimail_version)s
 Auto-Submitted: auto-generated
 """
 
 Cc: %(cc_recipients)s
 Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
 MIME-Version: 1.0
-Content-Type: text/plain; charset=%(charset)s
+Content-Type: text/%(contenttype)s; charset=%(charset)s
 Content-Transfer-Encoding: 8bit
 From: %(fromaddr)s
 Reply-To: %(reply_to)s
 X-Git-Refname: %(refname)s
 X-Git-Reftype: %(refname_type)s
 X-Git-Rev: %(rev)s
+X-Git-NotificationType: diff
+X-Git-Multimail-Version: %(multimail_version)s
 Auto-Submitted: auto-generated
 """
 
 To: %(recipients)s
 Subject: %(subject)s
 MIME-Version: 1.0
-Content-Type: text/plain; charset=%(charset)s
+Content-Type: text/%(contenttype)s; charset=%(charset)s
 Content-Transfer-Encoding: 8bit
 Message-ID: %(msgid)s
 From: %(fromaddr)s
 X-Git-Oldrev: %(oldrev)s
 X-Git-Newrev: %(newrev)s
 X-Git-Rev: %(rev)s
+X-Git-NotificationType: ref_changed_plus_diff
+X-Git-Multimail-Version: %(multimail_version)s
 Auto-Submitted: auto-generated
 """
 
@@ -352,12 +407,14 @@ def read_git_output(args, input=None, keepends=False, **kw):
 def read_output(cmd, input=None, keepends=False, **kw):
     if input:
         stdin = subprocess.PIPE
+        input = str_to_bytes(input)
     else:
         stdin = None
     p = subprocess.Popen(
         cmd, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
         )
     (out, err) = p.communicate(input)
+    out = bytes_to_str(out)
     retcode = p.wait()
     if retcode:
         raise CommandError(cmd, retcode)
@@ -418,26 +475,37 @@ def git_log(spec, **kw):
 def header_encode(text, header_name=None):
     """Encode and line-wrap the value of an email header field."""
 
-    try:
-        if isinstance(text, str):
-            text = text.decode(ENCODING, 'replace')
-        return Header(text, header_name=header_name).encode()
-    except UnicodeEncodeError:
-        return Header(text, header_name=header_name, charset=CHARSET,
-                      errors='replace').encode()
+    # Convert to unicode, if required.
+    if not isinstance(text, unicode):
+        text = unicode(text, 'utf-8')
+
+    if is_ascii(text):
+        charset = 'ascii'
+    else:
+        charset = 'utf-8'
+
+    return Header(text, header_name=header_name, charset=Charset(charset)).encode()
 
 
 def addr_header_encode(text, header_name=None):
     """Encode and line-wrap the value of an email header field containing
     email addresses."""
 
-    return Header(
-        ', '.join(
-            formataddr((header_encode(name), emailaddr))
-            for name, emailaddr in getaddresses([text])
-            ),
-        header_name=header_name
-        ).encode()
+    # Convert to unicode, if required.
+    if not isinstance(text, unicode):
+        text = unicode(text, 'utf-8')
+
+    text = ', '.join(
+        formataddr((header_encode(name), emailaddr))
+        for name, emailaddr in getaddresses([text])
+        )
+
+    if is_ascii(text):
+        charset = 'ascii'
+    else:
+        charset = 'utf-8'
+
+    return Header(text, header_name=header_name, charset=Charset(charset)).encode()
 
 
 class Config(object):
@@ -496,7 +564,8 @@ def get_all(self, name, default=None):
                 ['config', '--get-all', '--null', '%s.%s' % (self.section, name)],
                 env=self.env, keepends=True,
                 ))
-        except CommandError, e:
+        except CommandError:
+            t, e, traceback = sys.exc_info()
             if e.retcode == 1:
                 # "the section or key is invalid"; i.e., there is no
                 # value for the specified key.
@@ -504,18 +573,6 @@ def get_all(self, name, default=None):
             else:
                 raise
 
-    def get_recipients(self, name, default=None):
-        """Read a recipients list from the configuration.
-
-        Return the result as a comma-separated list of email
-        addresses, or default if the option is unset.  If the setting
-        has multiple values, concatenate them with comma separators."""
-
-        lines = self.get_all(name, default=None)
-        if lines is None:
-            return default
-        return ', '.join(line.strip() for line in lines)
-
     def set(self, name, value):
         read_git_output(
             ['config', '%s.%s' % (self.section, name), value],
@@ -542,7 +599,8 @@ def unset_all(self, name):
                 ['config', '--unset-all', '%s.%s' % (self.section, name)],
                 env=self.env,
                 )
-        except CommandError, e:
+        except CommandError:
+            t, e, traceback = sys.exc_info()
             if e.retcode == 5:
                 # The name doesn't exist, which is what we wanted anyway...
                 pass
@@ -636,7 +694,7 @@ def get_summary(self):
         if not self.sha1:
             raise ValueError('Empty commit has no summary')
 
-        return iter(generate_summaries('--no-walk', self.sha1)).next()
+        return next(iter(generate_summaries('--no-walk', self.sha1)))
 
     def __eq__(self, other):
         return isinstance(other, GitObject) and self.sha1 == other.sha1
@@ -647,6 +705,10 @@ def __hash__(self):
     def __nonzero__(self):
         return bool(self.sha1)
 
+    def __bool__(self):
+        """Python 2 backward compatibility"""
+        return self.__nonzero__()
+
     def __str__(self):
         return self.sha1 or ZEROS
 
@@ -661,6 +723,12 @@ class Change(object):
     def __init__(self, environment):
         self.environment = environment
         self._values = None
+        self._contains_html_diff = False
+
+    def _contains_diff(self):
+        # We do contain a diff, should it be rendered in HTML?
+        if self.environment.commit_email_format == "html":
+            self._contains_html_diff = True
 
     def _compute_values(self):
         """Return a dictionary {keyword: expansion} for this Change.
@@ -670,7 +738,12 @@ def _compute_values(self):
         get_values().  The return value should always be a new
         dictionary."""
 
-        return self.environment.get_values()
+        values = self.environment.get_values()
+        fromaddr = self.environment.get_fromaddr(change=self)
+        if fromaddr is not None:
+            values['fromaddr'] = fromaddr
+        values['multimail_version'] = get_version()
+        return values
 
     def get_values(self, **extra_values):
         """Return a dictionary {keyword: expansion} for this Change.
@@ -713,12 +786,18 @@ def expand_header_lines(self, template, **extra_values):
         skip lines that contain references to unknown variables."""
 
         values = self.get_values(**extra_values)
+        if self._contains_html_diff:
+            values['contenttype'] = 'html'
+        else:
+            values['contenttype'] = 'plain'
+
         for line in template.splitlines():
-            (name, value) = line.split(':', 1)
+            (name, value) = line.split(': ', 1)
 
             try:
                 value = value % values
-            except KeyError, e:
+            except KeyError:
+                t, e, traceback = sys.exc_info()
                 if DEBUG:
                     self.environment.log_warning(
                         'Warning: unknown variable %r in the following line; line skipped:\n'
@@ -764,6 +843,24 @@ def generate_email_footer(self):
 
         raise NotImplementedError()
 
+    def _wrap_for_html(self, lines):
+        """Wrap the lines in HTML <pre> tag when using HTML format.
+
+        Escape special HTML characters and add <pre> and </pre> tags around
+        the given lines if we should be generating HTML as indicated by
+        self._contains_html_diff being set to true.
+        """
+        if self._contains_html_diff:
+            yield "<pre style='margin:0'>\n"
+
+            for line in lines:
+                yield cgi.escape(line)
+
+            yield '</pre>\n'
+        else:
+            for line in lines:
+                yield line
+
     def generate_email(self, push, body_filter=None, extra_header_values={}):
         """Generate an email describing this change.
 
@@ -779,18 +876,76 @@ def generate_email(self, push, body_filter=None, extra_header_values={}):
         for line in self.generate_email_header(**extra_header_values):
             yield line
         yield '\n'
-        for line in self.generate_email_intro():
+        for line in self._wrap_for_html(self.generate_email_intro()):
             yield line
 
         body = self.generate_email_body(push)
         if body_filter is not None:
             body = body_filter(body)
+
+        diff_started = False
+        if self._contains_html_diff:
+            # "white-space: pre" is the default, but we need to
+            # specify it again in case the message is viewed in a
+            # webmail which wraps it in an element setting white-space
+            # to something else (Zimbra does this and sets
+            # white-space: pre-line).
+            yield '<pre style="white-space: pre; background: #F8F8F8">'
         for line in body:
+            if self._contains_html_diff:
+                # This is very, very naive. It would be much better to really
+                # parse the diff, i.e. look at how many lines do we have in
+                # the hunk headers instead of blindly highlighting everything
+                # that looks like it might be part of a diff.
+                bgcolor = ''
+                fgcolor = ''
+                if line.startswith('--- a/'):
+                    diff_started = True
+                    bgcolor = 'e0e0ff'
+                elif line.startswith('diff ') or line.startswith('index '):
+                    diff_started = True
+                    fgcolor = '808080'
+                elif diff_started:
+                    if line.startswith('+++ '):
+                        bgcolor = 'e0e0ff'
+                    elif line.startswith('@@'):
+                        bgcolor = 'e0e0e0'
+                    elif line.startswith('+'):
+                        bgcolor = 'e0ffe0'
+                    elif line.startswith('-'):
+                        bgcolor = 'ffe0e0'
+                elif line.startswith('commit '):
+                    fgcolor = '808000'
+                elif line.startswith('    '):
+                    fgcolor = '404040'
+
+                # Chop the trailing LF, we don't want it inside <pre>.
+                line = cgi.escape(line[:-1])
+
+                if bgcolor or fgcolor:
+                    style = 'display:block; white-space:pre;'
+                    if bgcolor:
+                        style += 'background:#' + bgcolor + ';'
+                    if fgcolor:
+                        style += 'color:#' + fgcolor + ';'
+                    # Use a <span style='display:block> to color the
+                    # whole line. The newline must be inside the span
+                    # to display properly both in Firefox and in
+                    # text-based browser.
+                    line = "<span style='%s'>%s\n</span>" % (style, line)
+                else:
+                    line = line + '\n'
+
             yield line
+        if self._contains_html_diff:
+            yield '</pre>'
 
-        for line in self.generate_email_footer():
+        for line in self._wrap_for_html(self.generate_email_footer()):
             yield line
 
+    def get_alt_fromaddr(self):
+        return None
+
 
 class Revision(Change):
     """A Change consisting of a single git commit."""
@@ -867,14 +1022,25 @@ def generate_email_intro(self):
     def generate_email_body(self, push):
         """Show this revision."""
 
-        return read_git_lines(
-            ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
-            keepends=True,
-            )
+        for line in read_git_lines(
+                ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
+                keepends=True,
+                ):
+            if line.startswith('Date:   ') and self.environment.date_substitute:
+                yield self.environment.date_substitute + line[len('Date:   '):]
+            else:
+                yield line
 
     def generate_email_footer(self):
         return self.expand_lines(REVISION_FOOTER_TEMPLATE)
 
+    def generate_email(self, push, body_filter=None, extra_header_values={}):
+        self._contains_diff()
+        return Change.generate_email(self, push, body_filter, extra_header_values)
+
+    def get_alt_fromaddr(self):
+        return self.environment.from_commit
+
 
 class ReferenceChange(Change):
     """A Change to a Git reference.
@@ -1096,10 +1262,10 @@ def generate_revision_change_log(self, new_commits_list):
             yield '\n'
             yield 'Detailed log of new commits:\n\n'
             for line in read_git_lines(
-                    ['log', '--no-walk']
-                    + self.logopts
-                    + new_commits_list
-                    ['--'],
+                    ['log', '--no-walk'] +
+                    self.logopts +
+                    new_commits_list +
+                    ['--'],
                     keepends=True,
                     ):
                 yield line
@@ -1253,9 +1419,9 @@ def generate_revision_change_summary(self, push):
             yield '\n'
             yield 'Summary of changes:\n'
             for line in read_git_lines(
-                    ['diff-tree']
-                    + self.diffopts
-                    ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
+                    ['diff-tree'] +
+                    self.diffopts +
+                    ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
                     keepends=True,
                     ):
                 yield line
@@ -1316,6 +1482,9 @@ def generate_delete_summary(self, push):
             )
         yield '\n'
 
+    def get_alt_fromaddr(self):
+        return self.environment.from_refchange
+
 
 class BranchChange(ReferenceChange):
     refname_type = 'branch'
@@ -1397,9 +1566,9 @@ def split_line(line):
             # commit is a non-merge commit, though it may make sense to
             # combine if it is a merge as well.
             if not (
-                    len(new_commits) == 1
-                    and len(new_commits[0][1]) == 1
-                    and new_commits[0][0] in known_added_sha1s
+                    len(new_commits) == 1 and
+                    len(new_commits[0][1]) == 1 and
+                    new_commits[0][0] in known_added_sha1s
                     ):
                 return None
 
@@ -1432,6 +1601,7 @@ def generate_combined_email(self, push, revision, body_filter=None, extra_header
             values['subject'] = self.expand(COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values)
 
         self._single_revision = revision
+        self._contains_diff()
         self.header_template = COMBINED_HEADER_TEMPLATE
         self.intro_template = COMBINED_INTRO_TEMPLATE
         self.footer_template = COMBINED_FOOTER_TEMPLATE
@@ -1690,17 +1860,18 @@ def __init__(self, command=None, envelopesender=None):
     def send(self, lines, to_addrs):
         try:
             p = subprocess.Popen(self.command, stdin=subprocess.PIPE)
-        except OSError, e:
+        except OSError:
             sys.stderr.write(
-                '*** Cannot execute command: %s\n' % ' '.join(self.command)
-                + '*** %s\n' % str(e)
-                + '*** Try setting multimailhook.mailer to "smtp"\n'
+                '*** Cannot execute command: %s\n' % ' '.join(self.command) +
+                '*** %s\n' % sys.exc_info()[1] +
+                '*** Try setting multimailhook.mailer to "smtp"\n' +
                 '*** to send emails without using the sendmail command.\n'
                 )
             sys.exit(1)
         try:
+            lines = (str_to_bytes(line) for line in lines)
             p.stdin.writelines(lines)
-        except Exception, e:
+        except Exception:
             sys.stderr.write(
                 '*** Error while generating commit email\n'
                 '***  - mail sending aborted.\n'
@@ -1710,7 +1881,7 @@ def send(self, lines, to_addrs):
                 p.terminate()
             except AttributeError:
                 pass
-            raise e
+            raise
         else:
             p.stdin.close()
             retcode = p.wait()
@@ -1770,11 +1941,11 @@ def call(klass, server, timeout):
                     "*** Setting debug on for SMTP server connection (%s) ***\n"
                     % self.smtpserverdebuglevel)
                 self.smtp.set_debuglevel(self.smtpserverdebuglevel)
-        except Exception, e:
+        except Exception:
             sys.stderr.write(
                 '*** Error establishing SMTP connection to %s ***\n'
                 % self.smtpserver)
-            sys.stderr.write('*** %s\n' % str(e))
+            sys.stderr.write('*** %s\n' % sys.exc_info()[1])
             sys.exit(1)
 
     def __del__(self):
@@ -1784,16 +1955,15 @@ def __del__(self):
     def send(self, lines, to_addrs):
         try:
             if self.username or self.password:
-                sys.stderr.write("*** Authenticating as %s ***\n" % self.username)
                 self.smtp.login(self.username, self.password)
             msg = ''.join(lines)
             # turn comma-separated list into Python list if needed.
             if isinstance(to_addrs, basestring):
                 to_addrs = [email for (name, email) in getaddresses([to_addrs])]
             self.smtp.sendmail(self.envelopesender, to_addrs, msg)
-        except Exception, e:
+        except Exception:
             sys.stderr.write('*** Error sending email ***\n')
-            sys.stderr.write('*** %s\n' % str(e))
+            sys.stderr.write('*** %s\n' % sys.exc_info()[1])
             self.smtp.quit()
             sys.exit(1)
 
@@ -1809,9 +1979,10 @@ def __init__(self, f):
         self.f = f
 
     def send(self, lines, to_addrs):
-        self.f.write(self.SEPARATOR)
-        self.f.writelines(lines)
-        self.f.write(self.SEPARATOR)
+        write_str(self.f, self.SEPARATOR)
+        for line in lines:
+            write_str(self.f, line)
+        write_str(self.f, self.SEPARATOR)
 
 
 def get_git_dir():
@@ -1877,11 +2048,13 @@ class Environment(object):
             Return the address to be used as the 'From' email address
             in the email envelope.
 
-        get_fromaddr()
+        get_fromaddr(change=None)
 
             Return the 'From' email address used in the email 'From:'
-            headers.  (May be a full RFC 2822 email address like 'Joe
-            User <user@example.com>'.)
+            headers.  If the change is known when this function is
+            called, it is passed in as the 'change' parameter.  (May
+            be a full RFC 2822 email address like 'Joe User
+            <user@example.com>'.)
 
         get_administrator()
 
@@ -1901,12 +2074,29 @@ class Environment(object):
             get_reply_to_commit() is used for individual commit
             emails.
 
+        get_ref_filter_regex()
+
+            Return a tuple -- a compiled regex, and a boolean indicating
+            whether the regex picks refs to include (if False, the regex
+            matches on refs to exclude).
+
+        get_default_ref_ignore_regex()
+
+            Return a regex that should be ignored for both what emails
+            to send and when computing what commits are considered new
+            to the repository.  Default is "^refs/notes/".
+
     They should also define the following attributes:
 
         announce_show_shortlog (bool)
 
             True iff announce emails should include a shortlog.
 
+        commit_email_format (string)
+
+            If "html", generate commit emails in HTML instead of plain text
+            used by default.
+
         refchange_showgraph (bool)
 
             True iff refchanges emails should include a detailed graph.
@@ -1939,6 +2129,11 @@ class Environment(object):
             commit mail.  The value should be a list of strings
             representing words to be passed to the command.
 
+        date_substitute (string)
+
+            String to be used in substitution for 'Date:' at start of
+            line in the output of 'git log'.
+
         quiet (bool)
             On success do not write to stderr
 
@@ -1950,6 +2145,13 @@ class Environment(object):
             True if a combined email should be produced when a single
             new commit is pushed to a branch, False otherwise.
 
+        from_refchange, from_commit (strings)
+
+            Addresses to use for the From: field for refchange emails
+            and commit emails respectively.  Set from
+            multimailhook.fromRefchange and multimailhook.fromCommit
+            by ConfigEnvironmentMixin.
+
     """
 
     REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
@@ -1957,6 +2159,7 @@ class Environment(object):
     def __init__(self, osenv=None):
         self.osenv = osenv or os.environ
         self.announce_show_shortlog = False
+        self.commit_email_format = "text"
         self.maxcommitemails = 500
         self.diffopts = ['--stat', '--summary', '--find-copies-harder']
         self.graphopts = ['--oneline', '--decorate']
@@ -1964,6 +2167,7 @@ def __init__(self, osenv=None):
         self.refchange_showgraph = False
         self.refchange_showlog = False
         self.commitlogopts = ['-C', '--stat', '-p', '--cc']
+        self.date_substitute = 'AuthorDate: '
         self.quiet = False
         self.stdout = False
         self.combine_when_single_commit = True
@@ -1972,7 +2176,6 @@ def __init__(self, osenv=None):
             'administrator',
             'charset',
             'emailprefix',
-            'fromaddr',
             'pusher',
             'pusher_email',
             'repo_path',
@@ -1998,7 +2201,7 @@ def get_pusher(self):
     def get_pusher_email(self):
         return None
 
-    def get_fromaddr(self):
+    def get_fromaddr(self, change=None):
         config = Config('user')
         fromname = config.get('name', default='')
         fromemail = config.get('email', default='')
@@ -2080,6 +2283,15 @@ def get_revision_recipients(self, revision):
     def get_reply_to_commit(self, revision):
         return revision.author
 
+    def get_default_ref_ignore_regex(self):
+        # The commit messages of git notes are essentially meaningless
+        # and "filenames" in git notes commits are an implementational
+        # detail that might surprise users at first.  As such, we
+        # would need a completely different method for handling emails
+        # of git notes in order for them to be of benefit for users,
+        # which we simply do not have right now.
+        return "^refs/notes/"
+
     def filter_body(self, lines):
         """Filter the lines intended for an email body.
 
@@ -2095,19 +2307,19 @@ def log_msg(self, msg):
         """Write the string msg on a log file or on stderr.
 
         Sends the text to stderr by default, override to change the behavior."""
-        sys.stderr.write(msg)
+        write_str(sys.stderr, msg)
 
     def log_warning(self, msg):
         """Write the string msg on a log file or on stderr.
 
         Sends the text to stderr by default, override to change the behavior."""
-        sys.stderr.write(msg)
+        write_str(sys.stderr, msg)
 
     def log_error(self, msg):
         """Write the string msg on a log file or on stderr.
 
         Sends the text to stderr by default, override to change the behavior."""
-        sys.stderr.write(msg)
+        write_str(sys.stderr, msg)
 
 
 class ConfigEnvironmentMixin(Environment):
@@ -2128,6 +2340,14 @@ def __init__(self, config, **kw):
 class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
     """An Environment that reads most of its information from "git config"."""
 
+    @staticmethod
+    def forbid_field_values(name, value, forbidden):
+        for forbidden_val in forbidden:
+            if value is not None and value.lower() == forbidden:
+                raise ConfigurationException(
+                    '"%s" is not an allowed setting for %s' % (value, name)
+                    )
+
     def __init__(self, config, **kw):
         super(ConfigOptionsEnvironmentMixin, self).__init__(
             config=config, **kw
@@ -2144,14 +2364,26 @@ def __init__(self, config, **kw):
             if val is not None:
                 setattr(self, var, val)
 
+        commit_email_format = config.get('commitEmailFormat')
+        if commit_email_format is not None:
+            if commit_email_format != "html" and commit_email_format != "text":
+                self.log_warning(
+                    '*** Unknown value for multimailhook.commitEmailFormat: %s\n' %
+                    commit_email_format +
+                    '*** Expected either "text" or "html".  Ignoring.\n'
+                    )
+            else:
+                self.commit_email_format = commit_email_format
+
         maxcommitemails = config.get('maxcommitemails')
         if maxcommitemails is not None:
             try:
                 self.maxcommitemails = int(maxcommitemails)
             except ValueError:
                 self.log_warning(
-                    '*** Malformed value for multimailhook.maxCommitEmails: %s\n' % maxcommitemails
-                    + '*** Expected a number.  Ignoring.\n'
+                    '*** Malformed value for multimailhook.maxCommitEmails: %s\n'
+                    % maxcommitemails +
+                    '*** Expected a number.  Ignoring.\n'
                     )
 
         diffopts = config.get('diffopts')
@@ -2170,32 +2402,44 @@ def __init__(self, config, **kw):
         if commitlogopts is not None:
             self.commitlogopts = shlex.split(commitlogopts)
 
+        date_substitute = config.get('dateSubstitute')
+        if date_substitute == 'none':
+            self.date_substitute = None
+        elif date_substitute is not None:
+            self.date_substitute = date_substitute
+
         reply_to = config.get('replyTo')
         self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
-        if (
-                self.__reply_to_refchange is not None
-                and self.__reply_to_refchange.lower() == 'author'
-                ):
-            raise ConfigurationException(
-                '"author" is not an allowed setting for replyToRefchange'
-                )
+        self.forbid_field_values('replyToRefchange',
+                                 self.__reply_to_refchange,
+                                 ['author'])
         self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
 
+        from_addr = self.config.get('from')
+        self.from_refchange = config.get('fromRefchange')
+        self.forbid_field_values('fromRefchange',
+                                 self.from_refchange,
+                                 ['author', 'none'])
+        self.from_commit = config.get('fromCommit')
+        self.forbid_field_values('fromCommit',
+                                 self.from_commit,
+                                 ['none'])
+
         combine = config.get_bool('combineWhenSingleCommit')
         if combine is not None:
             self.combine_when_single_commit = combine
 
     def get_administrator(self):
         return (
-            self.config.get('administrator')
-            or self.get_sender()
-            or super(ConfigOptionsEnvironmentMixin, self).get_administrator()
+            self.config.get('administrator') or
+            self.get_sender() or
+            super(ConfigOptionsEnvironmentMixin, self).get_administrator()
             )
 
     def get_repo_shortname(self):
         return (
-            self.config.get('reponame')
-            or super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
+            self.config.get('reponame') or
+            super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
             )
 
     def get_emailprefix(self):
@@ -2212,33 +2456,42 @@ def get_emailprefix(self):
     def get_sender(self):
         return self.config.get('envelopesender')
 
-    def get_fromaddr(self):
+    def process_addr(self, addr, change):
+        if addr.lower() == 'author':
+            if hasattr(change, 'author'):
+                return change.author
+            else:
+                return None
+        elif addr.lower() == 'pusher':
+            return self.get_pusher_email()
+        elif addr.lower() == 'none':
+            return None
+        else:
+            return addr
+
+    def get_fromaddr(self, change=None):
         fromaddr = self.config.get('from')
+        if change:
+            alt_fromaddr = change.get_alt_fromaddr()
+            if alt_fromaddr:
+                fromaddr = alt_fromaddr
+        if fromaddr:
+            fromaddr = self.process_addr(fromaddr, change)
         if fromaddr:
             return fromaddr
-        return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr()
+        return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr(change)
 
     def get_reply_to_refchange(self, refchange):
         if self.__reply_to_refchange is None:
             return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_refchange(refchange)
-        elif self.__reply_to_refchange.lower() == 'pusher':
-            return self.get_pusher_email()
-        elif self.__reply_to_refchange.lower() == 'none':
-            return None
         else:
-            return self.__reply_to_refchange
+            return self.process_addr(self.__reply_to_refchange, refchange)
 
     def get_reply_to_commit(self, revision):
         if self.__reply_to_commit is None:
             return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision)
-        elif self.__reply_to_commit.lower() == 'author':
-            return revision.author
-        elif self.__reply_to_commit.lower() == 'pusher':
-            return self.get_pusher_email()
-        elif self.__reply_to_commit.lower() == 'none':
-            return None
         else:
-            return self.__reply_to_commit
+            return self.process_addr(self.__reply_to_commit, revision)
 
     def get_scancommitforcc(self):
         return self.config.get('scancommitforcc')
@@ -2270,12 +2523,14 @@ def __init__(self, strict_utf8=True, emailmaxlinelength=500, **kw):
     def filter_body(self, lines):
         lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines)
         if self.__strict_utf8:
-            lines = (line.decode(ENCODING, 'replace') for line in lines)
+            if not PYTHON3:
+                lines = (line.decode(ENCODING, 'replace') for line in lines)
             # Limit the line length in Unicode-space to avoid
             # splitting characters:
             if self.__emailmaxlinelength:
                 lines = limit_linelength(lines, self.__emailmaxlinelength)
-            lines = (line.encode(ENCODING, 'replace') for line in lines)
+            if not PYTHON3:
+                lines = (line.encode(ENCODING, 'replace') for line in lines)
         elif self.__emailmaxlinelength:
             lines = limit_linelength(lines, self.__emailmaxlinelength)
 
@@ -2404,10 +2659,10 @@ def __init__(
         # actual *contents* of the change being reported, we only
         # choose based on the *type* of the change.  Therefore we can
         # compute them once and for all:
-        if not (refchange_recipients
-                or announce_recipients
-                or revision_recipients
-                or scancommitforcc):
+        if not (refchange_recipients or
+                announce_recipients or
+                revision_recipients or
+                scancommitforcc):
             raise ConfigurationException('No email recipients configured!')
         self.__refchange_recipients = refchange_recipients
         self.__announce_recipients = announce_recipients
@@ -2457,13 +2712,104 @@ def _get_recipients(self, config, *names):
         found, raise a ConfigurationException."""
 
         for name in names:
-            retval = config.get_recipients(name)
-            if retval is not None:
-                return retval
+            lines = config.get_all(name)
+            if lines is not None:
+                lines = [line.strip() for line in lines]
+                # Single "none" is a special value equivalen to empty string.
+                if lines == ['none']:
+                    lines = ['']
+                return ', '.join(lines)
         else:
             return ''
 
 
+class StaticRefFilterEnvironmentMixin(Environment):
+    """Set branch filter statically based on constructor parameters."""
+
+    def __init__(self, ref_filter_incl_regex, ref_filter_excl_regex,
+                 ref_filter_do_send_regex, ref_filter_dont_send_regex,
+                 **kw):
+        super(StaticRefFilterEnvironmentMixin, self).__init__(**kw)
+
+        if ref_filter_incl_regex and ref_filter_excl_regex:
+            raise ConfigurationException(
+                "Cannot specify both a ref inclusion and exclusion regex.")
+        self.__is_inclusion_filter = bool(ref_filter_incl_regex)
+        default_exclude = self.get_default_ref_ignore_regex()
+        if ref_filter_incl_regex:
+            ref_filter_regex = ref_filter_incl_regex
+        elif ref_filter_excl_regex:
+            ref_filter_regex = ref_filter_excl_regex + '|' + default_exclude
+        else:
+            ref_filter_regex = default_exclude
+        try:
+            self.__compiled_regex = re.compile(ref_filter_regex)
+        except Exception:
+            raise ConfigurationException(
+                'Invalid Ref Filter Regex "%s": %s' % (ref_filter_regex, sys.exc_info()[1]))
+
+        if ref_filter_do_send_regex and ref_filter_dont_send_regex:
+            raise ConfigurationException(
+                "Cannot specify both a ref doSend and dontSend regex.")
+        if ref_filter_do_send_regex or ref_filter_dont_send_regex:
+            self.__is_do_send_filter = bool(ref_filter_do_send_regex)
+            if ref_filter_incl_regex:
+                ref_filter_send_regex = ref_filter_incl_regex
+            elif ref_filter_excl_regex:
+                ref_filter_send_regex = ref_filter_excl_regex
+            else:
+                ref_filter_send_regex = '.*'
+                self.__is_do_send_filter = True
+            try:
+                self.__send_compiled_regex = re.compile(ref_filter_send_regex)
+            except Exception:
+                raise ConfigurationException(
+                    'Invalid Ref Filter Regex "%s": %s' %
+                    (ref_filter_send_regex, sys.exc_info()[1]))
+        else:
+            self.__send_compiled_regex = self.__compiled_regex
+            self.__is_do_send_filter = self.__is_inclusion_filter
+
+    def get_ref_filter_regex(self, send_filter=False):
+        if send_filter:
+            return self.__send_compiled_regex, self.__is_do_send_filter
+        else:
+            return self.__compiled_regex, self.__is_inclusion_filter
+
+
+class ConfigRefFilterEnvironmentMixin(
+        ConfigEnvironmentMixin,
+        StaticRefFilterEnvironmentMixin
+        ):
+    """Determine branch filtering statically based on config."""
+
+    def _get_regex(self, config, key):
+        """Get a list of whitespace-separated regex. The refFilter* config
+        variables are multivalued (hence the use of get_all), and we
+        allow each entry to be a whitespace-separated list (hence the
+        split on each line). The whole thing is glued into a single regex."""
+        values = config.get_all(key)
+        if values is None:
+            return values
+        items = []
+        for line in values:
+            for i in line.split():
+                items.append(i)
+        if items == []:
+            return None
+        return '|'.join(items)
+
+    def __init__(self, config, **kw):
+        super(ConfigRefFilterEnvironmentMixin, self).__init__(
+            config=config,
+            ref_filter_incl_regex=self._get_regex(config, 'refFilterInclusionRegex'),
+            ref_filter_excl_regex=self._get_regex(config, 'refFilterExclusionRegex'),
+            ref_filter_do_send_regex=self._get_regex(config, 'refFilterDoSendRegex'),
+            ref_filter_dont_send_regex=self._get_regex(config, 'refFilterDontSendRegex'),
+            **kw
+            )
+
+
 class ProjectdescEnvironmentMixin(Environment):
     """Make a "projectdesc" value available for templates.
 
@@ -2499,6 +2845,7 @@ class GenericEnvironment(
         ComputeFQDNEnvironmentMixin,
         ConfigFilterLinesEnvironmentMixin,
         ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
         PusherDomainEnvironmentMixin,
         ConfigOptionsEnvironmentMixin,
         GenericEnvironmentMixin,
@@ -2513,14 +2860,14 @@ def get_repo_shortname(self):
         # repo_shortname (though it's probably not as good as a value
         # the user might have explicitly put in his config).
         return (
-            self.osenv.get('GL_REPO', None)
-            or super(GitoliteEnvironmentMixin, self).get_repo_shortname()
+            self.osenv.get('GL_REPO', None) or
+            super(GitoliteEnvironmentMixin, self).get_repo_shortname()
             )
 
     def get_pusher(self):
         return self.osenv.get('GL_USER', 'unknown user')
 
-    def get_fromaddr(self):
+    def get_fromaddr(self, change=None):
         GL_USER = self.osenv.get('GL_USER')
         if GL_USER is not None:
             # Find the path to gitolite.conf.  Note that gitolite v3
@@ -2536,9 +2883,9 @@ def get_fromaddr(self):
                 f = open(GL_CONF, 'rU')
                 try:
                     in_user_emails_section = False
-                    re_template = r'^\s*#\s*{}\s*$'
+                    re_template = r'^\s*#\s*%s\s*$'
                     re_begin, re_user, re_end = (
-                        re.compile(re_template.format(x))
+                        re.compile(re_template % x)
                         for x in (
                             r'BEGIN\s+USER\s+EMAILS',
                             re.escape(GL_USER) + r'\s+(.*)',
@@ -2557,7 +2904,7 @@ def get_fromaddr(self):
                             return m.group(1)
                 finally:
                     f.close()
-        return super(GitoliteEnvironmentMixin, self).get_fromaddr()
+        return super(GitoliteEnvironmentMixin, self).get_fromaddr(change)
 
 
 class IncrementalDateTime(object):
@@ -2570,8 +2917,9 @@ class IncrementalDateTime(object):
 
     def __init__(self):
         self.time = time.time()
+        self.next = self.__next__  # Python 2 backward compatibility
 
-    def next(self):
+    def __next__(self):
         formatted = formatdate(self.time, True)
         self.time += 1
         return formatted
@@ -2583,6 +2931,7 @@ class GitoliteEnvironment(
         ComputeFQDNEnvironmentMixin,
         ConfigFilterLinesEnvironmentMixin,
         ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
         PusherDomainEnvironmentMixin,
         ConfigOptionsEnvironmentMixin,
         GitoliteEnvironmentMixin,
@@ -2591,6 +2940,117 @@ class GitoliteEnvironment(
     pass
 
 
+class StashEnvironmentMixin(Environment):
+    def __init__(self, user=None, repo=None, **kw):
+        super(StashEnvironmentMixin, self).__init__(**kw)
+        self.__user = user
+        self.__repo = repo
+
+    def get_repo_shortname(self):
+        return self.__repo
+
+    def get_pusher(self):
+        return re.match('(.*?)\s*<', self.__user).group(1)
+
+    def get_pusher_email(self):
+        return self.__user
+
+    def get_fromaddr(self, change=None):
+        return self.__user
+
+
+class StashEnvironment(
+        StashEnvironmentMixin,
+        ProjectdescEnvironmentMixin,
+        ConfigMaxlinesEnvironmentMixin,
+        ComputeFQDNEnvironmentMixin,
+        ConfigFilterLinesEnvironmentMixin,
+        ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
+        PusherDomainEnvironmentMixin,
+        ConfigOptionsEnvironmentMixin,
+        Environment,
+        ):
+    pass
+
+
+class GerritEnvironmentMixin(Environment):
+    def __init__(self, project=None, submitter=None, update_method=None, **kw):
+        super(GerritEnvironmentMixin, self).__init__(**kw)
+        self.__project = project
+        self.__submitter = submitter
+        self.__update_method = update_method
+        "Make an 'update_method' value available for templates."
+        self.COMPUTED_KEYS += ['update_method']
+
+    def get_repo_shortname(self):
+        return self.__project
+
+    def get_pusher(self):
+        if self.__submitter:
+            if self.__submitter.find('<') != -1:
+                # Submitter has a configured email, we transformed
+                # __submitter into an RFC 2822 string already.
+                return re.match('(.*?)\s*<', self.__submitter).group(1)
+            else:
+                # Submitter has no configured email, it's just his name.
+                return self.__submitter
+        else:
+            # If we arrive here, this means someone pushed "Submit" from
+            # the gerrit web UI for the CR (or used one of the programmatic
+            # APIs to do the same, such as gerrit review) and the
+            # merge/push was done by the Gerrit user.  It was technically
+            # triggered by someone else, but sadly we have no way of
+            # determining who that someone else is at this point.
+            return 'Gerrit'  # 'unknown user'?
+
+    def get_pusher_email(self):
+        if self.__submitter:
+            return self.__submitter
+        else:
+            return super(GerritEnvironmentMixin, self).get_pusher_email()
+
+    def get_fromaddr(self, change=None):
+        if self.__submitter and self.__submitter.find('<') != -1:
+            return self.__submitter
+        else:
+            return super(GerritEnvironmentMixin, self).get_fromaddr(change)
+
+    def get_default_ref_ignore_regex(self):
+        default = super(GerritEnvironmentMixin, self).get_default_ref_ignore_regex()
+        return default + '|^refs/changes/|^refs/cache-automerge/|^refs/meta/'
+
+    def get_revision_recipients(self, revision):
+        # Merge commits created by Gerrit when users hit "Submit this patchset"
+        # in the Web UI (or do equivalently with REST APIs or the gerrit review
+        # command) are not something users want to see an individual email for.
+        # Filter them out.
+        committer = read_git_output(['log', '--no-walk', '--format=%cN',
+                                     revision.rev.sha1])
+        if committer == 'Gerrit Code Review':
+            return []
+        else:
+            return super(GerritEnvironmentMixin, self).get_revision_recipients(revision)
+
+    def get_update_method(self):
+        return self.__update_method
+
+
+class GerritEnvironment(
+        GerritEnvironmentMixin,
+        ProjectdescEnvironmentMixin,
+        ConfigMaxlinesEnvironmentMixin,
+        ComputeFQDNEnvironmentMixin,
+        ConfigFilterLinesEnvironmentMixin,
+        ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
+        PusherDomainEnvironmentMixin,
+        ConfigOptionsEnvironmentMixin,
+        Environment,
+        ):
+    pass
+
+
 class Push(object):
     """Represent an entire push (i.e., a group of ReferenceChanges).
 
@@ -2673,10 +3133,11 @@ class is to figure out these things, and to make sure that new
             ])
         )
 
-    def __init__(self, changes, ignore_other_refs=False):
+    def __init__(self, environment, changes, ignore_other_refs=False):
         self.changes = sorted(changes, key=self._sort_key)
         self.__other_ref_sha1s = None
         self.__cached_commits_spec = {}
+        self.environment = environment
 
         if ignore_other_refs:
             self.__other_ref_sha1s = set()
@@ -2703,10 +3164,14 @@ def _other_ref_sha1s(self):
                 '%(objectname) %(objecttype) %(refname)\n'
                 '%(*objectname) %(*objecttype) %(refname)'
                 )
+            ref_filter_regex, is_inclusion_filter = \
+                self.environment.get_ref_filter_regex()
             for line in read_git_lines(
                     ['for-each-ref', '--format=%s' % (fmt,)]):
                 (sha1, type, name) = line.split(' ', 2)
-                if sha1 and type == 'commit' and name not in updated_refs:
+                if (sha1 and type == 'commit' and
+                        name not in updated_refs and
+                        include_ref(name, ref_filter_regex, is_inclusion_filter)):
                     sha1s.add(sha1)
 
             self.__other_ref_sha1s = sha1s
@@ -2856,7 +3321,7 @@ def send_emails(self, mailer, body_filter=None):
                 if not change.environment.quiet:
                     change.environment.log_msg(
                         'Sending notification emails to: %s\n' % (change.recipients,))
-                extra_values = {'send_date': send_date.next()}
+                extra_values = {'send_date': next(send_date)}
 
                 rev = change.send_single_combined_email(sha1s)
                 if rev:
@@ -2876,9 +3341,9 @@ def send_emails(self, mailer, body_filter=None):
             max_emails = change.environment.maxcommitemails
             if max_emails and len(sha1s) > max_emails:
                 change.environment.log_warning(
-                    '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s)
-                    + '*** Try setting multimailhook.maxCommitEmails to a greater value\n'
-                    '*** Currently, multimailhook.maxCommitEmails=%d\n' % max_emails
+                    '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s) +
+                    '*** Try setting multimailhook.maxCommitEmails to a greater value\n' +
+                    '*** Currently, multimailhook.maxCommitEmails=%d\n' % max_emails
                     )
                 return
 
@@ -2889,7 +3354,7 @@ def send_emails(self, mailer, body_filter=None):
                     rev.recipients = rev.cc_recipients
                     rev.cc_recipients = None
                 if rev.recipients:
-                    extra_values = {'send_date': send_date.next()}
+                    extra_values = {'send_date': next(send_date)}
                     mailer.send(
                         rev.generate_email(self, body_filter, extra_values),
                         rev.recipients,
@@ -2904,18 +3369,33 @@ def send_emails(self, mailer, body_filter=None):
                 )
 
 
+def include_ref(refname, ref_filter_regex, is_inclusion_filter):
+    does_match = bool(ref_filter_regex.search(refname))
+    if is_inclusion_filter:
+        return does_match
+    else:  # exclusion filter -- we include the ref if the regex doesn't match
+        return not does_match
+
+
 def run_as_post_receive_hook(environment, mailer):
+    ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(True)
     changes = []
     for line in sys.stdin:
         (oldrev, newrev, refname) = line.strip().split(' ', 2)
+        if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
+            continue
         changes.append(
             ReferenceChange.create(environment, oldrev, newrev, refname)
             )
-    push = Push(changes)
-    push.send_emails(mailer, body_filter=environment.filter_body)
+    if changes:
+        push = Push(environment, changes)
+        push.send_emails(mailer, body_filter=environment.filter_body)
 
 
 def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
+    ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(True)
+    if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
+        return
     changes = [
         ReferenceChange.create(
             environment,
@@ -2924,7 +3404,7 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=
             refname,
             ),
         ]
-    push = Push(changes, force_send)
+    push = Push(environment, changes, force_send)
     push.send_emails(mailer, body_filter=environment.filter_body)
 
 
@@ -2953,8 +3433,8 @@ def choose_mailer(config, environment):
         mailer = SendMailer(command=command, envelopesender=environment.get_sender())
     else:
         environment.log_error(
-            'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer
-            'please use one of "smtp" or "sendmail".\n'
+            'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer +
+            'please use one of "smtp" or "sendmail".\n'
             )
         sys.exit(1)
     return mailer
@@ -2963,14 +3443,18 @@ def choose_mailer(config, environment):
 KNOWN_ENVIRONMENTS = {
     'generic': GenericEnvironmentMixin,
     'gitolite': GitoliteEnvironmentMixin,
+    'stash': StashEnvironmentMixin,
+    'gerrit': GerritEnvironmentMixin,
     }
 
 
-def choose_environment(config, osenv=None, env=None, recipients=None):
+def choose_environment(config, osenv=None, env=None, recipients=None,
+                       hook_info=None):
     if not osenv:
         osenv = os.environ
 
     environment_mixins = [
+        ConfigRefFilterEnvironmentMixin,
         ProjectdescEnvironmentMixin,
         ConfigMaxlinesEnvironmentMixin,
         ComputeFQDNEnvironmentMixin,
@@ -2992,7 +3476,15 @@ def choose_environment(config, osenv=None, env=None, recipients=None):
         else:
             env = 'generic'
 
-    environment_mixins.append(KNOWN_ENVIRONMENTS[env])
+    environment_mixins.insert(0, KNOWN_ENVIRONMENTS[env])
+
+    if env == 'stash':
+        environment_kw['user'] = hook_info['stash_user']
+        environment_kw['repo'] = hook_info['stash_repo']
+    elif env == 'gerrit':
+        environment_kw['project'] = hook_info['project']
+        environment_kw['submitter'] = hook_info['submitter']
+        environment_kw['update_method'] = hook_info['update_method']
 
     if recipients:
         environment_mixins.insert(0, StaticRecipientsEnvironmentMixin)
@@ -3011,6 +3503,116 @@ def choose_environment(config, osenv=None, env=None, recipients=None):
     return environment_klass(**environment_kw)
 
 
+def get_version():
+    oldcwd = os.getcwd()
+    try:
+        try:
+            os.chdir(os.path.dirname(os.path.realpath(__file__)))
+            git_version = read_git_output(['describe', '--tags', 'HEAD'])
+            if git_version == __version__:
+                return git_version
+            else:
+                return '%s (%s)' % (__version__, git_version)
+        except:
+            pass
+    finally:
+        os.chdir(oldcwd)
+    return __version__
+
+
+def compute_gerrit_options(options, args, required_gerrit_options):
+    if None in required_gerrit_options:
+        raise SystemExit("Error: Specify all of --oldrev, --newrev, --refname, "
+                         "and --project; or none of them.")
+
+    if options.environment not in (None, 'gerrit'):
+        raise SystemExit("Non-gerrit environments incompatible with --oldrev, "
+                         "--newrev, --refname, and --project")
+    options.environment = 'gerrit'
+
+    if args:
+        raise SystemExit("Error: Positional parameters not allowed with "
+                         "--oldrev, --newrev, and --refname.")
+
+    # Gerrit oddly omits 'refs/heads/' in the refname when calling
+    # ref-updated hook; put it back.
+    git_dir = get_git_dir()
+    if (not os.path.exists(os.path.join(git_dir, options.refname)) and
+        os.path.exists(os.path.join(git_dir, 'refs', 'heads',
+                                    options.refname))):
+        options.refname = 'refs/heads/' + options.refname
+
+    # Convert each string option unicode for Python3.
+    if PYTHON3:
+        opts = ['environment', 'recipients', 'oldrev', 'newrev', 'refname',
+                'project', 'submitter', 'stash-user', 'stash-repo']
+        for opt in opts:
+            if not hasattr(options, opt):
+                continue
+            obj = getattr(options, opt)
+            if obj:
+                enc = obj.encode('utf-8', 'surrogateescape')
+                dec = enc.decode('utf-8', 'replace')
+                setattr(options, opt, dec)
+
+    # New revisions can appear in a gerrit repository either due to someone
+    # pushing directly (in which case options.submitter will be set), or they
+    # can press "Submit this patchset" in the web UI for some CR (in which
+    # case options.submitter will not be set and gerrit will not have provided
+    # us the information about who pressed the button).
+    #
+    # Note for the nit-picky: I'm lumping in REST API calls and the ssh
+    # gerrit review command in with "Submit this patchset" button, since they
+    # have the same effect.
+    if options.submitter:
+        update_method = 'pushed'
+        # The submitter argument is almost an RFC 2822 email address; change it
+        # from 'User Name (email@domain)' to 'User Name <email@domain>' so it is
+        options.submitter = options.submitter.replace('(', '<').replace(')', '>')
+    else:
+        update_method = 'submitted'
+        # Gerrit knew who submitted this patchset, but threw that information
+        # away when it invoked this hook.  However, *IF* Gerrit created a
+        # merge to bring the patchset in (project 'Submit Type' is either
+        # "Always Merge", or is "Merge if Necessary" and happens to be
+        # necessary for this particular CR), then it will have the committer
+        # of that merge be 'Gerrit Code Review' and the author will be the
+        # person who requested the submission of the CR.  Since this is fairly
+        # likely for most gerrit installations (of a reasonable size), it's
+        # worth the extra effort to try to determine the actual submitter.
+        rev_info = read_git_lines(['log', '--no-walk', '--merges',
+                                   '--format=%cN%n%aN <%aE>', options.newrev])
+        if rev_info and rev_info[0] == 'Gerrit Code Review':
+            options.submitter = rev_info[1]
+
+    # We pass back refname, oldrev, newrev as args because then the
+    # gerrit ref-updated hook is much like the git update hook
+    return (options,
+            [options.refname, options.oldrev, options.newrev],
+            {'project': options.project, 'submitter': options.submitter,
+             'update_method': update_method})
+
+
+def check_hook_specific_args(options, args):
+    # First check for stash arguments
+    if (options.stash_user is None) != (options.stash_repo is None):
+        raise SystemExit("Error: Specify both of --stash-user and "
+                         "--stash-repo or neither.")
+    if options.stash_user:
+        options.environment = 'stash'
+        return options, args, {'stash_user': options.stash_user,
+                               'stash_repo': options.stash_repo}
+
+    # Finally, check for gerrit specific arguments
+    required_gerrit_options = (options.oldrev, options.newrev, options.refname,
+                               options.project)
+    if required_gerrit_options != (None,) * 4:
+        return compute_gerrit_options(options, args, required_gerrit_options)
+
+    # No special options in use, just return what we started with
+    return options, args, {}
+
+
 def main(args):
     parser = optparse.OptionParser(
         description=__doc__,
@@ -3019,7 +3621,7 @@ def main(args):
 
     parser.add_option(
         '--environment', '--env', action='store', type='choice',
-        choices=['generic', 'gitolite'], default=None,
+        choices=list(KNOWN_ENVIRONMENTS.keys()), default=None,
         help=(
             'Choose type of environment is in use.  Default is taken from '
             'multimailhook.environment if set; otherwise "generic".'
@@ -3048,8 +3650,58 @@ def main(args):
             'detection in this mode.'
             ),
         )
+    parser.add_option(
+        '-c', metavar="<name>=<value>", action='append',
+        help=(
+            'Pass a configuration parameter through to git.  The value given '
+            'will override values from configuration files.  See the -c option '
+            'of git(1) for more details.  (Only works with git >= 1.7.3)'
+            ),
+        )
+    parser.add_option(
+        '--version', '-v', action='store_true', default=False,
+        help=(
+            "Display git-multimail's version"
+            ),
+        )
+    # The following options permit this script to be run as a gerrit
+    # ref-updated hook.  See e.g.
+    # code.google.com/p/gerrit/source/browse/Documentation/config-hooks.txt
+    # We suppress help for these items, since these are specific to gerrit,
+    # and we don't want users directly using them any way other than how the
+    # gerrit ref-updated hook is called.
+    parser.add_option('--oldrev', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--newrev', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--refname', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--project', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--submitter', action='store', help=optparse.SUPPRESS_HELP)
+
+    # The following allow this to be run as a stash asynchronous post-receive
+    # hook (almost identical to a git post-receive hook but triggered also for
+    # merges of pull requests from the UI).  We suppress help for these items,
+    # since these are specific to stash.
+    parser.add_option('--stash-user', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--stash-repo', action='store', help=optparse.SUPPRESS_HELP)
 
     (options, args) = parser.parse_args(args)
+    (options, args, hook_info) = check_hook_specific_args(options, args)
+
+    if options.version:
+        sys.stdout.write('git-multimail version ' + get_version() + '\n')
+        return
+
+    if options.c:
+        parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
+        if parameters:
+            parameters += ' '
+        # git expects GIT_CONFIG_PARAMETERS to be of the form
+        #    "'name1=value1' 'name2=value2' 'name3=value3'"
+        # including everything inside the double quotes (but not the double
+        # quotes themselves).  Spacing is critical.  Also, if a value contains
+        # a literal single quote that quote must be represented using the
+        # four character sequence: '\''
+        parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in options.c)
+        os.environ['GIT_CONFIG_PARAMETERS'] = parameters
 
     config = Config('multimailhook')
 
@@ -3058,6 +3710,7 @@ def main(args):
             config, osenv=os.environ,
             env=options.environment,
             recipients=options.recipients,
+            hook_info=hook_info,
             )
 
         if options.show_env:
@@ -3080,9 +3733,20 @@ def main(args):
             run_as_update_hook(environment, mailer, refname, oldrev, newrev, options.force_send)
         else:
             run_as_post_receive_hook(environment, mailer)
-    except ConfigurationException, e:
-        sys.exit(str(e))
-
+    except ConfigurationException:
+        sys.exit(sys.exc_info()[1])
+    except Exception:
+        t, e, tb = sys.exc_info()
+        import traceback
+        sys.stdout.write('\n')
+        sys.stdout.write('Exception \'' + t.__name__ +
+                         '\' raised. Please report this as a bug to\n')
+        sys.stdout.write('https://github.com/git-multimail/git-multimail/issues\n')
+        sys.stdout.write('with the information below:\n\n')
+        sys.stdout.write('git-multimail version ' + get_version() + '\n')
+        sys.stdout.write('Python version ' + sys.version + '\n')
+        traceback.print_exc(file=sys.stdout)
+        sys.exit(1)
 
 if __name__ == '__main__':
     main(sys.argv[1:])
index d0e9b392013b1dc0f8beeaead3a08d91ce5c3f03..992657bbdc3545070496fbb43a287679cba03ca1 100755 (executable)
@@ -1,4 +1,4 @@
-#! /usr/bin/env python2
+#! /usr/bin/env python
 
 """Migrate a post-receive-email configuration to be usable with git_multimail.py.
 
index 43f7b6b635b15b1dd3648d2f91b78d4e31acf504..9975df7107ac29be2b2ac861b90dfc0730ec7b97 100755 (executable)
@@ -1,4 +1,4 @@
-#! /usr/bin/env python2
+#! /usr/bin/env python
 
 """Example post-receive hook based on git-multimail.
 
@@ -42,7 +42,6 @@ import os
 
 import git_multimail
 
-
 # It is possible to modify the output templates here; e.g.:
 
 #git_multimail.FOOTER_TEMPLATE = """\
@@ -61,8 +60,9 @@ config = git_multimail.Config('multimailhook')
 try:
     environment = git_multimail.GenericEnvironment(config=config)
     #environment = git_multimail.GitoliteEnvironment(config=config)
-except git_multimail.ConfigurationException, e:
-    sys.exit(str(e))
+except git_multimail.ConfigurationException:
+    sys.stderr.write('*** %s\n' % sys.exc_info()[1])
+    sys.exit(1)
 
 
 # Choose the method of sending emails based on the git config:
index 9f065718513c5c1e82d355aa9dab40ee421b765e..308b777b0aa43a7466fb3402cc67c1f023f57a7a 100755 (executable)
@@ -648,7 +648,7 @@ cmd_split()
                debug "Merging split branch into HEAD..."
                latest_old=$(cache_get latest_old)
                git merge -s ours \
-                       -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+                       -m "$(rejoin_msg "$dir" $latest_old $latest_new)" \
                        $latest_new >&2 || exit $?
        fi
        if [ -n "$branch" ]; then
@@ -735,7 +735,7 @@ cmd_push()
            refspec=$2
            echo "git push using: " $repository $refspec
            localrev=$(git subtree split --prefix="$prefix") || die
-           git push $repository $localrev:refs/heads/$refspec
+           git push "$repository" $localrev:refs/heads/$refspec
        else
            die "'$dir' must already exist. Try 'git subtree add'."
        fi
index 90519823be381390b7f89a1eb2f6007f1e8c86f4..dfbe443deaf1739e47d3f0f791331a3ee6773174 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 #
 # Copyright (c) 2012 Avery Pennaraum
+# Copyright (c) 2015 Alexey Shumkin
 #
 test_description='Basic porcelain support for subtrees
 
@@ -32,25 +33,6 @@ check_equal()
        fi
 }
 
-fixnl()
-{
-       t=""
-       while read x; do
-               t="$t$x "
-       done
-       echo $t
-}
-
-multiline()
-{
-       while read x; do
-               set -- $x
-               for d in "$@"; do
-                       echo "$d"
-               done
-       done
-}
-
 undo()
 {
        git reset --hard HEAD~
@@ -62,11 +44,11 @@ last_commit_message()
 }
 
 test_expect_success 'init subproj' '
-       test_create_repo subproj
+       test_create_repo "sub proj"
 '
 
 # To the subproject!
-cd subproj
+cd ./"sub proj"
 
 test_expect_success 'add sub1' '
        create sub1 &&
@@ -106,39 +88,39 @@ test_expect_success 'add main4' '
 '
 
 test_expect_success 'fetch subproj history' '
-       git fetch ./subproj sub1 &&
+       git fetch ./"sub proj" sub1 &&
        git branch sub1 FETCH_HEAD
 '
 
 test_expect_success 'no subtree exists in main tree' '
-       test_must_fail git subtree merge --prefix=subdir sub1
+       test_must_fail git subtree merge --prefix="sub dir" sub1
 '
 
 test_expect_success 'no pull from non-existant subtree' '
-       test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+       test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" sub1
 '
 
 test_expect_success 'check if --message works for add' '
-       git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+       git subtree add --prefix="sub dir" --message="Added subproject" sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject" &&
        undo
 '
 
 test_expect_success 'check if --message works as -m and --prefix as -P' '
-       git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
+       git subtree add -P "sub dir" -m "Added subproject using git subtree" sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
        undo
 '
 
 test_expect_success 'check if --message works with squash too' '
-       git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+       git subtree add -P "sub dir" -m "Added subproject with squash" --squash sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
        undo
 '
 
 test_expect_success 'add subproj to mainline' '
-       git subtree add --prefix=subdir/ FETCH_HEAD &&
-       check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+       git subtree add --prefix="sub dir"/ FETCH_HEAD &&
+       check_equal ''"$(last_commit_message)"'' "Add '"'sub dir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
 '
 
 # this shouldn't actually do anything, since FETCH_HEAD is already a parent
@@ -147,7 +129,7 @@ test_expect_success 'merge fetched subproj' '
 '
 
 test_expect_success 'add main-sub5' '
-       create subdir/main-sub5 &&
+       create "sub dir/main-sub5" &&
        git commit -m "main-sub5"
 '
 
@@ -157,29 +139,29 @@ test_expect_success 'add main6' '
 '
 
 test_expect_success 'add main-sub7' '
-       create subdir/main-sub7 &&
+       create "sub dir/main-sub7" &&
        git commit -m "main-sub7"
 '
 
 test_expect_success 'fetch new subproj history' '
-       git fetch ./subproj sub2 &&
+       git fetch ./"sub proj" sub2 &&
        git branch sub2 FETCH_HEAD
 '
 
 test_expect_success 'check if --message works for merge' '
-       git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+       git subtree merge --prefix="sub dir" -m "Merged changes from subproject" sub2 &&
        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
        undo
 '
 
 test_expect_success 'check if --message for merge works with squash too' '
-       git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
+       git subtree merge --prefix "sub dir" -m "Merged changes from subproject using squash" --squash sub2 &&
        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
        undo
 '
 
 test_expect_success 'merge new subproj history into subdir' '
-       git subtree merge --prefix=subdir FETCH_HEAD &&
+       git subtree merge --prefix="sub dir" FETCH_HEAD &&
        git branch pre-split &&
        check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" &&
        undo
@@ -208,53 +190,53 @@ test_expect_success 'Check that the <prefix> exists for a split' '
 '
 
 test_expect_success 'check if --message works for split+rejoin' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        git branch spl1 "$spl1" &&
        check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
        undo
 '
 
 test_expect_success 'check split with --branch' '
-       spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+       spl1=$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
        undo &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr1 &&
        check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
 '
 
 test_expect_success 'check hash of split' '
-       spl1=$(git subtree split --prefix subdir) &&
-       git subtree split --prefix subdir --branch splitbr1test &&
+       spl1=$(git subtree split --prefix "sub dir") &&
+       git subtree split --prefix "sub dir" --branch splitbr1test &&
        check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" &&
        new_hash=$(git rev-parse splitbr1test~2) &&
        check_equal ''"$new_hash"'' "$subdir_hash"
 '
 
 test_expect_success 'check split with --branch for an existing branch' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        undo &&
        git branch splitbr2 sub1 &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr2 &&
        check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
 '
 
 test_expect_success 'check split with --branch for an incompatible branch' '
-       test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+       test_must_fail git subtree split --prefix "sub dir" --onto FETCH_HEAD --branch subdir
 '
 
 test_expect_success 'check split+rejoin' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        undo &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
-       check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --rejoin &&
+       check_equal ''"$(last_commit_message)"'' "Split '"'"'sub dir/'"'"' into commit '"'"'"$spl1"'"'"'"
 '
 
 test_expect_success 'add main-sub8' '
-       create subdir/main-sub8 &&
+       create "sub dir/main-sub8" &&
        git commit -m "main-sub8"
 '
 
 # To the subproject!
-cd ./subproj
+cd ./"sub proj"
 
 test_expect_success 'merge split into subproj' '
        git fetch .. spl1 &&
@@ -271,22 +253,22 @@ test_expect_success 'add sub9' '
 cd ..
 
 test_expect_success 'split for sub8' '
-       split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' &&
+       split2=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir/" --rejoin)"'' &&
        git branch split2 "$split2"
 '
 
 test_expect_success 'add main-sub10' '
-       create subdir/main-sub10 &&
+       create "sub dir/main-sub10" &&
        git commit -m "main-sub10"
 '
 
 test_expect_success 'split for sub10' '
-       spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+       spl3=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --rejoin)"'' &&
        git branch spl3 "$spl3"
 '
 
 # To the subproject!
-cd ./subproj
+cd ./"sub proj"
 
 test_expect_success 'merge split into subproj' '
        git fetch .. spl3 &&
@@ -295,42 +277,64 @@ test_expect_success 'merge split into subproj' '
        git branch subproj-merge-spl3
 '
 
-chkm="main4 main6"
-chkms="main-sub10 main-sub5 main-sub7 main-sub8"
-chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
-chks="sub1 sub2 sub3 sub9"
-chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+chkm="main4
+main6"
+chkms="main-sub10
+main-sub5
+main-sub7
+main-sub8"
+chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chkms
+TXT
+)
+chks="sub1
+sub2
+sub3
+sub9"
+chks_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chks
+TXT
+)
 
 test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
-       subfiles=''"$(git ls-files | fixnl)"'' &&
-       check_equal "$subfiles" "$chkms $chks"
+       subfiles="$(git ls-files)" &&
+       check_equal "$subfiles" "$chkms
+$chks"
 '
-
 test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
-       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
-       check_equal "$allchanges" "$chkms $chks"
+       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+       check_equal "$allchanges" "$chkms
+$chks"
 '
 
 # Back to mainline
 cd ..
 
 test_expect_success 'pull from subproj' '
-       git fetch ./subproj subproj-merge-spl3 &&
+       git fetch ./"sub proj" subproj-merge-spl3 &&
        git branch subproj-merge-spl3 FETCH_HEAD &&
-       git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+       git subtree pull --prefix="sub dir" ./"sub proj" subproj-merge-spl3
 '
 
 test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
-       mainfiles=''"$(git ls-files | fixnl)"'' &&
-       check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+       mainfiles=$(git ls-files) &&
+       check_equal "$mainfiles" "$chkm
+$chkms_sub
+$chks_sub"
 '
 
 test_expect_success 'make sure each filename changed exactly once in the entire history' '
        # main-sub?? and /subdir/main-sub?? both change, because those are the
        # changes that were split into their own history.  And subdir/sub?? never
        # change, since they were *only* changed in the subtree branch.
-       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
-       check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+       check_equal "$allchanges" ''"$(cat <<TXT | sort
+$chkms
+$chkm
+$chks
+$chkms_sub
+TXT
+)"''
 '
 
 test_expect_success 'make sure the --rejoin commits never make it into subproj' '
@@ -377,7 +381,7 @@ cd ../main
 test_expect_success 'add sub as subdir in main' '
        git fetch ../sub master &&
        git branch sub2 FETCH_HEAD &&
-       git subtree add --prefix subdir sub2
+       git subtree add --prefix "sub dir" sub2
 '
 
 cd ../sub
@@ -392,16 +396,16 @@ cd ../main
 test_expect_success 'merge from sub' '
        git fetch ../sub master &&
        git branch sub3 FETCH_HEAD &&
-       git subtree merge --prefix subdir sub3
+       git subtree merge --prefix "sub dir" sub3
 '
 
 test_expect_success 'add main-sub4' '
-       create subdir/main-sub4 &&
+       create "sub dir/main-sub4" &&
        git commit -m "main-sub4"
 '
 
 test_expect_success 'split for main-sub4 without --onto' '
-       git subtree split --prefix subdir --branch mainsub4
+       git subtree split --prefix "sub dir" --branch mainsub4
 '
 
 # at this point, the new commit parent should be sub3 if it is not,
@@ -468,4 +472,50 @@ test_expect_success 'verify one file change per commit' '
        ))
 '
 
+# test push
+
+cd ../..
+
+mkdir test-push
+
+cd test-push
+
+test_expect_success 'init main' '
+       test_create_repo main
+'
+
+test_expect_success 'init sub' '
+       test_create_repo "sub project"
+'
+
+cd ./"sub project"
+
+test_expect_success 'add subproject' '
+       create "sub project" &&
+       git commit -m "Sub project: 1" &&
+       git branch sub-branch-1
+'
+
+cd ../main
+
+test_expect_success 'make first commit and add subproject' '
+       create "main-1" &&
+       git commit -m "main: 1" &&
+       git subtree add "../sub project" --prefix "sub dir" --message "Added subproject" sub-branch-1 &&
+       check_equal "$(last_commit_message)" "Added subproject"
+'
+
+test_expect_success 'make second commit to a subproject file and push it into a sub project' '
+       create "sub dir/sub1" &&
+       git commit -m "Sub project: 2" &&
+       git subtree push "../sub project" --prefix "sub dir" sub-branch-1
+'
+
+cd ../"sub project"
+
+test_expect_success 'Test second commit is pushed' '
+       git checkout sub-branch-1 &&
+       check_equal "$(last_commit_message)" "Sub project: 2"
+'
+
 test_done
index f3bd3e93fb2ecf95413db3c53d7e686cd03d1e69..814e814438b7c0f4f84850787670766fb4765f08 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1289,7 +1289,8 @@ static struct stream_filter *ident_filter(const unsigned char *sha1)
 {
        struct ident_filter *ident = xmalloc(sizeof(*ident));
 
-       sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+       xsnprintf(ident->ident, sizeof(ident->ident),
+                 ": %s $", sha1_to_hex(sha1));
        strbuf_init(&ident->left, 0);
        ident->filter.vtbl = &ident_vtbl;
        ident->state = 0;
index eef6fce4c72ab37d75a0f75129666009a6a1ee54..82715aa8b8c050591559a1c4f9f92b2e99602212 100644 (file)
@@ -2,7 +2,6 @@
 #include "tempfile.h"
 #include "credential.h"
 #include "unix-socket.h"
-#include "sigchain.h"
 #include "parse-options.h"
 
 static struct tempfile socket_file;
index f9eb296888c37c8f2a523f3941be027ddb36df3c..be70cd4da09ccab8850be41e19b0fc4b9a10f502 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -802,6 +802,7 @@ static void check_dead_children(void)
                        /* remove the child */
                        *cradle = blanket->next;
                        live_children--;
+                       child_process_clear(&blanket->cld);
                        free(blanket);
                } else
                        cradle = &blanket->next;
@@ -811,8 +812,6 @@ static char **cld_argv;
 static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
-       char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
-       char *env[] = { addrbuf, portbuf, NULL };
 
        if (max_connections && live_children >= max_connections) {
                kill_some_child();
@@ -826,27 +825,23 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
        }
 
        if (addr->sa_family == AF_INET) {
+               char buf[128] = "";
                struct sockaddr_in *sin_addr = (void *) addr;
-               inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf + 12,
-                   sizeof(addrbuf) - 12);
-               snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
-                   ntohs(sin_addr->sin_port));
+               inet_ntop(addr->sa_family, &sin_addr->sin_addr, buf, sizeof(buf));
+               argv_array_pushf(&cld.env_array, "REMOTE_ADDR=%s", buf);
+               argv_array_pushf(&cld.env_array, "REMOTE_PORT=%d",
+                                ntohs(sin_addr->sin_port));
 #ifndef NO_IPV6
        } else if (addr->sa_family == AF_INET6) {
+               char buf[128] = "";
                struct sockaddr_in6 *sin6_addr = (void *) addr;
-
-               char *buf = addrbuf + 12;
-               *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
-               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf,
-                   sizeof(addrbuf) - 13);
-               strcat(buf, "]");
-
-               snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
-                   ntohs(sin6_addr->sin6_port));
+               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(buf));
+               argv_array_pushf(&cld.env_array, "REMOTE_ADDR=[%s]", buf);
+               argv_array_pushf(&cld.env_array, "REMOTE_PORT=%d",
+                                ntohs(sin6_addr->sin6_port));
 #endif
        }
 
-       cld.env = (const char **)env;
        cld.argv = (const char **)cld_argv;
        cld.in = incoming;
        cld.out = dup(incoming);
@@ -901,7 +896,7 @@ static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
                inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len);
                break;
        default:
-               strcpy(ip, "<unknown>");
+               xsnprintf(ip, sizeof(ip), "<unknown>");
        }
        return ip;
 }
@@ -916,7 +911,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
        int gai;
        long flags;
 
-       sprintf(pbuf, "%d", listen_port);
+       xsnprintf(pbuf, sizeof(pbuf), "%d", listen_port);
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
diff --git a/date.c b/date.c
index 8f9156909b8d9d2cad6425aa44deebdcd01b2168..7c9f76998ac7a00fb5f8781dd8e460f899edbfac 100644 (file)
--- a/date.c
+++ b/date.c
@@ -166,6 +166,7 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
        if (type == DATE_STRFTIME)
                die("BUG: cannot create anonymous strftime date_mode struct");
        mode.type = type;
+       mode.local = 0;
        return &mode;
 }
 
@@ -174,6 +175,9 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
        struct tm *tm;
        static struct strbuf timebuf = STRBUF_INIT;
 
+       if (mode->local)
+               tz = local_tzoffset(time);
+
        if (mode->type == DATE_RAW) {
                strbuf_reset(&timebuf);
                strbuf_addf(&timebuf, "%lu %+05d", time, tz);
@@ -189,9 +193,6 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
                return timebuf.buf;
        }
 
-       if (mode->type == DATE_LOCAL)
-               tz = local_tzoffset(time);
-
        tm = time_to_tm(time, tz);
        if (!tm) {
                tm = time_to_tm(0, 0);
@@ -232,7 +233,7 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                tm->tm_year + 1900,
-                               (mode->type == DATE_LOCAL) ? 0 : ' ',
+                               mode->local ? 0 : ' ',
                                tz);
        return timebuf.buf;
 }
@@ -770,31 +771,50 @@ int parse_date(const char *date, struct strbuf *result)
        return 0;
 }
 
+static enum date_mode_type parse_date_type(const char *format, const char **end)
+{
+       if (skip_prefix(format, "relative", end))
+               return DATE_RELATIVE;
+       if (skip_prefix(format, "iso8601-strict", end) ||
+           skip_prefix(format, "iso-strict", end))
+               return DATE_ISO8601_STRICT;
+       if (skip_prefix(format, "iso8601", end) ||
+           skip_prefix(format, "iso", end))
+               return DATE_ISO8601;
+       if (skip_prefix(format, "rfc2822", end) ||
+           skip_prefix(format, "rfc", end))
+               return DATE_RFC2822;
+       if (skip_prefix(format, "short", end))
+               return DATE_SHORT;
+       if (skip_prefix(format, "default", end))
+               return DATE_NORMAL;
+       if (skip_prefix(format, "raw", end))
+               return DATE_RAW;
+       if (skip_prefix(format, "format", end))
+               return DATE_STRFTIME;
+
+       die("unknown date format %s", format);
+}
+
 void parse_date_format(const char *format, struct date_mode *mode)
 {
-       if (!strcmp(format, "relative"))
-               mode->type = DATE_RELATIVE;
-       else if (!strcmp(format, "iso8601") ||
-                !strcmp(format, "iso"))
-               mode->type = DATE_ISO8601;
-       else if (!strcmp(format, "iso8601-strict") ||
-                !strcmp(format, "iso-strict"))
-               mode->type = DATE_ISO8601_STRICT;
-       else if (!strcmp(format, "rfc2822") ||
-                !strcmp(format, "rfc"))
-               mode->type = DATE_RFC2822;
-       else if (!strcmp(format, "short"))
-               mode->type = DATE_SHORT;
-       else if (!strcmp(format, "local"))
-               mode->type = DATE_LOCAL;
-       else if (!strcmp(format, "default"))
-               mode->type = DATE_NORMAL;
-       else if (!strcmp(format, "raw"))
-               mode->type = DATE_RAW;
-       else if (skip_prefix(format, "format:", &format)) {
-               mode->type = DATE_STRFTIME;
-               mode->strftime_fmt = xstrdup(format);
-       } else
+       const char *p;
+
+       /* historical alias */
+       if (!strcmp(format, "local"))
+               format = "default-local";
+
+       mode->type = parse_date_type(format, &p);
+       mode->local = 0;
+
+       if (skip_prefix(p, "-local", &p))
+               mode->local = 1;
+
+       if (mode->type == DATE_STRFTIME) {
+               if (!skip_prefix(p, ":", &p))
+                       die("date format missing colon separator: %s", format);
+               mode->strftime_fmt = xstrdup(p);
+       } else if (*p)
                die("unknown date format %s", format);
 }
 
index 0320605a84178ffb2ab9384b8ee9fbac7df16a73..8e0fd270b5be4d9ab8616ab57202862dd2fc6eda 100644 (file)
@@ -136,15 +136,13 @@ static int queue_diff(struct diff_options *o,
 
                if (name1) {
                        strbuf_addstr(&buffer1, name1);
-                       if (buffer1.len && buffer1.buf[buffer1.len - 1] != '/')
-                               strbuf_addch(&buffer1, '/');
+                       strbuf_complete(&buffer1, '/');
                        len1 = buffer1.len;
                }
 
                if (name2) {
                        strbuf_addstr(&buffer2, name2);
-                       if (buffer2.len && buffer2.buf[buffer2.len - 1] != '/')
-                               strbuf_addch(&buffer2, '/');
+                       strbuf_complete(&buffer2, '/');
                        len2 = buffer2.len;
                }
 
diff --git a/diff.c b/diff.c
index 46260ed7a1d521cf95342caff83a825b34959bdc..80eb0c2156a0f3727f8a9598f3e83233224539b0 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -13,7 +13,6 @@
 #include "run-command.h"
 #include "utf8.h"
 #include "userdiff.h"
-#include "sigchain.h"
 #include "submodule-config.h"
 #include "submodule.h"
 #include "ll-merge.h"
@@ -322,7 +321,7 @@ static struct diff_tempfile {
         */
        const char *name;
 
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        char mode[10];
 
        /*
@@ -2882,9 +2881,8 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                die_errno("unable to write temp-file");
        close_tempfile(&temp->tempfile);
        temp->name = get_tempfile_path(&temp->tempfile);
-       strcpy(temp->hex, sha1_to_hex(sha1));
-       temp->hex[40] = 0;
-       sprintf(temp->mode, "%06o", mode);
+       sha1_to_hex_r(temp->hex, sha1);
+       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
        free(path_dup);
@@ -2901,8 +2899,8 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                 * a '+' entry produces this for file-1.
                 */
                temp->name = "/dev/null";
-               strcpy(temp->hex, ".");
-               strcpy(temp->mode, ".");
+               xsnprintf(temp->hex, sizeof(temp->hex), ".");
+               xsnprintf(temp->mode, sizeof(temp->mode), ".");
                return temp;
        }
 
@@ -2930,16 +2928,16 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
                        if (!one->sha1_valid)
-                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                               sha1_to_hex_r(temp->hex, null_sha1);
                        else
-                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                               sha1_to_hex_r(temp->hex, one->sha1);
                        /* Even though we may sometimes borrow the
                         * contents from the work tree, we always want
                         * one->mode.  mode is trustworthy even when
                         * !(one->sha1_valid), as long as
                         * DIFF_FILE_VALID(one).
                         */
-                       sprintf(temp->mode, "%06o", one->mode);
+                       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
                }
                return temp;
        }
@@ -4085,9 +4083,9 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len)
        if (abblen < 37) {
                static char hex[41];
                if (len < abblen && abblen <= len + 2)
-                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+                       xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
-                       sprintf(hex, "%s...", abbrev);
+                       xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
        return sha1_to_hex(sha1);
diff --git a/dir.c b/dir.c
index 88e89585eb1634e07a9316ccd6caf8ea3643e817..d2a8f06b0243bda92c04f57d96c0b18838ee7dce 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -882,6 +882,25 @@ int match_pathname(const char *pathname, int pathlen,
                 */
                if (!patternlen && !namelen)
                        return 1;
+               /*
+                * This can happen when we ignore some exclude rules
+                * on directories in other to see if negative rules
+                * may match. E.g.
+                *
+                * /abc
+                * !/abc/def/ghi
+                *
+                * The pattern of interest is "/abc". On the first
+                * try, we should match path "abc" with this pattern
+                * in the "if" statement right above, but the caller
+                * ignores it.
+                *
+                * On the second try with paths within "abc",
+                * e.g. "abc/xyz", we come here and try to match it
+                * with "/abc".
+                */
+               if (!patternlen && namelen && *name == '/')
+                       return 1;
        }
 
        return fnmatch_icase_mem(pattern, patternlen,
@@ -889,6 +908,48 @@ int match_pathname(const char *pathname, int pathlen,
                                 WM_PATHNAME) == 0;
 }
 
+/*
+ * Return non-zero if pathname is a directory and an ancestor of the
+ * literal path in a (negative) pattern. This is used to keep
+ * descending in "foo" and "foo/bar" when the pattern is
+ * "!foo/bar/.gitignore". "foo/notbar" will not be descended however.
+ */
+static int match_neg_path(const char *pathname, int pathlen, int *dtype,
+                         const char *base, int baselen,
+                         const char *pattern, int prefix, int patternlen,
+                         int flags)
+{
+       assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR));
+
+       if (*dtype == DT_UNKNOWN)
+               *dtype = get_dtype(NULL, pathname, pathlen);
+       if (*dtype != DT_DIR)
+               return 0;
+
+       if (*pattern == '/') {
+               pattern++;
+               patternlen--;
+               prefix--;
+       }
+
+       if (baselen) {
+               if (((pathlen < baselen && base[pathlen] == '/') ||
+                    pathlen == baselen) &&
+                   !strncmp_icase(pathname, base, pathlen))
+                       return 1;
+               pathname += baselen + 1;
+               pathlen  -= baselen + 1;
+       }
+
+
+       if (prefix &&
+           ((pathlen < prefix && pattern[pathlen] == '/') &&
+            !strncmp_icase(pathname, pattern, pathlen)))
+               return 1;
+
+       return 0;
+}
+
 /*
  * Scan the given exclude list in reverse to see whether pathname
  * should be ignored.  The first match (i.e. the last on the list), if
@@ -901,7 +962,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
                                                       int *dtype,
                                                       struct exclude_list *el)
 {
-       int i;
+       struct exclude *exc = NULL; /* undecided */
+       int i, matched_negative_path = 0;
 
        if (!el->nr)
                return NULL;    /* undefined */
@@ -922,18 +984,33 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
                        if (match_basename(basename,
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
-                                          x->flags))
-                               return x;
+                                          x->flags)) {
+                               exc = x;
+                               break;
+                       }
                        continue;
                }
 
                assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
+                                  exclude, prefix, x->patternlen, x->flags)) {
+                       exc = x;
+                       break;
+               }
+
+               if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path &&
+                   match_neg_path(pathname, pathlen, dtype, x->base,
+                                  x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
-                       return x;
+                       matched_negative_path = 1;
        }
-       return NULL; /* undecided */
+       if (exc &&
+           !(exc->flags & EXC_FLAG_NEGATIVE) &&
+           !(exc->flags & EXC_FLAG_NODIR) &&
+           matched_negative_path)
+               exc = NULL;
+       return exc;
 }
 
 /*
@@ -1505,8 +1582,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
        }
        strbuf_addstr(path, cdir->ucd->name);
        /* treat_one_path() does this before it calls treat_directory() */
-       if (path->buf[path->len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
        if (cdir->ucd->check_only)
                /*
                 * check_only is set as a result of treat_directory() getting
@@ -2121,8 +2197,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
                else
                        return -1;
        }
-       if (path->buf[original_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
 
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
diff --git a/entry.c b/entry.c
index 1eda8e94714bbd6020d61b4b627f721b1042673c..582c40071a3034af9a391cf438dd74ddb34e15ff 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -96,8 +96,8 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf
 {
        int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
        if (to_tempfile) {
-               strcpy(path, symlink
-                      ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+               xsnprintf(path, TEMPORARY_FILENAME_LENGTH, "%s",
+                         symlink ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
                return mkstemp(path);
        } else {
                return create_file(path, !symlink ? ce->ce_mode : 0666);
index 23a38e4b14f61f078bec3d7c2a5e5bcc5d505d9d..2da7fe2e06ff38b977209d44b7404c93358430af 100644 (file)
@@ -144,11 +144,8 @@ static char *git_path_from_env(const char *envvar, const char *git_dir,
                               const char *path, int *fromenv)
 {
        const char *value = getenv(envvar);
-       if (!value) {
-               char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2);
-               sprintf(buf, "%s/%s", git_dir, path);
-               return buf;
-       }
+       if (!value)
+               return xstrfmt("%s/%s", git_dir, path);
        if (fromenv)
                *fromenv = 1;
        return xstrdup(value);
index 6c7c3c9b669eb272f4da530aef459c9c39c4d294..e3b421d5149bc3a2ad58ca8df98fdf2c924ed7c0 100644 (file)
@@ -424,7 +424,7 @@ static void write_crash_report(const char *err)
        fprintf(rpt, "fast-import crash report:\n");
        fprintf(rpt, "    fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
        fprintf(rpt, "    parent process     : %"PRIuMAX"\n", (uintmax_t) getppid());
-       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(LOCAL)));
+       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601)));
        fputc('\n', rpt);
 
        fputs("fatal: ", rpt);
@@ -644,8 +644,9 @@ static void *pool_calloc(size_t count, size_t size)
 
 static char *pool_strdup(const char *s)
 {
-       char *r = pool_alloc(strlen(s) + 1);
-       strcpy(r, s);
+       size_t len = strlen(s) + 1;
+       char *r = pool_alloc(len);
+       memcpy(r, s, len);
        return r;
 }
 
@@ -702,7 +703,7 @@ static struct atom_str *to_atom(const char *s, unsigned short len)
 
        c = pool_alloc(sizeof(struct atom_str) + len + 1);
        c->str_len = len;
-       strncpy(c->str_dat, s, len);
+       memcpy(c->str_dat, s, len);
        c->str_dat[len] = 0;
        c->next_atom = atom_table[hc];
        atom_table[hc] = c;
@@ -863,13 +864,15 @@ static void start_packfile(void)
 {
        static char tmp_file[PATH_MAX];
        struct packed_git *p;
+       int namelen;
        struct pack_header hdr;
        int pack_fd;
 
        pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                              "pack/tmp_pack_XXXXXX");
-       p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
-       strcpy(p->pack_name, tmp_file);
+       namelen = strlen(tmp_file) + 2;
+       p = xcalloc(1, sizeof(*p) + namelen);
+       xsnprintf(p->pack_name, namelen, "%s", tmp_file);
        p->pack_fd = pack_fd;
        p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
@@ -1035,8 +1038,8 @@ static int store_object(
        git_SHA_CTX c;
        git_zstream s;
 
-       hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
-               (unsigned long)dat->len) + 1;
+       hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu",
+                          typename(type), (unsigned long)dat->len) + 1;
        git_SHA1_Init(&c);
        git_SHA1_Update(&c, hdr, hdrlen);
        git_SHA1_Update(&c, dat->buf, dat->len);
index 820251a8d80518508514b728b4b2afd8a171e296..2dabee97b28915ccf5c06b58dd0193bf03a01db8 100644 (file)
@@ -681,11 +681,10 @@ static int get_pack(struct fetch_pack_args *args,
                    int xd[2], char **pack_lockfile)
 {
        struct async demux;
-       const char *argv[22];
-       char keep_arg[256];
-       char hdr_arg[256];
-       const char **av, *cmd_name;
        int do_keep = args->keep_pack;
+       const char *cmd_name;
+       struct pack_header header;
+       int pass_header = 0;
        struct child_process cmd = CHILD_PROCESS_INIT;
        int ret;
 
@@ -705,17 +704,11 @@ static int get_pack(struct fetch_pack_args *args,
        else
                demux.out = xd[0];
 
-       cmd.argv = argv;
-       av = argv;
-       *hdr_arg = 0;
        if (!args->keep_pack && unpack_limit) {
-               struct pack_header header;
 
                if (read_pack_header(demux.out, &header))
                        die("protocol error: bad pack header");
-               snprintf(hdr_arg, sizeof(hdr_arg),
-                        "--pack_header=%"PRIu32",%"PRIu32,
-                        ntohl(header.hdr_version), ntohl(header.hdr_entries));
+               pass_header = 1;
                if (ntohl(header.hdr_entries) < unpack_limit)
                        do_keep = 0;
                else
@@ -723,44 +716,49 @@ static int get_pack(struct fetch_pack_args *args,
        }
 
        if (alternate_shallow_file) {
-               *av++ = "--shallow-file";
-               *av++ = alternate_shallow_file;
+               argv_array_push(&cmd.args, "--shallow-file");
+               argv_array_push(&cmd.args, alternate_shallow_file);
        }
 
        if (do_keep) {
                if (pack_lockfile)
                        cmd.out = -1;
-               *av++ = cmd_name = "index-pack";
-               *av++ = "--stdin";
+               cmd_name = "index-pack";
+               argv_array_push(&cmd.args, cmd_name);
+               argv_array_push(&cmd.args, "--stdin");
                if (!args->quiet && !args->no_progress)
-                       *av++ = "-v";
+                       argv_array_push(&cmd.args, "-v");
                if (args->use_thin_pack)
-                       *av++ = "--fix-thin";
+                       argv_array_push(&cmd.args, "--fix-thin");
                if (args->lock_pack || unpack_limit) {
-                       int s = sprintf(keep_arg,
-                                       "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
-                       if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                               strcpy(keep_arg + s, "localhost");
-                       *av++ = keep_arg;
+                       char hostname[256];
+                       if (gethostname(hostname, sizeof(hostname)))
+                               xsnprintf(hostname, sizeof(hostname), "localhost");
+                       argv_array_pushf(&cmd.args,
+                                       "--keep=fetch-pack %"PRIuMAX " on %s",
+                                       (uintmax_t)getpid(), hostname);
                }
                if (args->check_self_contained_and_connected)
-                       *av++ = "--check-self-contained-and-connected";
+                       argv_array_push(&cmd.args, "--check-self-contained-and-connected");
        }
        else {
-               *av++ = cmd_name = "unpack-objects";
+               cmd_name = "unpack-objects";
+               argv_array_push(&cmd.args, cmd_name);
                if (args->quiet || args->no_progress)
-                       *av++ = "-q";
+                       argv_array_push(&cmd.args, "-q");
                args->check_self_contained_and_connected = 0;
        }
-       if (*hdr_arg)
-               *av++ = hdr_arg;
+
+       if (pass_header)
+               argv_array_pushf(&cmd.args, "--pack_header=%"PRIu32",%"PRIu32,
+                                ntohl(header.hdr_version),
+                                ntohl(header.hdr_entries));
        if (fetch_fsck_objects >= 0
            ? fetch_fsck_objects
            : transfer_fsck_objects >= 0
            ? transfer_fsck_objects
            : 0)
-               *av++ = "--strict";
-       *av++ = NULL;
+               argv_array_push(&cmd.args, "--strict");
 
        cmd.in = demux.out;
        cmd.git_cmd = 1;
index ea63223ab3b5d4f8fb0a9af45db4278aef010fb7..5d1cb00d86b3700b2012cae1ed3b3b8b744acaf2 100755 (executable)
@@ -1,14 +1,19 @@
 #!/bin/sh
 
-USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
+USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect help
        print this long help message.
-git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
+git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
        reset bisect state and start bisection.
-git bisect bad [<rev>]
-       mark <rev> a known-bad revision.
-git bisect good [<rev>...]
-       mark <rev>... known-good revisions.
+git bisect (bad|new) [<rev>]
+       mark <rev> a known-bad revision/
+               a revision after change in a given property.
+git bisect (good|old) [<rev>...]
+       mark <rev>... known-good revisions/
+               revisions before change in a given property.
+git bisect terms [--term-good | --term-bad]
+       show the terms used for old and new commits (default: bad, good)
 git bisect skip [(<rev>|<range>)...]
        mark <rev>... untestable revisions.
 git bisect next
@@ -95,6 +100,24 @@ bisect_start() {
                --no-checkout)
                        mode=--no-checkout
                        shift ;;
+               --term-good|--term-old)
+                       shift
+                       must_write_terms=1
+                       TERM_GOOD=$1
+                       shift ;;
+               --term-good=*|--term-old=*)
+                       must_write_terms=1
+                       TERM_GOOD=${1#*=}
+                       shift ;;
+               --term-bad|--term-new)
+                       shift
+                       must_write_terms=1
+                       TERM_BAD=$1
+                       shift ;;
+               --term-bad=*|--term-new=*)
+                       must_write_terms=1
+                       TERM_BAD=${1#*=}
+                       shift ;;
                --*)
                        die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
                *)
@@ -294,7 +317,7 @@ bisect_next_check() {
                false
                ;;
        t,,"$TERM_GOOD")
-               # have bad but not good.  we could bisect although
+               # have bad (or new) but not good (or old).  we could bisect although
                # this is less optimum.
                eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
                if test -t 0
@@ -451,6 +474,8 @@ bisect_replay () {
                        eval "$cmd" ;;
                "$TERM_GOOD"|"$TERM_BAD"|skip)
                        bisect_write "$command" "$rev" ;;
+               terms)
+                       bisect_terms $rev ;;
                *)
                        die "$(gettext "?? what are you talking about?")" ;;
                esac
@@ -535,9 +560,42 @@ get_terms () {
 write_terms () {
        TERM_BAD=$1
        TERM_GOOD=$2
+       if test "$TERM_BAD" = "$TERM_GOOD"
+       then
+               die "$(gettext "please use two different terms")"
+       fi
+       check_term_format "$TERM_BAD" bad
+       check_term_format "$TERM_GOOD" good
        printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
 }
 
+check_term_format () {
+       term=$1
+       git check-ref-format refs/bisect/"$term" ||
+       die "$(eval_gettext "'\$term' is not a valid term")"
+       case "$term" in
+       help|start|terms|skip|next|reset|visualize|replay|log|run)
+               die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
+               ;;
+       bad|new)
+               if test "$2" != bad
+               then
+                       # In theory, nothing prevents swapping
+                       # completely good and bad, but this situation
+                       # could be confusing and hasn't been tested
+                       # enough. Forbid it for now.
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       good|old)
+               if test "$2" != good
+               then
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       esac
+}
+
 check_and_set_terms () {
        cmd="$1"
        case "$cmd" in
@@ -554,14 +612,51 @@ check_and_set_terms () {
                                write_terms bad good
                        fi
                        ;;
+               new|old)
+                       if ! test -s "$GIT_DIR/BISECT_TERMS"
+                       then
+                               write_terms new old
+                       fi
+                       ;;
                esac ;;
        esac
 }
 
 bisect_voc () {
        case "$1" in
-       bad) echo "bad" ;;
-       good) echo "good" ;;
+       bad) echo "bad|new" ;;
+       good) echo "good|old" ;;
+       esac
+}
+
+bisect_terms () {
+       get_terms
+       if ! test -s "$GIT_DIR/BISECT_TERMS"
+       then
+               die "$(gettext "no terms defined")"
+       fi
+       case "$#" in
+       0)
+               gettextln "Your current terms are $TERM_GOOD for the old state
+and $TERM_BAD for the new state."
+               ;;
+       1)
+               arg=$1
+               case "$arg" in
+                       --term-good|--term-old)
+                               printf '%s\n' "$TERM_GOOD"
+                               ;;
+                       --term-bad|--term-new)
+                               printf '%s\n' "$TERM_BAD"
+                               ;;
+                       *)
+                               die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
+Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
+                               ;;
+               esac
+               ;;
+       *)
+               usage ;;
        esac
 }
 
@@ -577,7 +672,7 @@ case "$#" in
                git bisect -h ;;
        start)
                bisect_start "$@" ;;
-       bad|good|"$TERM_BAD"|"$TERM_GOOD")
+       bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
                bisect_state "$cmd" "$@" ;;
        skip)
                bisect_skip "$@" ;;
@@ -594,6 +689,8 @@ case "$#" in
                bisect_log ;;
        run)
                bisect_run "$@" ;;
+       terms)
+               bisect_terms "$@" ;;
        *)
                usage ;;
        esac
index f649e81f1107722f4c7d051201920ae2a0e7846a..8e3986791d13a5e9196eea95761f35b5dc2078da 100644 (file)
@@ -229,7 +229,7 @@ typedef unsigned long uintptr_t;
 #else
 #define precompose_str(in,i_nfd2nfc)
 #define precompose_argv(c,v)
-#define probe_utf8_pathname_composition(a,b)
+#define probe_utf8_pathname_composition()
 #endif
 
 #ifdef MKDIR_WO_TRAILING_SLASH
@@ -296,6 +296,10 @@ extern char *gitbasename(char *);
 #define PRIuMAX "llu"
 #endif
 
+#ifndef SCNuMAX
+#define SCNuMAX PRIuMAX
+#endif
+
 #ifndef PRIu32
 #define PRIu32 "u"
 #endif
@@ -568,7 +572,7 @@ extern int git_lstat(const char *, struct stat *);
 #endif
 
 #define DEFAULT_PACKED_GIT_LIMIT \
-       ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256))
+       ((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? 8192 : 256))
 
 #ifdef NO_PREAD
 #define pread git_pread
@@ -744,6 +748,9 @@ static inline size_t xsize_t(off_t len)
        return (size_t)len;
 }
 
+__attribute__((format (printf, 3, 4)))
+extern int xsnprintf(char *dst, size_t max, const char *fmt, ...);
+
 /* in ctype.c, for kwset users */
 extern const unsigned char tolower_trans_tbl[256];
 
@@ -814,6 +821,9 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
        char *p;
 
        errno = 0;
+       /* negative values would be accepted by strtoul */
+       if (strchr(s, '-'))
+               return -1;
        ul = strtoul(s, &p, base);
        if (errno || *p || p == s || (unsigned int) ul != ul)
                return -1;
index 7df7c8a9a7c889fbf5f0b2d6e67be66bf6fea6b1..488d14b153e50f69fcfb0c732cc8c23cf4b643de 100755 (executable)
@@ -70,9 +70,7 @@ sub use_wt_file
        my ($repo, $workdir, $file, $sha1) = @_;
        my $null_sha1 = '0' x 40;
 
-       if (! -e "$workdir/$file") {
-               # If the file doesn't exist in the working tree, we cannot
-               # use it.
+       if (-l "$workdir/$file" || ! -e _) {
                return (0, $null_sha1);
        }
 
index fff8093d4f301b96a455a74dc062f7f645b15eed..27c9c54fbd24ef3a2995fd90b04911e5a1d8aed9 100755 (executable)
@@ -275,11 +275,41 @@ commits=$(wc -l <../revs | tr -d " ")
 test $commits -eq 0 && die "Found nothing to rewrite"
 
 # Rewrite the commits
+report_progress ()
+{
+       if test -n "$progress" &&
+               test $git_filter_branch__commit_count -gt $next_sample_at
+       then
+               count=$git_filter_branch__commit_count
+
+               now=$(date +%s)
+               elapsed=$(($now - $start_timestamp))
+               remaining=$(( ($commits - $count) * $elapsed / $count ))
+               if test $elapsed -gt 0
+               then
+                       next_sample_at=$(( ($elapsed + 1) * $count / $elapsed ))
+               else
+                       next_sample_at=$(($next_sample_at + 1))
+               fi
+               progress=" ($elapsed seconds passed, remaining $remaining predicted)"
+       fi
+       printf "\rRewrite $commit ($count/$commits)$progress    "
+}
 
 git_filter_branch__commit_count=0
+
+progress= start_timestamp=
+if date '+%s' 2>/dev/null | grep -q '^[0-9][0-9]*$'
+then
+       next_sample_at=0
+       progress="dummy to ensure this is not empty"
+       start_timestamp=$(date '+%s')
+fi
+
 while read commit parents; do
        git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1))
-       printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)"
+
+       report_progress
 
        case "$filter_subdir" in
        "")
index 07dfeb8df4bf910c5c79fb4c3dd3345ba4719da0..cdc02af517c9d77a081d7918480f8d0a2c6a29d0 100755 (executable)
@@ -38,6 +38,14 @@ case "${1:-.}${2:-.}${3:-.}" in
 # Deleted in both or deleted in one and unchanged in the other
 #
 "$1.." | "$1.$1" | "$1$1.")
+       if { test -z "$6" && test "$5" != "$7"; } ||
+          { test -z "$7" && test "$5" != "$6"; }
+       then
+               echo "ERROR: File $4 deleted on one branch but had its" >&2
+               echo "ERROR: permissions changed on the other." >&2
+               exit 1
+       fi
+
        if test -n "$2"
        then
                echo "Removing $4"
index 36c5c55b61b6dc16c27c219099654611a5b085f5..212ef2be9670bc2fe5573eee856471113eb96889 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -22,6 +22,9 @@
 import re
 import shutil
 import stat
+import zipfile
+import zlib
+import ctypes
 
 try:
     from subprocess import CalledProcessError
@@ -104,6 +107,16 @@ def chdir(path, is_client_path=False):
         path = os.getcwd()
     os.environ['PWD'] = path
 
+def calcDiskFree():
+    """Return free space in bytes on the disk of the given dirname."""
+    if platform.system() == 'Windows':
+        free_bytes = ctypes.c_ulonglong(0)
+        ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(os.getcwd()), None, None, ctypes.pointer(free_bytes))
+        return free_bytes.value
+    else:
+        st = os.statvfs(os.getcwd())
+        return st.f_bavail * st.f_frsize
+
 def die(msg):
     if verbose:
         raise Exception(msg)
@@ -602,9 +615,12 @@ def gitBranchExists(branch):
 
 _gitConfig = {}
 
-def gitConfig(key):
+def gitConfig(key, typeSpecifier=None):
     if not _gitConfig.has_key(key):
-        cmd = [ "git", "config", key ]
+        cmd = [ "git", "config" ]
+        if typeSpecifier:
+            cmd += [ typeSpecifier ]
+        cmd += [ key ]
         s = read_pipe(cmd, ignore_error=True)
         _gitConfig[key] = s.strip()
     return _gitConfig[key]
@@ -615,16 +631,26 @@ def gitConfigBool(key):
        in the config."""
 
     if not _gitConfig.has_key(key):
-        cmd = [ "git", "config", "--bool", key ]
+        _gitConfig[key] = gitConfig(key, '--bool') == "true"
+    return _gitConfig[key]
+
+def gitConfigInt(key):
+    if not _gitConfig.has_key(key):
+        cmd = [ "git", "config", "--int", key ]
         s = read_pipe(cmd, ignore_error=True)
         v = s.strip()
-        _gitConfig[key] = v == "true"
+        try:
+            _gitConfig[key] = int(gitConfig(key, '--int'))
+        except ValueError:
+            _gitConfig[key] = None
     return _gitConfig[key]
 
 def gitConfigList(key):
     if not _gitConfig.has_key(key):
         s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
         _gitConfig[key] = s.strip().split(os.linesep)
+        if _gitConfig[key] == ['']:
+            _gitConfig[key] = []
     return _gitConfig[key]
 
 def p4BranchesInGit(branchesAreInRemotes=True):
@@ -907,6 +933,182 @@ def wildcard_present(path):
     m = re.search("[*#@%]", path)
     return m is not None
 
+class LargeFileSystem(object):
+    """Base class for large file system support."""
+
+    def __init__(self, writeToGitStream):
+        self.largeFiles = set()
+        self.writeToGitStream = writeToGitStream
+
+    def generatePointer(self, cloneDestination, contentFile):
+        """Return the content of a pointer file that is stored in Git instead of
+           the actual content."""
+        assert False, "Method 'generatePointer' required in " + self.__class__.__name__
+
+    def pushFile(self, localLargeFile):
+        """Push the actual content which is not stored in the Git repository to
+           a server."""
+        assert False, "Method 'pushFile' required in " + self.__class__.__name__
+
+    def hasLargeFileExtension(self, relPath):
+        return reduce(
+            lambda a, b: a or b,
+            [relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
+            False
+        )
+
+    def generateTempFile(self, contents):
+        contentFile = tempfile.NamedTemporaryFile(prefix='git-p4-large-file', delete=False)
+        for d in contents:
+            contentFile.write(d)
+        contentFile.close()
+        return contentFile.name
+
+    def exceedsLargeFileThreshold(self, relPath, contents):
+        if gitConfigInt('git-p4.largeFileThreshold'):
+            contentsSize = sum(len(d) for d in contents)
+            if contentsSize > gitConfigInt('git-p4.largeFileThreshold'):
+                return True
+        if gitConfigInt('git-p4.largeFileCompressedThreshold'):
+            contentsSize = sum(len(d) for d in contents)
+            if contentsSize <= gitConfigInt('git-p4.largeFileCompressedThreshold'):
+                return False
+            contentTempFile = self.generateTempFile(contents)
+            compressedContentFile = tempfile.NamedTemporaryFile(prefix='git-p4-large-file', delete=False)
+            zf = zipfile.ZipFile(compressedContentFile.name, mode='w')
+            zf.write(contentTempFile, compress_type=zipfile.ZIP_DEFLATED)
+            zf.close()
+            compressedContentsSize = zf.infolist()[0].compress_size
+            os.remove(contentTempFile)
+            os.remove(compressedContentFile.name)
+            if compressedContentsSize > gitConfigInt('git-p4.largeFileCompressedThreshold'):
+                return True
+        return False
+
+    def addLargeFile(self, relPath):
+        self.largeFiles.add(relPath)
+
+    def removeLargeFile(self, relPath):
+        self.largeFiles.remove(relPath)
+
+    def isLargeFile(self, relPath):
+        return relPath in self.largeFiles
+
+    def processContent(self, git_mode, relPath, contents):
+        """Processes the content of git fast import. This method decides if a
+           file is stored in the large file system and handles all necessary
+           steps."""
+        if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath):
+            contentTempFile = self.generateTempFile(contents)
+            (git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile)
+
+            # Move temp file to final location in large file system
+            largeFileDir = os.path.dirname(localLargeFile)
+            if not os.path.isdir(largeFileDir):
+                os.makedirs(largeFileDir)
+            shutil.move(contentTempFile, localLargeFile)
+            self.addLargeFile(relPath)
+            if gitConfigBool('git-p4.largeFilePush'):
+                self.pushFile(localLargeFile)
+            if verbose:
+                sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
+        return (git_mode, contents)
+
+class MockLFS(LargeFileSystem):
+    """Mock large file system for testing."""
+
+    def generatePointer(self, contentFile):
+        """The pointer content is the original content prefixed with "pointer-".
+           The local filename of the large file storage is derived from the file content.
+           """
+        with open(contentFile, 'r') as f:
+            content = next(f)
+            gitMode = '100644'
+            pointerContents = 'pointer-' + content
+            localLargeFile = os.path.join(os.getcwd(), '.git', 'mock-storage', 'local', content[:-1])
+            return (gitMode, pointerContents, localLargeFile)
+
+    def pushFile(self, localLargeFile):
+        """The remote filename of the large file storage is the same as the local
+           one but in a different directory.
+           """
+        remotePath = os.path.join(os.path.dirname(localLargeFile), '..', 'remote')
+        if not os.path.exists(remotePath):
+            os.makedirs(remotePath)
+        shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
+
+class GitLFS(LargeFileSystem):
+    """Git LFS as backend for the git-p4 large file system.
+       See https://git-lfs.github.com/ for details."""
+
+    def __init__(self, *args):
+        LargeFileSystem.__init__(self, *args)
+        self.baseGitAttributes = []
+
+    def generatePointer(self, contentFile):
+        """Generate a Git LFS pointer for the content. Return LFS Pointer file
+           mode and content which is stored in the Git repository instead of
+           the actual content. Return also the new location of the actual
+           content.
+           """
+        pointerProcess = subprocess.Popen(
+            ['git', 'lfs', 'pointer', '--file=' + contentFile],
+            stdout=subprocess.PIPE
+        )
+        pointerFile = pointerProcess.stdout.read()
+        if pointerProcess.wait():
+            os.remove(contentFile)
+            die('git-lfs pointer command failed. Did you install the extension?')
+        pointerContents = [i+'\n' for i in pointerFile.split('\n')[2:][:-1]]
+        oid = pointerContents[1].split(' ')[1].split(':')[1][:-1]
+        localLargeFile = os.path.join(
+            os.getcwd(),
+            '.git', 'lfs', 'objects', oid[:2], oid[2:4],
+            oid,
+        )
+        # LFS Spec states that pointer files should not have the executable bit set.
+        gitMode = '100644'
+        return (gitMode, pointerContents, localLargeFile)
+
+    def pushFile(self, localLargeFile):
+        uploadProcess = subprocess.Popen(
+            ['git', 'lfs', 'push', '--object-id', 'origin', os.path.basename(localLargeFile)]
+        )
+        if uploadProcess.wait():
+            die('git-lfs push command failed. Did you define a remote?')
+
+    def generateGitAttributes(self):
+        return (
+            self.baseGitAttributes +
+            [
+                '\n',
+                '#\n',
+                '# Git LFS (see https://git-lfs.github.com/)\n',
+                '#\n',
+            ] +
+            ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n'
+                for f in sorted(gitConfigList('git-p4.largeFileExtensions'))
+            ] +
+            ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n'
+                for f in sorted(self.largeFiles) if not self.hasLargeFileExtension(f)
+            ]
+        )
+
+    def addLargeFile(self, relPath):
+        LargeFileSystem.addLargeFile(self, relPath)
+        self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())
+
+    def removeLargeFile(self, relPath):
+        LargeFileSystem.removeLargeFile(self, relPath)
+        self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())
+
+    def processContent(self, git_mode, relPath, contents):
+        if relPath == '.gitattributes':
+            self.baseGitAttributes = contents
+            return (git_mode, self.generateGitAttributes())
+        else:
+            return LargeFileSystem.processContent(self, git_mode, relPath, contents)
+
 class Command:
     def __init__(self):
         self.usage = "usage: %prog [options]"
@@ -1080,6 +1282,9 @@ def __init__(self):
         self.p4HasMoveCommand = p4_has_move_command()
         self.branch = None
 
+        if gitConfig('git-p4.largeFileSystem'):
+            die("Large file system not supported for git-p4 submit command. Please remove it from config.")
+
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
             die("You have files opened with perforce! Close them before starting the sync.")
@@ -2030,6 +2235,13 @@ def __init__(self):
         self.clientSpecDirs = None
         self.tempBranches = []
         self.tempBranchLocation = "git-p4-tmp"
+        self.largeFileSystem = None
+
+        if gitConfig('git-p4.largeFileSystem'):
+            largeFileSystemConstructor = globals()[gitConfig('git-p4.largeFileSystem')]
+            self.largeFileSystem = largeFileSystemConstructor(
+                lambda git_mode, relPath, contents: self.writeToGitStream(git_mode, relPath, contents)
+            )
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
@@ -2150,13 +2362,22 @@ def splitFilesIntoBranches(self, commit):
 
         return branches
 
+    def writeToGitStream(self, gitMode, relPath, contents):
+        self.gitStream.write('M %s inline %s\n' % (gitMode, relPath))
+        self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
+        for d in contents:
+            self.gitStream.write(d)
+        self.gitStream.write('\n')
+
     # output one file from the P4 stream
     # - helper for streamP4Files
 
     def streamOneP4File(self, file, contents):
         relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
         if verbose:
-            sys.stderr.write("%s\n" % relPath)
+            size = int(self.stream_file['fileSize'])
+            sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
+            sys.stdout.flush()
 
         (type_base, type_mods) = split_p4_type(file["type"])
 
@@ -2225,24 +2446,31 @@ def streamOneP4File(self, file, contents):
             text = regexp.sub(r'$\1$', text)
             contents = [ text ]
 
-        self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
+        try:
+            relPath.decode('ascii')
+        except:
+            encoding = 'utf8'
+            if gitConfig('git-p4.pathEncoding'):
+                encoding = gitConfig('git-p4.pathEncoding')
+            relPath = relPath.decode(encoding, 'replace').encode('utf8', 'replace')
+            if self.verbose:
+                print 'Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, relPath)
 
-        # total length...
-        length = 0
-        for d in contents:
-            length = length + len(d)
+        if self.largeFileSystem:
+            (git_mode, contents) = self.largeFileSystem.processContent(git_mode, relPath, contents)
 
-        self.gitStream.write("data %d\n" % length)
-        for d in contents:
-            self.gitStream.write(d)
-        self.gitStream.write("\n")
+        self.writeToGitStream(git_mode, relPath, contents)
 
     def streamOneP4Deletion(self, file):
         relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
         if verbose:
-            sys.stderr.write("delete %s\n" % relPath)
+            sys.stdout.write("delete %s\n" % relPath)
+            sys.stdout.flush()
         self.gitStream.write("D %s\n" % relPath)
 
+        if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
+            self.largeFileSystem.removeLargeFile(relPath)
+
     # handle another chunk of streaming data
     def streamP4FilesCb(self, marshalled):
 
@@ -2252,6 +2480,14 @@ def streamP4FilesCb(self, marshalled):
             if marshalled["code"] == "error":
                 if "data" in marshalled:
                     err = marshalled["data"].rstrip()
+
+        if not err and 'fileSize' in self.stream_file:
+            required_bytes = int((4 * int(self.stream_file["fileSize"])) - calcDiskFree())
+            if required_bytes > 0:
+                err = 'Not enough space left on %s! Free at least %i MB.' % (
+                    os.getcwd(), required_bytes/1024/1024
+                )
+
         if err:
             f = None
             if self.stream_have_file_info:
@@ -2280,10 +2516,23 @@ def streamP4FilesCb(self, marshalled):
         # 'data' field we need to append to our array
         for k in marshalled.keys():
             if k == 'data':
+                if 'streamContentSize' not in self.stream_file:
+                    self.stream_file['streamContentSize'] = 0
+                self.stream_file['streamContentSize'] += len(marshalled['data'])
                 self.stream_contents.append(marshalled['data'])
             else:
                 self.stream_file[k] = marshalled[k]
 
+        if (verbose and
+            'streamContentSize' in self.stream_file and
+            'fileSize' in self.stream_file and
+            'depotFile' in self.stream_file):
+            size = int(self.stream_file["fileSize"])
+            if size > 0:
+                progress = 100*self.stream_file['streamContentSize']/size
+                sys.stdout.write('\r%s %d%% (%i MB)' % (self.stream_file['depotFile'], progress, int(size/1024/1024)))
+                sys.stdout.flush()
+
         self.stream_have_file_info = True
 
     # Stream directly from "p4 files" into "git fast-import"
index 167d79fea809b918e81c2228ee27baa5fab23db4..6d3a88decdeee3f85d9ee43ef8e716ccd1a6328b 100755 (executable)
@@ -6,7 +6,8 @@ git quiltimport [options]
 --
 n,dry-run     dry run
 author=       author name and email address for patches without any
-patches=      path to the quilt series and patches
+patches=      path to the quilt patches
+series=       path to the quilt series file
 "
 SUBDIRECTORY_ON=Yes
 . git-sh-setup
@@ -27,6 +28,10 @@ do
                shift
                QUILT_PATCHES="$1"
                ;;
+       --series)
+               shift
+               QUILT_SERIES="$1"
+               ;;
        --)
                shift
                break;;
@@ -53,6 +58,13 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
        exit 1
 fi
 
+# Quilt series file
+: ${QUILT_SERIES:=$QUILT_PATCHES/series}
+if ! [ -e "$QUILT_SERIES" ] ; then
+       echo "The \"$QUILT_SERIES\" file does not exist."
+       exit 1
+fi
+
 # Temporary directories
 tmp_dir="$GIT_DIR"/rebase-apply
 tmp_msg="$tmp_dir/msg"
@@ -135,5 +147,5 @@ do
                commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
                git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
-done 3<"$QUILT_PATCHES/series"
+done 3<"$QUILT_SERIES"
 rm -rf $tmp_dir || exit 5
index 1757404bc271ba4e1a08eb30fe27542c35e3008e..af7ba5fd90c3000892ed31893e1812514e4f3773 100755 (executable)
@@ -14,7 +14,7 @@ git-rebase --continue | --abort | --skip | --edit-todo
  Available options are
 v,verbose!         display a diffstat of what changed upstream
 q,quiet!           be quiet. implies --no-stat
-autostash!         automatically stash/stash pop before and after
+autostash          automatically stash/stash pop before and after
 fork-point         use 'merge-base --fork-point' to refine upstream
 onto=!             rebase onto given branch instead of upstream
 p,preserve-merges! try to recreate merges instead of ignoring them
@@ -292,6 +292,9 @@ do
        --autostash)
                autostash=true
                ;;
+       --no-autostash)
+               autostash=false
+               ;;
        --verbose)
                verbose=t
                diffstat=t
index e3ff44b4d0cd25f247138d9ead160d2aedff5033..e907e0eacf31c21dcb75fa102eb980d6a51f96c3 100755 (executable)
@@ -1365,7 +1365,11 @@ sub send_message {
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
-               $smtp->datasend("$header\n$message") or die $smtp->message;
+               $smtp->datasend("$header\n") or die $smtp->message;
+               my @lines = split /^/, $message;
+               foreach my $line (@lines) {
+                       $smtp->datasend("$line") or die $smtp->message;
+               }
                $smtp->dataend() or die $smtp->message;
                $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
        }
index 1d5ba7a4f935fd08572c235b7c5d4390eb8d6528..c7c65e25f50e7b558952a5180081ad499f2a90fc 100755 (executable)
@@ -305,7 +305,25 @@ show_stash () {
        ALLOW_UNKNOWN_FLAGS=t
        assert_stash_like "$@"
 
-       git diff ${FLAGS:---stat} $b_commit $w_commit
+       if test -z "$FLAGS"
+       then
+               if test "$(git config --bool stash.showStat || echo true)" = "true"
+               then
+                       FLAGS=--stat
+               fi
+
+               if test "$(git config --bool stash.showPatch || echo false)" = "true"
+               then
+                       FLAGS=${FLAGS}${FLAGS:+ }-p
+               fi
+
+               if test -z "$FLAGS"
+               then
+                       return 0
+               fi
+       fi
+
+       git diff ${FLAGS} $b_commit $w_commit
 }
 
 show_help () {
index 82e35582cd8e741280229f377b4f61d7d985bc6a..9bc5c5f94d1d7b24dffae649cca371299540aa46 100755 (executable)
@@ -154,48 +154,6 @@ relative_path ()
        echo "$result$target"
 }
 
-#
-# Get submodule info for registered submodules
-# $@ = path to limit submodule list
-#
-module_list()
-{
-       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
-       (
-               git ls-files -z --error-unmatch --stage -- "$@" ||
-               echo "unmatched pathspec exists"
-       ) |
-       @@PERL@@ -e '
-       my %unmerged = ();
-       my ($null_sha1) = ("0" x 40);
-       my @out = ();
-       my $unmatched = 0;
-       $/ = "\0";
-       while (<STDIN>) {
-               if (/^unmatched pathspec/) {
-                       $unmatched = 1;
-                       next;
-               }
-               chomp;
-               my ($mode, $sha1, $stage, $path) =
-                       /^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
-               next unless $mode eq "160000";
-               if ($stage ne "0") {
-                       if (!$unmerged{$path}++) {
-                               push @out, "$mode $null_sha1 U\t$path\n";
-                       }
-                       next;
-               }
-               push @out, "$_\n";
-       }
-       if ($unmatched) {
-               print "#unmatched\n";
-       } else {
-               print for (@out);
-       }
-       '
-}
-
 die_if_unmatched ()
 {
        if test "$1" = "#unmatched"
@@ -229,98 +187,6 @@ get_submodule_config () {
        printf '%s' "${value:-$default}"
 }
 
-
-#
-# Map submodule path to submodule name
-#
-# $1 = path
-#
-module_name()
-{
-       # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
-       sm_path="$1"
-       re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
-       name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
-               sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
-       test -z "$name" &&
-       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
-       printf '%s\n' "$name"
-}
-
-#
-# Clone a submodule
-#
-# $1 = submodule path
-# $2 = submodule name
-# $3 = URL to clone
-# $4 = reference repository to reuse (empty for independent)
-# $5 = depth argument for shallow clones (empty for deep)
-#
-# Prior to calling, cmd_update checks that a possibly existing
-# path is not a git repository.
-# Likewise, cmd_add checks that path does not exist at all,
-# since it is the location of a new submodule.
-#
-module_clone()
-{
-       sm_path=$1
-       name=$2
-       url=$3
-       reference="$4"
-       depth="$5"
-       quiet=
-       if test -n "$GIT_QUIET"
-       then
-               quiet=-q
-       fi
-
-       gitdir=
-       gitdir_base=
-       base_name=$(dirname "$name")
-
-       gitdir=$(git rev-parse --git-dir)
-       gitdir_base="$gitdir/modules/$base_name"
-       gitdir="$gitdir/modules/$name"
-
-       if test -d "$gitdir"
-       then
-               mkdir -p "$sm_path"
-               rm -f "$gitdir/index"
-       else
-               mkdir -p "$gitdir_base"
-               (
-                       clear_local_git_env
-                       git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \
-                               --separate-git-dir "$gitdir" "$url" "$sm_path"
-               ) ||
-               die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
-       fi
-
-       # We already are at the root of the work tree but cd_to_toplevel will
-       # resolve any symlinks that might be present in $PWD
-       a=$(cd_to_toplevel && cd "$gitdir" && pwd)/
-       b=$(cd_to_toplevel && cd "$sm_path" && pwd)/
-       # Remove all common leading directories after a sanity check
-       if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then
-               die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")"
-       fi
-       while test "${a%%/*}" = "${b%%/*}"
-       do
-               a=${a#*/}
-               b=${b#*/}
-       done
-       # Now chop off the trailing '/'s that were added in the beginning
-       a=${a%/}
-       b=${b%/}
-
-       # Turn each leading "*/" component into "../"
-       rel=$(printf '%s\n' "$b" | sed -e 's|[^/][^/]*|..|g')
-       printf '%s\n' "gitdir: $rel/$a" >"$sm_path/.git"
-
-       rel=$(printf '%s\n' "$a" | sed -e 's|[^/][^/]*|..|g')
-       (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
-}
-
 isnumber()
 {
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
@@ -481,7 +347,7 @@ Use -f if you really want to add it." >&2
                                echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
                        fi
                fi
-               module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit
+               git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit
                (
                        clear_local_git_env
                        cd "$sm_path" &&
@@ -541,7 +407,7 @@ cmd_foreach()
        # command in the subshell (and a recursive call to this function)
        exec 3<&0
 
-       module_list |
+       git submodule--helper list --prefix "$wt_prefix"|
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
@@ -549,7 +415,7 @@ cmd_foreach()
                then
                        displaypath=$(relative_path "$sm_path")
                        say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
-                       name=$(module_name "$sm_path")
+                       name=$(git submodule--helper name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
                                clear_local_git_env
@@ -601,11 +467,11 @@ cmd_init()
                shift
        done
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
 
                displaypath=$(relative_path "$sm_path")
 
@@ -683,11 +549,11 @@ cmd_deinit()
                die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
        fi
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
 
                displaypath=$(relative_path "$sm_path")
 
@@ -799,7 +665,7 @@ cmd_update()
        fi
 
        cloned_modules=
-       module_list "$@" | {
+       git submodule--helper list --prefix "$wt_prefix" "$@" | {
        err=
        while read mode sha1 stage sm_path
        do
@@ -809,7 +675,7 @@ cmd_update()
                        echo >&2 "Skipping unmerged submodule $prefix$sm_path"
                        continue
                fi
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                branch=$(get_submodule_config "$name" branch master)
                if ! test -z "$update"
@@ -843,7 +709,7 @@ Maybe you want to use 'update --init'?")"
 
                if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git
                then
-                       module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit
+                       git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit
                        cloned_modules="$cloned_modules;$name"
                        subsha1=
                else
@@ -1073,7 +939,7 @@ cmd_summary() {
                        # Respect the ignore setting for --for-status.
                        if test -n "$for_status"
                        then
-                               name=$(module_name "$sm_path")
+                               name=$(git submodule--helper name "$sm_path")
                                ignore_config=$(get_submodule_config "$name" ignore none)
                                test $status != A && test $ignore_config = all && continue
                        fi
@@ -1231,11 +1097,11 @@ cmd_status()
                shift
        done
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                displaypath=$(relative_path "$prefix$sm_path")
                if test "$stage" = U
@@ -1308,11 +1174,11 @@ cmd_sync()
                esac
        done
        cd_to_toplevel
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path")
+               name=$(git submodule--helper name "$sm_path")
                url=$(git config -f .gitmodules --get submodule."$name".url)
 
                # Possibly a url relative to parent
diff --git a/git.c b/git.c
index 5feba410cab6d95e3b9ff9745db56a9d045f0c20..6ed824cacfccddcb01104835b45ab934ff3f443e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -417,7 +417,7 @@ static struct cmd_struct commands[] = {
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
        { "init", cmd_init_db, NO_SETUP },
        { "init-db", cmd_init_db, NO_SETUP },
-       { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP },
+       { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
        { "log", cmd_log, RUN_SETUP },
        { "ls-files", cmd_ls_files, RUN_SETUP },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
@@ -470,6 +470,7 @@ static struct cmd_struct commands[] = {
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
+       { "submodule--helper", cmd_submodule__helper, RUN_SETUP },
        { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
        { "tag", cmd_tag, RUN_SETUP },
        { "unpack-file", cmd_unpack_file, RUN_SETUP },
index 2028b554f48798c87852cdb3c5ab6b558ad6722e..fcc606eab7353e67f82de873c3532cefc137a9e5 100755 (executable)
@@ -12452,8 +12452,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
     set viewchanged(1) 0
     set vdatemode(1) 0
     addviewmenu 1
-    .bar.view entryconf [mca "Edit view..."] -state normal
-    .bar.view entryconf [mca "Delete view"] -state normal
+    .bar.view entryconf [mca "&Edit view..."] -state normal
+    .bar.view entryconf [mca "&Delete view"] -state normal
 }
 
 if {[info exists permviews]} {
index 61073ebf6b56824ec5ade2bf43a6a31a17e5b96e..909a56463fe6ddb35427301624ba648950070787 100644 (file)
@@ -89,71 +89,71 @@ msgid "Cancel"
 msgstr "Отказ"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Обновяване"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Презареждане"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Наново прочитане на настройките"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Изброяване на указателите"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Стартиране на „git gui“"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Спиране на програмата"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Файл"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Настройки"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Редактиране"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Нов изглед…"
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Редактиране на изгледа…"
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Изтриване на изгледа"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Всички файлове"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Изглед"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Относно gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Клавишни комбинации"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Помощ"
 
 #: gitk:2185 gitk:8652
index 976037a645041e9aad0be5ee1350d1c8af783835..5ad066f7ce84c4be7801a467498ee3de3abccbf9 100644 (file)
@@ -9,7 +9,7 @@ msgstr ""
 "Project-Id-Version: gitk\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2015-05-17 14:32+1000\n"
-"PO-Revision-Date: 2015-02-01 22:49-0700\n"
+"PO-Revision-Date: 2015-10-05 22:23-0600\n"
 "Last-Translator: Alex Henrie <alexhenrie24@gmail.com>\n"
 "Language-Team: Catalan\n"
 "Language: ca\n"
@@ -17,7 +17,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 1.7.3\n"
+"X-Generator: Poedit 1.8.5\n"
 
 #: gitk:140
 msgid "Couldn't get list of unmerged files:"
@@ -91,71 +91,71 @@ msgid "Cancel"
 msgstr "Cancel·la"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Actualitza"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Recarrega"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Rellegeix les referències"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Llista les referències"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Inicia el git gui"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Surt"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Fitxer"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Preferències"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Edita"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Vista nova..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Edita la vista..."
 
 #: gitk:2086
-msgid "Delete view"
-msgstr "Suprimeix vista"
+msgid "&Delete view"
+msgstr "Suprimeix la vista"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Tots els fitxers"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Vista"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Quant al gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Associacions de tecles"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Ajuda"
 
 #: gitk:2185 gitk:8652
@@ -330,7 +330,7 @@ msgstr "Elimina aquesta branca"
 
 #: gitk:2649
 msgid "Copy branch name"
-msgstr ""
+msgstr "Copia el nom de branca"
 
 #: gitk:2656
 msgid "Highlight this too"
@@ -350,7 +350,7 @@ msgstr "Culpabilitat de la comissió mare"
 
 #: gitk:2660
 msgid "Copy path"
-msgstr ""
+msgstr "Copia el camí"
 
 #: gitk:2667
 msgid "Show origin of this line"
@@ -408,11 +408,11 @@ msgstr "<Fi>\t\tVés a l'última comissió"
 
 #: gitk:3052
 msgid "<Up>, p, k\tMove up one commit"
-msgstr "<Amunt>, p, k\tMou-te una comissió amunt"
+msgstr "<Amunt>, p, k\tMou-te cap amunt per una comissió"
 
 #: gitk:3053
 msgid "<Down>, n, j\tMove down one commit"
-msgstr "<Avall>, n, j\tMou-te una comissió avall"
+msgstr "<Avall>, n, j\tMou-te cap avall per una comissió"
 
 #: gitk:3054
 msgid "<Left>, z, h\tGo back in history list"
@@ -430,11 +430,11 @@ msgstr ""
 
 #: gitk:3057
 msgid "<PageUp>\tMove up one page in commit list"
-msgstr "<RePàg>\tBaixa una pàgina en la llista de comissions"
+msgstr "<RePàg>\tMou-te cap amunt per una pàgina en la llista de comissions"
 
 #: gitk:3058
 msgid "<PageDown>\tMove down one page in commit list"
-msgstr "<AvPàg>\tBaixa per una pàgina en la llista de comissions"
+msgstr "<AvPàg>\tMou-te cap avall per una pàgina en la llista de comissions"
 
 #: gitk:3059
 #, tcl-format
@@ -449,50 +449,50 @@ msgstr "<%s-Fi>\tDesplaça't a la part inferior de la llista de comissions"
 #: gitk:3061
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
-msgstr "<%s-Amunt>\tDesplaça la llista de comissions una línia cap amunt"
+msgstr "<%s-Amunt>\tDesplaça la llista de comissions cap amunt per una línia"
 
 #: gitk:3062
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
-msgstr "<%s-Avall>\tDesplaça la llista de comissions una línia cap avall"
+msgstr "<%s-Avall>\tDesplaça la llista de comissions cap avall per una línia"
 
 #: gitk:3063
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
-msgstr "<%s-RePàg>\tDesplaça la llista de comissions amunt per una pàgina"
+msgstr "<%s-RePàg>\tDesplaça la llista de comissions cap amunt per una pàgina"
 
 #: gitk:3064
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
-msgstr "<%s-AvPàg>\tDesplaça la llista de comissions una pàgina cap avall"
+msgstr "<%s-AvPàg>\tDesplaça la llista de comissions cap avall per una pàgina"
 
 #: gitk:3065
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
-msgstr "<Maj-Amunt>\tCerca cap enrere (amunt, les comissions més noves)"
+msgstr "<Maj-Amunt>\tCerca cap enrere (cap amunt, les comissions més noves)"
 
 #: gitk:3066
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
-msgstr "<Maj-Avall>\tCerca cap endavant (avall, les comissions més velles)"
+msgstr "<Maj-Avall>\tCerca cap endavant (cap avall, les comissions més velles)"
 
 #: gitk:3067
 msgid "<Delete>, b\tScroll diff view up one page"
-msgstr "<Supr>, b\tDesplaça la vista de diferència una pàgina cap amunt"
+msgstr "<Supr>, b\tDesplaça la vista de diferència cap amunt per una pàgina"
 
 #: gitk:3068
 msgid "<Backspace>\tScroll diff view up one page"
-msgstr "<Retrocés>\tDesplaça la vista de diferència una pàgina cap amunt"
+msgstr "<Retrocés>\tDesplaça la vista de diferència cap amunt per una pàgina"
 
 #: gitk:3069
 msgid "<Space>\t\tScroll diff view down one page"
-msgstr "<Espai>\t\tDesplaça la vista de diferència una pàgina cap avall"
+msgstr "<Espai>\t\tDesplaça la vista de diferència cap avall per una pàgina"
 
 #: gitk:3070
 msgid "u\t\tScroll diff view up 18 lines"
-msgstr "u\t\tDesplaça la vista de diferència 18 línies cap amunt"
+msgstr "u\t\tDesplaça la vista de diferència cap amunt per 18 línies"
 
 #: gitk:3071
 msgid "d\t\tScroll diff view down 18 lines"
-msgstr "d\t\tDesplaça la vista de diferència 18 línies cap avall "
+msgstr "d\t\tDesplaça la vista de diferència cap avall per 18 línies"
 
 #: gitk:3072
 #, tcl-format
@@ -509,9 +509,8 @@ msgid "<Return>\tMove to next find hit"
 msgstr "<Retorn>\tMou-te a la propera coincidència de la cerca"
 
 #: gitk:3075
-#, fuzzy
 msgid "g\t\tGo to commit"
-msgstr "<Fi>\t\tVés a l'última comissió"
+msgstr "g\t\tVés a l'última comissió"
 
 #: gitk:3076
 msgid "/\t\tFocus the search box"
@@ -668,9 +667,8 @@ msgid "Matches all Commit Info criteria"
 msgstr "Coincideix amb tots els criteris d'informació de comissió"
 
 #: gitk:4086
-#, fuzzy
 msgid "Matches no Commit Info criteria"
-msgstr "Coincideix amb tots els criteris d'informació de comissió"
+msgstr "No coincideix amb cap criteri d'informació de comissió"
 
 #: gitk:4087
 msgid "Changes to Files:"
@@ -1310,7 +1308,7 @@ msgstr "fons de la línia marcada"
 
 #: gitk:11450
 msgid "Select bg"
-msgstr "fons de la selecció"
+msgstr "Fons de la selecció"
 
 #: gitk:11459
 msgid "Fonts: press to choose"
@@ -1354,6 +1352,8 @@ msgid ""
 "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
 " Gitk requires at least Tcl/Tk 8.4."
 msgstr ""
+"Perdó, el gitk no pot executar-se amb aquesta versió de Tcl/Tk.\n"
+" El Gitk requereix com a mínim el Tcl/Tk 8.4."
 
 #: gitk:12269
 msgid "Cannot find a git repository here."
@@ -1367,6 +1367,3 @@ msgstr "Paràmetre ambigu '%s': és tant revisió com nom de fitxer"
 #: gitk:12328
 msgid "Bad arguments to gitk:"
 msgstr "Paràmetres dolents al gitk:"
-
-#~ msgid "mc"
-#~ msgstr "mc"
index 1a3264b2b0020a0912da8497259ce9485288589d..d9ba4052e20bc8dddc091e3a60103d33af349a63 100644 (file)
@@ -9,7 +9,7 @@ msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2015-05-17 14:32+1000\n"
-"PO-Revision-Date: 2010-01-27 20:27+0100\n"
+"PO-Revision-Date: 2015-10-20 14:20+0200\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "Language: \n"
@@ -89,72 +89,72 @@ msgid "Cancel"
 msgstr "Abbrechen"
 
 #: gitk:2069
-msgid "Update"
-msgstr "Aktualisieren"
+msgid "&Update"
+msgstr "&Aktualisieren"
 
 #: gitk:2070
-msgid "Reload"
-msgstr "Neu laden"
+msgid "&Reload"
+msgstr "&Neu laden"
 
 #: gitk:2071
-msgid "Reread references"
-msgstr "Zweige neu laden"
+msgid "Reread re&ferences"
+msgstr "&Zweige neu laden"
 
 #: gitk:2072
-msgid "List references"
-msgstr "Zweige/Markierungen auflisten"
+msgid "&List references"
+msgstr "Zweige/Markierungen auf&listen"
 
 #: gitk:2074
-msgid "Start git gui"
-msgstr "»git gui« starten"
+msgid "Start git &gui"
+msgstr "»git &gui« starten"
 
 #: gitk:2076
-msgid "Quit"
-msgstr "Beenden"
+msgid "&Quit"
+msgstr "&Beenden"
 
 #: gitk:2068
-msgid "File"
-msgstr "Datei"
+msgid "&File"
+msgstr "&Datei"
 
 #: gitk:2080
-msgid "Preferences"
-msgstr "Einstellungen"
+msgid "&Preferences"
+msgstr "&Einstellungen"
 
 #: gitk:2079
-msgid "Edit"
-msgstr "Bearbeiten"
+msgid "&Edit"
+msgstr "&Bearbeiten"
 
 #: gitk:2084
-msgid "New view..."
-msgstr "Neue Ansicht ..."
+msgid "&New view..."
+msgstr "&Neue Ansicht ..."
 
 #: gitk:2085
-msgid "Edit view..."
-msgstr "Ansicht bearbeiten ..."
+msgid "&Edit view..."
+msgstr "Ansicht &bearbeiten ..."
 
 #: gitk:2086
-msgid "Delete view"
-msgstr "Ansicht entfernen"
+msgid "&Delete view"
+msgstr "Ansicht &entfernen"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
-msgstr "Alle Dateien"
+msgid "&All files"
+msgstr "&Alle Dateien"
 
 #: gitk:2083 gitk:4067
-msgid "View"
-msgstr "Ansicht"
+msgid "&View"
+msgstr "&Ansicht"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
-msgstr "Über gitk"
+msgid "&About gitk"
+msgstr "Über &gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
-msgstr "Tastenkürzel"
+msgid "&Key bindings"
+msgstr "&Tastenkürzel"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
-msgstr "Hilfe"
+msgid "&Help"
+msgstr "&Hilfe"
 
 #: gitk:2185 gitk:8652
 msgid "SHA1 ID:"
index 6402a411a6e866595617de1ad3641105b5067a42..ddcb0a5f68dc27127d72f2a42f6d77d2bbf7c23f 100644 (file)
@@ -91,71 +91,71 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Actualizar"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr ""
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Releer referencias"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Lista de referencias"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr ""
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Salir"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Archivo"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Preferencias"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Editar"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Nueva vista..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Modificar vista..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Eliminar vista"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Todos los archivos"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Vista"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Acerca de gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Combinaciones de teclas"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Ayuda"
 
 #: gitk:2185 gitk:8652
index 6b1f05c6b9061e4cf2a98664b00f927a58ff0cea..80f72fb4535a2cfb4dc33b9f12fd002beb3f5d86 100644 (file)
@@ -93,71 +93,71 @@ msgid "Cancel"
 msgstr "Annuler"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Mise à jour"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Recharger"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Relire les références"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Lister les références"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Démarrer git gui"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Quitter"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Fichier"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Préférences"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Éditer"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Nouvelle vue..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Éditer la vue..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Supprimer la vue"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Tous les fichiers"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Vue"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "À propos de gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Raccourcis clavier"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Aide"
 
 #: gitk:2185 gitk:8652
index 00023f1cab4103f76d10438eeb793e7f6c1fd9b7..66fd75ba5b1634f13f067e4aab3cc1d921ca804b 100644 (file)
@@ -88,71 +88,71 @@ msgid "Cancel"
 msgstr "Visszavonás"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Frissités"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Újratöltés"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Referenciák újraolvasása"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Referenciák listázása"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Git gui indítása"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Kilépés"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Fájl"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Beállítások"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Szerkesztés"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Új nézet ..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Nézet szerkesztése ..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Nézet törlése"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Minden fájl"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Nézet"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Gitk névjegy"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Billentyűkombináció"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Segítség"
 
 #: gitk:2185 gitk:8652
index b8212b17009b1909ff22f4f48f70518a7531597c..b5f002db7d52d3106d3a52dbb03047ad67233b62 100644 (file)
@@ -89,71 +89,71 @@ msgid "Cancel"
 msgstr "Annulla"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Aggiorna"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Ricarica"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Rileggi riferimenti"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Elenca riferimenti"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Avvia git gui"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Esci"
 
 #: gitk:2068
-msgid "File"
-msgstr "File"
+msgid "&File"
+msgstr "&File"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Preferenze"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Modifica"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Nuova vista..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Modifica vista..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Elimina vista"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Tutti i file"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Vista"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Informazioni su gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Scorciatoie da tastiera"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Aiuto"
 
 #: gitk:2185 gitk:8652
index 8bbc67f6b58ab8a850f4950f834dbc8448ae22d3..59e42a89fd7e85dccc5acadbc1ad5bc4c8cd5e8f 100644 (file)
@@ -90,72 +90,72 @@ msgid "Cancel"
 msgstr "キャンセル"
 
 #: gitk:2069
-msgid "Update"
-msgstr "更新"
+msgid "&Update"
+msgstr "更新(&U)"
 
 #: gitk:2070
-msgid "Reload"
-msgstr "リロード"
+msgid "&Reload"
+msgstr "リロード(&R)"
 
 #: gitk:2071
-msgid "Reread references"
-msgstr "リファレンスを再読み込み"
+msgid "Reread re&ferences"
+msgstr "リファレンスを再読み込み(&F)"
 
 #: gitk:2072
-msgid "List references"
-msgstr "リファレンスリストを表示"
+msgid "&List references"
+msgstr "リファレンスリストを表示(&L)"
 
 #: gitk:2074
-msgid "Start git gui"
-msgstr "git gui の開始"
+msgid "Start git &gui"
+msgstr "git gui の開始(&G)"
 
 #: gitk:2076
-msgid "Quit"
-msgstr "終了"
+msgid "&Quit"
+msgstr "終了(&Q)"
 
 #: gitk:2068
-msgid "File"
-msgstr "ファイル"
+msgid "&File"
+msgstr "ファイル(&F)"
 
 #: gitk:2080
-msgid "Preferences"
-msgstr "設定"
+msgid "&Preferences"
+msgstr "設定(&P)"
 
 #: gitk:2079
-msgid "Edit"
-msgstr "編集"
+msgid "&Edit"
+msgstr "編集(&E)"
 
 #: gitk:2084
-msgid "New view..."
-msgstr "新規ビュー..."
+msgid "&New view..."
+msgstr "新規ビュー...(&N)"
 
 #: gitk:2085
-msgid "Edit view..."
-msgstr "ビュー編集..."
+msgid "&Edit view..."
+msgstr "ビュー編集...(&E)"
 
 #: gitk:2086
-msgid "Delete view"
-msgstr "ビュー削除"
+msgid "&Delete view"
+msgstr "ビュー削除(&D)"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
-msgstr "全てのファイル"
+msgid "&All files"
+msgstr "全てのファイル(&A)"
 
 #: gitk:2083 gitk:4067
-msgid "View"
-msgstr "ビュー"
+msgid "&View"
+msgstr "ビュー(&V)"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
-msgstr "gitk について"
+msgid "&About gitk"
+msgstr "gitk について(&A)"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
-msgstr "キーバインディング"
+msgid "&Key bindings"
+msgstr "キーバインディング(&K)"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
-msgstr "ヘルプ"
+msgid "&Help"
+msgstr "ヘルプ(&H)"
 
 #: gitk:2185 gitk:8652
 msgid "SHA1 ID:"
index 07e5d63b65cced4233d0a69f408b45d6ad072c6a..3f78f1b7482614098e295f3f1ffb6c0925ae6383 100644 (file)
@@ -90,71 +90,71 @@ msgid "Cancel"
 msgstr "Cancelar"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Atualizar"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Recarregar"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Ler as referências novamente"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Listar referências"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Iniciar Git GUI"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Sair"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Arquivo"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Preferências"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Editar"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Nova vista..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Editar vista..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Apagar vista"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Todos os arquivos"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Exibir"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Sobre o gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Atalhos de teclado"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Ajuda"
 
 #: gitk:2185 gitk:8652
index f1bac879e3a6dcd05c651d23223dccd126daaa09..17ed026aa7da7c636457c302b2c28f7b866ae45a 100644 (file)
@@ -1,18 +1,24 @@
-#
 # Translation of gitk to Russian.
 #
+# Translators:
+# 0xAX <kuleshovmail@gmail.com>, 2014
+# Alex Riesen <raa.lkml@gmail.com>, 2015
+# Dimitriy Ryazantcev <DJm00n@mail.ru>, 2015
+# Dmitry Potapov <dpotapov@gmail.com>, 2009
+# Skip <bsvskip@rambler.ru>, 2011
 msgid ""
 msgstr ""
-"Project-Id-Version: gitk\n"
+"Project-Id-Version: Git Russian Localization Project\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2015-05-17 14:32+1000\n"
-"PO-Revision-Date: 2009-04-24 16:00+0200\n"
-"Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n"
-"Language-Team: Russian\n"
-"Language: \n"
+"PO-Revision-Date: 2015-10-12 10:14+0000\n"
+"Last-Translator: Dimitriy Ryazantcev <DJm00n@mail.ru>\n"
+"Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Language: ru\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
 
 #: gitk:140
 msgid "Couldn't get list of unmerged files:"
@@ -20,15 +26,15 @@ msgstr "Невозможно получить список файлов неза
 
 #: gitk:212 gitk:2381
 msgid "Color words"
-msgstr ""
+msgstr "Цветные слова"
 
 #: gitk:217 gitk:2381 gitk:8220 gitk:8253
 msgid "Markup words"
-msgstr ""
+msgstr "Помеченые слова"
 
 #: gitk:324
 msgid "Error parsing revisions:"
-msgstr "Ð\9eÑ\88ибка Ð² Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\82оÑ\80е Ð²ÐµÑ\80Ñ\81ии:"
+msgstr "Ð\9eÑ\88ибка Ð¿Ñ\80и Ñ\80азбоÑ\80е Ñ\80едакÑ\86ии:"
 
 #: gitk:380
 msgid "Error executing --argscmd command:"
@@ -36,17 +42,13 @@ msgstr "Ошибка выполнения команды заданной --args
 
 #: gitk:393
 msgid "No files selected: --merge specified but no files are unmerged."
-msgstr ""
-"Файлы не выбраны: указан --merge, но не было найдено ни одного файла где эта "
-"операция должна быть завершена."
+msgstr "Файлы не выбраны: указан --merge, но не было найдено ни одного файла где эта операция должна быть завершена."
 
 #: gitk:396
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
-msgstr ""
-"Файлы не выбраны: указан --merge, но в рамках указанного ограничения на "
-"имена файлов нет ни одного где эта операция должна быть завершена."
+msgstr "Файлы не выбраны: указан --merge, но в рамках указанного ограничения на имена файлов нет ни одного где эта операция должна быть завершена."
 
 #: gitk:418 gitk:566
 msgid "Error executing git log:"
@@ -58,7 +60,7 @@ msgstr "Чтение"
 
 #: gitk:496 gitk:4525
 msgid "Reading commits..."
-msgstr "ЧÑ\82ение Ð²ÐµÑ\80Ñ\81ий..."
+msgstr "ЧÑ\82ение ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82ов..."
 
 #: gitk:499 gitk:1637 gitk:4528
 msgid "No commits selected"
@@ -74,7 +76,7 @@ msgstr "Ошибка обработки вывода команды git log:"
 
 #: gitk:1740
 msgid "No commit information available"
-msgstr "Нет информации о состоянии"
+msgstr "Нет информации о коммите"
 
 #: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521
 msgid "OK"
@@ -86,71 +88,71 @@ msgid "Cancel"
 msgstr "Отмена"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Обновить"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Перечитать"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Обновить список ссылок"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Список ссылок"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Запустить git gui"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Завершить"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Файл"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Настройки"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Редактировать"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Новое представление..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Редактировать представление..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Удалить представление"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Все файлы"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Представление"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "О gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Назначения клавиатуры"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Подсказка"
 
 #: gitk:2185 gitk:8652
@@ -167,7 +169,7 @@ msgstr "Поиск"
 
 #: gitk:2295
 msgid "commit"
-msgstr "состояние"
+msgstr "коммит"
 
 #: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827
 #: gitk:6912
@@ -184,7 +186,7 @@ msgstr "добавив/удалив строку:"
 
 #: gitk:2304 gitk:4779
 msgid "changing lines matching:"
-msgstr ""
+msgstr "изменяя совпадающие строки:"
 
 #: gitk:2313 gitk:2315 gitk:4766
 msgid "Exact"
@@ -217,7 +219,7 @@ msgstr "Автор"
 
 #: gitk:2319 gitk:4871 gitk:6786 gitk:7326
 msgid "Committer"
-msgstr "СоÑ\85Ñ\80анивÑ\88ий Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\9aоммиÑ\82еÑ\80"
 
 #: gitk:2350
 msgid "Search"
@@ -245,7 +247,7 @@ msgstr "Игнорировать пробелы"
 
 #: gitk:2378 gitk:2380 gitk:7959 gitk:8206
 msgid "Line diff"
-msgstr ""
+msgstr "Изменения строк"
 
 #: gitk:2445
 msgid "Patch"
@@ -257,11 +259,11 @@ msgstr "Файлы"
 
 #: gitk:2617 gitk:2637
 msgid "Diff this -> selected"
-msgstr "Сравнить это состояние с выделенным"
+msgstr "Сравнить этот коммит с выделенным"
 
 #: gitk:2618 gitk:2638
 msgid "Diff selected -> this"
-msgstr "Сравнить выделенное с этим состоянием"
+msgstr "Сравнить выделенный с этим коммитом"
 
 #: gitk:2619 gitk:2639
 msgid "Make patch"
@@ -273,63 +275,59 @@ msgstr "Создать метку"
 
 #: gitk:2621 gitk:9371
 msgid "Write commit to file"
-msgstr "СоÑ\85Ñ\80аниÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f в файл"
+msgstr "СоÑ\85Ñ\80аниÑ\82Ñ\8c ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82 в файл"
 
 #: gitk:2622 gitk:9428
 msgid "Create new branch"
-msgstr "СоздаÑ\82Ñ\8c Ð²ÐµÑ\82вÑ\8c"
+msgstr "СоздаÑ\82Ñ\8c Ð²ÐµÑ\82кÑ\83"
 
 #: gitk:2623
 msgid "Cherry-pick this commit"
-msgstr "СкопиÑ\80оваÑ\82Ñ\8c Ñ\8dÑ\82о Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\9eÑ\82боÑ\80 Ð»Ñ\83Ñ\87Ñ\88его Ð´Ð»Ñ\8f Ñ\8dÑ\82ого ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а"
 
 #: gitk:2624
 msgid "Reset HEAD branch to here"
-msgstr "Установить HEAD на это состояние"
+msgstr "Установить HEAD на этот коммит"
 
 #: gitk:2625
-#, fuzzy
 msgid "Mark this commit"
-msgstr "СкопиÑ\80оваÑ\82Ñ\8c Ñ\8dÑ\82о Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\9fомеÑ\82иÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82"
 
 #: gitk:2626
 msgid "Return to mark"
-msgstr ""
+msgstr "Вернуться на пометку"
 
 #: gitk:2627
 msgid "Find descendant of this and mark"
-msgstr ""
+msgstr "Найти и пометить потомка этого коммита"
 
 #: gitk:2628
 msgid "Compare with marked commit"
-msgstr ""
+msgstr "Сравнить с помеченным коммитом"
 
 #: gitk:2629 gitk:2640
-#, fuzzy
 msgid "Diff this -> marked commit"
-msgstr "Сравнить это состояние с выделенным"
+msgstr "Сравнить выделенное с помеченным коммитом"
 
 #: gitk:2630 gitk:2641
-#, fuzzy
 msgid "Diff marked commit -> this"
-msgstr "СÑ\80авниÑ\82Ñ\8c Ð²Ñ\8bделенное Ñ\81 Ñ\8dÑ\82им Ñ\81оÑ\81Ñ\82оÑ\8fнием"
+msgstr "СÑ\80авниÑ\82Ñ\8c Ð¿Ð¾Ð¼ÐµÑ\87еннÑ\8bй Ñ\81 Ñ\8dÑ\82им ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82ом"
 
 #: gitk:2631
-#, fuzzy
 msgid "Revert this commit"
-msgstr "СкопиÑ\80оваÑ\82Ñ\8c Ñ\8dÑ\82о Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\92озвÑ\80аÑ\82 Ñ\8dÑ\82ого ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а"
 
 #: gitk:2647
 msgid "Check out this branch"
-msgstr "Ð\9fеÑ\80ейÑ\82и Ð½Ð° Ñ\8dÑ\82Ñ\83 Ð²ÐµÑ\82вÑ\8c"
+msgstr "Ð\9fеÑ\80ейÑ\82и Ð½Ð° Ñ\8dÑ\82Ñ\83 Ð²ÐµÑ\82кÑ\83"
 
 #: gitk:2648
 msgid "Remove this branch"
-msgstr "УдалиÑ\82Ñ\8c Ñ\8dÑ\82Ñ\83 Ð²ÐµÑ\82вÑ\8c"
+msgstr "УдалиÑ\82Ñ\8c Ñ\8dÑ\82Ñ\83 Ð²ÐµÑ\82кÑ\83"
 
 #: gitk:2649
 msgid "Copy branch name"
-msgstr ""
+msgstr "Копировать имя ветки"
 
 #: gitk:2656
 msgid "Highlight this too"
@@ -345,11 +343,11 @@ msgstr "Программа сравнения"
 
 #: gitk:2659
 msgid "Blame parent commit"
-msgstr "Ð\90нноÑ\82иÑ\80оваÑ\82Ñ\8c Ñ\80одиÑ\82елÑ\8cÑ\81кое Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\90вÑ\82оÑ\80Ñ\8b Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ Ñ\80одиÑ\82елÑ\8cÑ\81кого ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а"
 
 #: gitk:2660
 msgid "Copy path"
-msgstr ""
+msgstr "Копировать путь"
 
 #: gitk:2667
 msgid "Show origin of this line"
@@ -360,21 +358,14 @@ msgid "Run git gui blame on this line"
 msgstr "Запустить git gui blame для этой строки"
 
 #: gitk:3014
-#, fuzzy
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright © 2005-2014 Paul Mackerras\n"
+"Copyright  2005-2014 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
-msgstr ""
-"\n"
-"Gitk - программа просмотра истории репозиториев Git\n"
-"\n"
-"Copyright \\u00a9 2005-2010 Paul Mackerras\n"
-"\n"
-"Использование и распространение согласно условиям GNU General Public License"
+msgstr "\nGitk - программа просмотра истории репозиториев git\n\n©  2005-2014 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License"
 
 #: gitk:3022 gitk:3089 gitk:9857
 msgid "Close"
@@ -394,87 +385,84 @@ msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tЗавершить"
 
 #: gitk:3049
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "<%s-W>\t\tClose window"
-msgstr "<%s-F>\t\tПоиск"
+msgstr "<%s-W>\t\tЗакрыть окно"
 
 #: gitk:3050
 msgid "<Home>\t\tMove to first commit"
-msgstr "<Home>\t\tПерейти к первому состоянию"
+msgstr "<Home>\t\tПерейти к первому коммиту"
 
 #: gitk:3051
 msgid "<End>\t\tMove to last commit"
-msgstr "<End>\t\tПерейти к последнему состоянию"
+msgstr "<End>\t\tПерейти к последнему коммиту"
 
 #: gitk:3052
-#, fuzzy
 msgid "<Up>, p, k\tMove up one commit"
-msgstr "<Up>, p, i\tПерейти к следующему состоянию"
+msgstr "<Up>, p, k\tПерейти на один коммит вверх"
 
 #: gitk:3053
-#, fuzzy
 msgid "<Down>, n, j\tMove down one commit"
-msgstr "<Down>, n, k\tПерейти к предыдущему состоянию"
+msgstr "<Down>, n, j\tПерейти на один коммит вниз"
 
 #: gitk:3054
-#, fuzzy
 msgid "<Left>, z, h\tGo back in history list"
-msgstr "<Left>, z, j\tПоказать ранее посещённое состояние"
+msgstr "<Left>, z, h\tПоказать ранее посещённое состояние"
 
 #: gitk:3055
 msgid "<Right>, x, l\tGo forward in history list"
-msgstr "<Right>, x, l\tÐ\9fоказаÑ\82Ñ\8c Ñ\81ледÑ\83Ñ\8eÑ\89ее Ð¿Ð¾Ñ\81еÑ\89Ñ\91нное Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "<Right>, x, l\tÐ\9fоказаÑ\82Ñ\8c Ñ\81ледÑ\83Ñ\8eÑ\89ий Ð¿Ð¾Ñ\81еÑ\89Ñ\91ннÑ\8bй ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82"
 
 #: gitk:3056
 #, tcl-format
 msgid "<%s-n>\tGo to n-th parent of current commit in history list"
-msgstr ""
+msgstr "<%s-n>\tПерейти на n родителя от текущего коммита"
 
 #: gitk:3057
 msgid "<PageUp>\tMove up one page in commit list"
-msgstr "<PageUp>\tПерейти на страницу выше в списке состояний"
+msgstr "<PageUp>\tПерейти на страницу выше в списке коммитов"
 
 #: gitk:3058
 msgid "<PageDown>\tMove down one page in commit list"
-msgstr "<PageDown>\tПерейти на страницу ниже в списке состояний"
+msgstr "<PageDown>\tПерейти на страницу ниже в списке коммитов"
 
 #: gitk:3059
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
-msgstr "<%s-Home>\tÐ\9fоказаÑ\82Ñ\8c Ð½Ð°Ñ\87ало Ñ\81пиÑ\81ка Ñ\81оÑ\81Ñ\82оÑ\8fний"
+msgstr "<%s-Home>\tÐ\9fеÑ\80ейÑ\82и Ð½Ð° Ð½Ð°Ñ\87ало Ñ\81пиÑ\81ка ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82ов"
 
 #: gitk:3060
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
-msgstr "<%s-End>\tÐ\9fоказаÑ\82Ñ\8c ÐºÐ¾Ð½ÐµÑ\86 Ñ\81пиÑ\81ка Ñ\81оÑ\81Ñ\82оÑ\8fний"
+msgstr "<%s-End>\tÐ\9fеÑ\80ейÑ\82и Ð½Ð° ÐºÐ¾Ð½ÐµÑ\86 Ñ\81пиÑ\81ка ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82ов"
 
 #: gitk:3061
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
-msgstr "<%s-Up>\tПровернуть список состояний вверх"
+msgstr "<%s-Up>\tПровернуть список коммитов вверх"
 
 #: gitk:3062
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
-msgstr "<%s-Down>\tПровернуть список состояний вниз"
+msgstr "<%s-Down>\tПровернуть список коммитов вниз"
 
 #: gitk:3063
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
-msgstr "<%s-PageUp>\tПровернуть список состояний на страницу вверх"
+msgstr "<%s-PageUp>\tПровернуть список коммитов на страницу вверх"
 
 #: gitk:3064
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
-msgstr "<%s-PageDown>\tПровернуть список состояний на страницу вниз"
+msgstr "<%s-PageDown>\tПровернуть список коммитов на страницу вниз"
 
 #: gitk:3065
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
-msgstr "<Shift-Up>\tПоиск в обратном порядке (вверх, среди новых состояний)"
+msgstr "<Shift-Up>\tПоиск в обратном порядке (вверх, среди новых коммитов)"
 
 #: gitk:3066
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
-msgstr "<Shift-Down>\tПоиск (вниз, среди старых состояний)"
+msgstr "<Shift-Down>\tПоиск (вниз, среди старых коммитов)"
 
 #: gitk:3067
 msgid "<Delete>, b\tScroll diff view up one page"
@@ -504,16 +492,15 @@ msgstr "<%s-F>\t\tПоиск"
 #: gitk:3073
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
-msgstr "<%s-G>\t\tПерейти к следующему найденному состоянию"
+msgstr "<%s-G>\t\tПерейти к следующему найденному коммиту"
 
 #: gitk:3074
 msgid "<Return>\tMove to next find hit"
-msgstr "<Return>\tПерейти к следующему найденному состоянию"
+msgstr "<Return>\tПерейти к следующему найденному коммиту"
 
 #: gitk:3075
-#, fuzzy
 msgid "g\t\tGo to commit"
-msgstr "<End>\t\tПерейти к последнему состоянию"
+msgstr "g\t\tПерейти на коммит"
 
 #: gitk:3076
 msgid "/\t\tFocus the search box"
@@ -521,7 +508,7 @@ msgstr "/\t\tПерейти к полю поиска"
 
 #: gitk:3077
 msgid "?\t\tMove to previous find hit"
-msgstr "?\t\tПерейти к предыдущему найденному состоянию"
+msgstr "?\t\tПерейти к предыдущему найденному коммиту"
 
 #: gitk:3078
 msgid "f\t\tScroll diff view to next file"
@@ -569,7 +556,7 @@ msgstr "Ошибка создания временного каталога %s:"
 #: gitk:3572
 #, tcl-format
 msgid "Error getting \"%s\" from %s:"
-msgstr "Ошибка получения \"%s\" из %s:"
+msgstr "Ошибка получения «%s» из %s:"
 
 #: gitk:3635
 msgid "command failed:"
@@ -577,7 +564,7 @@ msgstr "ошибка выполнения команды:"
 
 #: gitk:3784
 msgid "No such commit"
-msgstr "СоÑ\81Ñ\82оÑ\8fние Ð½Ðµ Ð½Ð°Ð¹Ð´ÐµÐ½Ð¾"
+msgstr "Ð\9aоммиÑ\82 Ð½Ðµ Ð½Ð°Ð¹Ð´ÐµÐ½"
 
 #: gitk:3798
 msgid "git gui blame: command failed:"
@@ -610,8 +597,7 @@ msgstr "Ошибка выполнения git blame: %s"
 #: gitk:3925
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
-msgstr ""
-"Эта строка принадлежит состоянию %s, которое не показано в этом представлении"
+msgstr "Эта строка принадлежит коммиту %s, который не показан в этом представлении"
 
 #: gitk:3939
 msgid "External diff viewer failed:"
@@ -627,103 +613,97 @@ msgstr "Запомнить представление"
 
 #: gitk:4075
 msgid "References (space separated list):"
-msgstr ""
+msgstr "Ссылки (разделённые пробелом):"
 
 #: gitk:4076
 msgid "Branches & tags:"
-msgstr ""
+msgstr "Ветки и метки"
 
 #: gitk:4077
-#, fuzzy
 msgid "All refs"
-msgstr "Ð\92Ñ\81е Ñ\84айлÑ\8b"
+msgstr "Ð\92Ñ\81е Ñ\81Ñ\81Ñ\8bлки"
 
 #: gitk:4078
 msgid "All (local) branches"
-msgstr ""
+msgstr "Все (локальные) ветки"
 
 #: gitk:4079
 msgid "All tags"
-msgstr ""
+msgstr "Все метки"
 
 #: gitk:4080
 msgid "All remote-tracking branches"
-msgstr ""
+msgstr "Все внешние отслеживаемые ветки"
 
 #: gitk:4081
 msgid "Commit Info (regular expressions):"
-msgstr ""
+msgstr "Информация о коммите (регулярные выражения):"
 
 #: gitk:4082
-#, fuzzy
 msgid "Author:"
-msgstr "Автор"
+msgstr "Автор:"
 
 #: gitk:4083
-#, fuzzy
 msgid "Committer:"
-msgstr "СоÑ\85Ñ\80анивÑ\88ий Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\9aоммиÑ\82еÑ\80:"
 
 #: gitk:4084
 msgid "Commit Message:"
-msgstr ""
+msgstr "Сообщение коммита:"
 
 #: gitk:4085
 msgid "Matches all Commit Info criteria"
-msgstr ""
+msgstr "Совпадает со всеми условиями информации о коммите"
 
 #: gitk:4086
 msgid "Matches no Commit Info criteria"
-msgstr ""
+msgstr "Не совпадает с условиями информации о коммите"
 
 #: gitk:4087
 msgid "Changes to Files:"
-msgstr ""
+msgstr "Изменения файлов:"
 
 #: gitk:4088
 msgid "Fixed String"
-msgstr ""
+msgstr "Обычная строка"
 
 #: gitk:4089
 msgid "Regular Expression"
-msgstr ""
+msgstr "Регулярное выражение:"
 
 #: gitk:4090
-#, fuzzy
 msgid "Search string:"
-msgstr "Ð\9fоиÑ\81к"
+msgstr "СÑ\82Ñ\80ока Ð´Ð»Ñ\8f Ð¿Ð¾Ð¸Ñ\81ка:"
 
 #: gitk:4091
 msgid ""
 "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
-msgstr ""
+msgstr "Даты коммита («2 недели назад», «2009-03-17 15:27:38», «17 марта 2009 15:27:38»):"
 
 #: gitk:4092
-#, fuzzy
 msgid "Since:"
 msgstr "С даты:"
 
 #: gitk:4093
-#, fuzzy
 msgid "Until:"
 msgstr "По дату:"
 
 #: gitk:4094
 msgid "Limit and/or skip a number of revisions (positive integer):"
-msgstr ""
+msgstr "Ограничить и/или пропустить количество редакций (положительное число):"
 
 #: gitk:4095
 msgid "Number to show:"
-msgstr ""
+msgstr "Показать количество:"
 
 #: gitk:4096
 msgid "Number to skip:"
-msgstr ""
+msgstr "Пропустить количество:"
 
 #: gitk:4097
 msgid "Miscellaneous options:"
-msgstr ""
+msgstr "Различные опции:"
 
 #: gitk:4098
 msgid "Strictly sort by date"
@@ -731,7 +711,7 @@ msgstr "Строгая сортировка по дате"
 
 #: gitk:4099
 msgid "Mark branch sides"
-msgstr "Ð\9eÑ\82меÑ\82иÑ\82Ñ\8c Ñ\81Ñ\82оÑ\80онÑ\8b Ð²ÐµÑ\82вей"
+msgstr "Ð\9eÑ\82меÑ\82иÑ\82Ñ\8c Ñ\81Ñ\82оÑ\80онÑ\8b Ð²ÐµÑ\82ок"
 
 #: gitk:4100
 msgid "Limit to first parent"
@@ -739,12 +719,11 @@ msgstr "Ограничить первым предком"
 
 #: gitk:4101
 msgid "Simple history"
-msgstr ""
+msgstr "Упрощенная история"
 
 #: gitk:4102
-#, fuzzy
 msgid "Additional arguments to git log:"
-msgstr "Ð\92клÑ\8eÑ\87иÑ\82Ñ\8c Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8f (аÑ\80гÑ\83менÑ\82Ñ\8b Ð´Ð»Ñ\8f git-log):"
+msgstr "Ð\94ополниÑ\82елÑ\8cнÑ\8bе Ð°Ñ\80гÑ\83менÑ\82Ñ\8b Ð´Ð»Ñ\8f git log:"
 
 #: gitk:4103
 msgid "Enter files and directories to include, one per line:"
@@ -752,21 +731,19 @@ msgstr "Файлы и каталоги для ограничения истор
 
 #: gitk:4104
 msgid "Command to generate more commits to include:"
-msgstr "Дополнительная команда для списка состояний:"
+msgstr "Дополнительная команда для списка коммитов:"
 
 #: gitk:4228
 msgid "Gitk: edit view"
-msgstr ""
+msgstr "Gitk: изменить представление"
 
 #: gitk:4236
-#, fuzzy
 msgid "-- criteria for selecting revisions"
-msgstr "Ошибка в идентификаторе версии:"
+msgstr "— критерий поиска редакций"
 
 #: gitk:4241
-#, fuzzy
 msgid "View Name"
-msgstr "Ð\9fÑ\80едÑ\81Ñ\82авление"
+msgstr "Ð\98мÑ\8f Ð¿Ñ\80едÑ\81Ñ\82авлениÑ\8f"
 
 #: gitk:4316
 msgid "Apply (F5)"
@@ -774,7 +751,7 @@ msgstr "Применить (F5)"
 
 #: gitk:4354
 msgid "Error in commit selection arguments:"
-msgstr "Ошибка в параметрах выбора состояний:"
+msgstr "Ошибка в параметрах выбора коммитов:"
 
 #: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374
 msgid "None"
@@ -798,23 +775,23 @@ msgstr "Не предок"
 
 #: gitk:5324
 msgid "Local changes checked in to index but not committed"
-msgstr "Ð\98зменениÑ\8f Ð·Ð°Ñ\80егиÑ\81Ñ\82Ñ\80иÑ\80ованнÑ\8bе Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е, Ð½Ð¾ Ð½Ðµ Ñ\81оÑ\85Ñ\80анÑ\91ннÑ\8bе"
+msgstr "Ð\9fÑ\80оиндекÑ\81иÑ\80ованнÑ\8bе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f"
 
 #: gitk:5360
 msgid "Local uncommitted changes, not checked in to index"
-msgstr "Ð\98зменениÑ\8f Ð² Ñ\80абоÑ\87ем ÐºÐ°Ñ\82алоге, Ð½Ðµ Ð·Ð°Ñ\80егиÑ\81Ñ\82Ñ\80иÑ\80ованнÑ\8bе Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е"
+msgstr "Ð\9dепÑ\80оиндекÑ\81иÑ\80ованнÑ\8bе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f"
 
 #: gitk:7134
 msgid "and many more"
-msgstr ""
+msgstr "и многое другое"
 
 #: gitk:7137
 msgid "many"
-msgstr ""
+msgstr "много"
 
 #: gitk:7328
 msgid "Tags:"
-msgstr "Таги:"
+msgstr "Ð\9cеÑ\82ки:"
 
 #: gitk:7345 gitk:7351 gitk:8825
 msgid "Parent"
@@ -826,7 +803,7 @@ msgstr "Потомок"
 
 #: gitk:7365
 msgid "Branch"
-msgstr "Ð\92еÑ\82вÑ\8c"
+msgstr "Ð\92еÑ\82ка"
 
 #: gitk:7368
 msgid "Follows"
@@ -851,9 +828,9 @@ msgid "Short SHA1 id %s is ambiguous"
 msgstr "Сокращённый SHA1 идентификатор %s неоднозначен"
 
 #: gitk:8678
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Revision %s is not known"
-msgstr "SHA1 идентификатор %s не найден"
+msgstr "Редакция %s не найдена"
 
 #: gitk:8688
 #, tcl-format
@@ -863,7 +840,7 @@ msgstr "SHA1 идентификатор %s не найден"
 #: gitk:8690
 #, tcl-format
 msgid "Revision %s is not in the current view"
-msgstr ""
+msgstr "Редакция %s не найдена в текущем представлении"
 
 #: gitk:8832 gitk:8847
 msgid "Date"
@@ -876,62 +853,60 @@ msgstr "Потомки"
 #: gitk:8898
 #, tcl-format
 msgid "Reset %s branch to here"
-msgstr "УÑ\81Ñ\82ановиÑ\82Ñ\8c Ð²ÐµÑ\82вÑ\8c %s Ð½Ð° Ñ\8dÑ\82о Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "СбÑ\80оÑ\81иÑ\82Ñ\8c Ð²ÐµÑ\82кÑ\83 %s Ð½Ð° Ñ\8dÑ\82оÑ\82 ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82"
 
 #: gitk:8900
 msgid "Detached head: can't reset"
-msgstr "СоÑ\81Ñ\82оÑ\8fние Ð½Ðµ Ð¿Ñ\80инадлежиÑ\82 Ð½Ð¸ Ð¾Ð´Ð½Ð¾Ð¹ Ð²ÐµÑ\82ви, Ð¿ÐµÑ\80еÑ\85од Ð½ÐµÐ²Ð¾Ð·Ð¼Ð¾Ð¶ÐµÐ½"
+msgstr "Ð\9aоммиÑ\82 Ð½Ðµ Ð¿Ñ\80инадлежиÑ\82 Ð½Ð¸ Ð¾Ð´Ð½Ð¾Ð¹ Ð²ÐµÑ\82ке, Ñ\81бÑ\80оÑ\81иÑ\82Ñ\8c Ð½ÐµÐ²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾"
 
 #: gitk:9005 gitk:9011
 msgid "Skipping merge commit "
-msgstr ""
+msgstr "Пропускаю коммит-слияние"
 
 #: gitk:9020 gitk:9025
-#, fuzzy
 msgid "Error getting patch ID for "
-msgstr "Ð\9eÑ\88ибка Ñ\81озданиÑ\8f Ð¿Ð°Ñ\82Ñ\87а:"
+msgstr "Ð\9dе Ñ\83далоÑ\81Ñ\8c Ð¿Ð¾Ð»Ñ\83Ñ\87иÑ\82Ñ\8c Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\82оÑ\80 Ð¿Ð°Ñ\82Ñ\87а Ð´Ð»Ñ\8f "
 
 #: gitk:9021 gitk:9026
 msgid " - stopping\n"
-msgstr ""
+msgstr " — останов\n"
 
 #: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065
-#, fuzzy
 msgid "Commit "
-msgstr "состояние"
+msgstr "Коммит"
 
 #: gitk:9035
 msgid ""
 " is the same patch as\n"
 "       "
-msgstr ""
+msgstr " такой же патч, как и\n       "
 
 #: gitk:9043
 msgid ""
 " differs from\n"
 "       "
-msgstr ""
+msgstr " отличается от\n       "
 
 #: gitk:9045
 msgid ""
 "Diff of commits:\n"
 "\n"
-msgstr ""
+msgstr "Различия коммитов:\n\n"
 
 #: gitk:9057 gitk:9066
 #, tcl-format
 msgid " has %s children - stopping\n"
-msgstr ""
+msgstr " является %s потомком — останов\n"
 
 #: gitk:9085
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Error writing commit to file: %s"
-msgstr "Ð\9eÑ\88ибка Ñ\81оÑ\85Ñ\80анениÑ\8f Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8f:"
+msgstr "Ð\9fÑ\80оизоÑ\88ла Ð¾Ñ\88ибка Ð¿Ñ\80и Ð·Ð°Ð¿Ð¸Ñ\81и ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а Ð² Ñ\84айл: %s"
 
 #: gitk:9091
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Error diffing commits: %s"
-msgstr "Ð\9eÑ\88ибка Ñ\81оÑ\85Ñ\80анениÑ\8f Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8f:"
+msgstr "Ð\9fÑ\80оизоÑ\88ла Ð¾Ñ\88ибка Ð¿Ñ\80и Ð²Ñ\8bводе Ñ\80азлиÑ\87ий ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82ов: %s"
 
 #: gitk:9137
 msgid "Top"
@@ -983,12 +958,11 @@ msgstr "Имя метки:"
 
 #: gitk:9268
 msgid "Tag message is optional"
-msgstr ""
+msgstr "Описание метки указывать не обязательно"
 
 #: gitk:9270
-#, fuzzy
 msgid "Tag message:"
-msgstr "Ð\98мÑ\8f метки:"
+msgstr "Ð\9eпиÑ\81ание метки:"
 
 #: gitk:9274 gitk:9439
 msgid "Create"
@@ -1001,7 +975,7 @@ msgstr "Не задано имя метки"
 #: gitk:9296
 #, tcl-format
 msgid "Tag \"%s\" already exists"
-msgstr "Метка \"%s\" уже существует"
+msgstr "Метка «%s» уже существует"
 
 #: gitk:9306
 msgid "Error creating tag:"
@@ -1017,7 +991,7 @@ msgstr "Запись"
 
 #: gitk:9408
 msgid "Error writing commit:"
-msgstr "Ð\9eÑ\88ибка Ñ\81оÑ\85Ñ\80анениÑ\8f Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8f:"
+msgstr "Ð\9fÑ\80оизоÑ\88ла Ð¾Ñ\88ибка Ð¿Ñ\80и Ð·Ð°Ð¿Ð¸Ñ\81и ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а:"
 
 #: gitk:9435
 msgid "Name:"
@@ -1025,17 +999,17 @@ msgstr "Имя:"
 
 #: gitk:9458
 msgid "Please specify a name for the new branch"
-msgstr "УкажиÑ\82е Ð¸Ð¼Ñ\8f Ð´Ð»Ñ\8f Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ\82ви"
+msgstr "УкажиÑ\82е Ð¸Ð¼Ñ\8f Ð´Ð»Ñ\8f Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ\82ки"
 
 #: gitk:9463
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
-msgstr "Ð\92еÑ\82вÑ\8c '%s' уже существует. Переписать?"
+msgstr "Ð\92еÑ\82ка Â«%s» уже существует. Переписать?"
 
 #: gitk:9530
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
-msgstr "СоÑ\81Ñ\82оÑ\8fние %s Ñ\83же Ð¿Ñ\80инадлежиÑ\82 Ð²ÐµÑ\82ви %s. Продолжить операцию?"
+msgstr "Ð\9aоммиÑ\82 %s Ñ\83же Ð²ÐºÐ»Ñ\8eÑ\87Ñ\91н Ð² Ð²ÐµÑ\82кÑ\83 %s. Продолжить операцию?"
 
 #: gitk:9535
 msgid "Cherry-picking"
@@ -1046,49 +1020,39 @@ msgstr "Копирование изменений"
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
 "Please commit, reset or stash your changes and try again."
-msgstr ""
-"Копирование невозможно из-за изменений в файле '%s'.\n"
-"Сохраните или отмените изменения и повторите операцию."
+msgstr "Отбор лучшего невозможен из-за изменений в файле «%s».\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию."
 
 #: gitk:9550
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
-msgstr ""
-"Копирование изменений невозможно из-за незавершённой операции слияния.\n"
-"Запустить git citool для завершения этой операции?"
+msgstr "Копирование изменений невозможно из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?"
 
 #: gitk:9566 gitk:9624
 msgid "No changes committed"
-msgstr "Изменения не сохранены"
+msgstr "Изменения не закоммичены"
 
 #: gitk:9593
-#, fuzzy, tcl-format
+#, tcl-format
 msgid "Commit %s is not included in branch %s -- really revert it?"
-msgstr "СоÑ\81Ñ\82оÑ\8fние %s Ñ\83же Ð¿Ñ\80инадлежиÑ\82 Ð²ÐµÑ\82ви %s. Продолжить операцию?"
+msgstr "Ð\9aоммиÑ\82 %s Ð½Ðµ Ð²ÐºÐ»Ñ\8eÑ\87Ñ\91н Ð² Ð²ÐµÑ\82кÑ\83 %s. Продолжить операцию?"
 
 #: gitk:9598
-#, fuzzy
 msgid "Reverting"
-msgstr "УÑ\81Ñ\82ановка"
+msgstr "Ð\92озвÑ\80аÑ\82 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹"
 
 #: gitk:9606
-#, fuzzy, tcl-format
+#, tcl-format
 msgid ""
 "Revert failed because of local changes to the following files:%s Please "
 "commit, reset or stash  your changes and try again."
-msgstr ""
-"Копирование невозможно из-за изменений в файле '%s'.\n"
-"Сохраните или отмените изменения и повторите операцию."
+msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию."
 
 #: gitk:9610
-#, fuzzy
 msgid ""
 "Revert failed because of merge conflict.\n"
 " Do you wish to run git citool to resolve it?"
-msgstr ""
-"Копирование изменений невозможно из-за незавершённой операции слияния.\n"
-"Запустить git citool для завершения этой операции?"
+msgstr "Возврат изменений невозможен из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?"
 
 #: gitk:9653
 msgid "Confirm reset"
@@ -1097,7 +1061,7 @@ msgstr "Подтвердите операцию перехода"
 #: gitk:9655
 #, tcl-format
 msgid "Reset branch %s to %s?"
-msgstr "УÑ\81Ñ\82ановиÑ\82Ñ\8c Ð²ÐµÑ\82вÑ\8c %s Ð½Ð° Ñ\81оÑ\81Ñ\82оÑ\8fние %s?"
+msgstr "СбÑ\80оÑ\81иÑ\82Ñ\8c Ð²ÐµÑ\82кÑ\83 %s Ð½Ð° ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82 %s?"
 
 #: gitk:9657
 msgid "Reset type:"
@@ -1115,13 +1079,11 @@ msgstr "Смешанный: оставить рабочий каталог не
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
-msgstr ""
-"Жесткий: переписать индекс и рабочий каталог\n"
-"(все изменения в рабочем каталоге будут потеряны)"
+msgstr "Жесткий: переписать индекс и рабочий каталог\n(все изменения в рабочем каталоге будут потеряны)"
 
 #: gitk:9683
 msgid "Resetting"
-msgstr "УÑ\81Ñ\82ановка"
+msgstr "СбÑ\80оÑ\81"
 
 #: gitk:9743
 msgid "Checking out"
@@ -1129,21 +1091,19 @@ msgstr "Переход"
 
 #: gitk:9796
 msgid "Cannot delete the currently checked-out branch"
-msgstr "Ð\90кÑ\82ивнаÑ\8f Ð²ÐµÑ\82вÑ\8c не может быть удалена"
+msgstr "Ð\90кÑ\82ивнаÑ\8f Ð²ÐµÑ\82ка не может быть удалена"
 
 #: gitk:9802
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
 "Really delete branch %s?"
-msgstr ""
-"Состояния ветви %s больше не принадлежат никакой другой ветви.\n"
-"Действительно удалить ветвь %s?"
+msgstr "Коммиты из ветки %s не принадлежат больше никакой другой ветке.\nДействительно удалить ветку %s?"
 
 #: gitk:9833
 #, tcl-format
 msgid "Tags and heads: %s"
-msgstr "Ð\9cеÑ\82ки Ð¸ Ð²ÐµÑ\82ви: %s"
+msgstr "Ð\9cеÑ\82ки Ð¸ Ð²ÐµÑ\82ки: %s"
 
 #: gitk:9850
 msgid "Filter"
@@ -1153,9 +1113,7 @@ msgstr "Фильтровать"
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
-msgstr ""
-"Ошибка чтения истории проекта; информация о ветвях и состояниях вокруг меток "
-"(до/после) может быть неполной."
+msgstr "Ошибка чтения истории проекта; информация о ветках и коммитах вокруг меток (до/после) может быть неполной."
 
 #: gitk:11123
 msgid "Tag"
@@ -1179,7 +1137,7 @@ msgstr "К"
 
 #: gitk:11348
 msgid "Commit list display options"
-msgstr "Параметры показа списка состояний"
+msgstr "Параметры показа списка коммитов"
 
 #: gitk:11351
 msgid "Maximum graph width (lines)"
@@ -1195,13 +1153,12 @@ msgid "Show local changes"
 msgstr "Показывать изменения в рабочем каталоге"
 
 #: gitk:11361
-#, fuzzy
 msgid "Auto-select SHA1 (length)"
-msgstr "Ð\92Ñ\8bделиÑ\82Ñ\8c SHA1"
+msgstr "Ð\90вÑ\82омаÑ\82иÑ\87еÑ\81ки Ð²Ñ\8bделиÑ\82Ñ\8c SHA1 (длинна)"
 
 #: gitk:11365
 msgid "Hide remote refs"
-msgstr ""
+msgstr "Скрыть внешние ссылки"
 
 #: gitk:11369
 msgid "Diff display options"
@@ -1212,13 +1169,12 @@ msgid "Tab spacing"
 msgstr "Ширина табуляции"
 
 #: gitk:11374
-#, fuzzy
 msgid "Display nearby tags/heads"
-msgstr "Показывать близкие метки"
+msgstr "Показывать близкие метки/ветки"
 
 #: gitk:11377
 msgid "Maximum # tags/heads to show"
-msgstr ""
+msgstr "Показывать максимальное количество меток/веток"
 
 #: gitk:11380
 msgid "Limit diffs to listed paths"
@@ -1237,21 +1193,20 @@ msgid "Choose..."
 msgstr "Выберите..."
 
 #: gitk:11395
-#, fuzzy
 msgid "General options"
-msgstr "СоздаÑ\82Ñ\8c Ð¿Ð°Ñ\82Ñ\87"
+msgstr "Ð\9eбÑ\89ие Ð¾Ð¿Ñ\86ии"
 
 #: gitk:11398
 msgid "Use themed widgets"
-msgstr ""
+msgstr "Использовать стили виджетов"
 
 #: gitk:11400
 msgid "(change requires restart)"
-msgstr ""
+msgstr "(изменение потребует перезапуск)"
 
 #: gitk:11402
 msgid "(currently unavailable)"
-msgstr ""
+msgstr "(недоступно в данный момент)"
 
 #: gitk:11413
 msgid "Colors: press to choose"
@@ -1259,12 +1214,11 @@ msgstr "Цвета: нажмите для выбора"
 
 #: gitk:11416
 msgid "Interface"
-msgstr ""
+msgstr "Интерфейс"
 
 #: gitk:11417
-#, fuzzy
 msgid "interface"
-msgstr "ШÑ\80иÑ\84Ñ\82 Ð¸Ð½Ñ\82еÑ\80Ñ\84ейÑ\81а"
+msgstr "инÑ\82еÑ\80Ñ\84ейÑ\81"
 
 #: gitk:11420
 msgid "Background"
@@ -1339,17 +1293,16 @@ msgid "Gitk preferences"
 msgstr "Настройки Gitk"
 
 #: gitk:11494
-#, fuzzy
 msgid "General"
-msgstr "СоздаÑ\82Ñ\8c"
+msgstr "Ð\9eбÑ\89ие"
 
 #: gitk:11495
 msgid "Colors"
-msgstr ""
+msgstr "Цвета"
 
 #: gitk:11496
 msgid "Fonts"
-msgstr ""
+msgstr "Шрифты"
 
 #: gitk:11546
 #, tcl-format
@@ -1360,9 +1313,7 @@ msgstr "Gitk: выберите цвет для %s"
 msgid ""
 "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
 " Gitk requires at least Tcl/Tk 8.4."
-msgstr ""
-"К сожалению gitk не может работать с этой версий Tcl/Tk.\n"
-"Требуется как минимум Tcl/Tk 8.4."
+msgstr "К сожалению gitk не может работать с этой версий Tcl/Tk.\nТребуется как минимум Tcl/Tk 8.4."
 
 #: gitk:12269
 msgid "Cannot find a git repository here."
@@ -1371,38 +1322,8 @@ msgstr "Git-репозитарий не найден в текущем ката
 #: gitk:12316
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
-msgstr "Неоднозначный аргумент '%s': существует как версия и имя файла"
+msgstr "Неоднозначный аргумент «%s»: существует как редакция и как имя файла"
 
 #: gitk:12328
 msgid "Bad arguments to gitk:"
 msgstr "Неправильные аргументы для gitk:"
-
-#~ msgid "SHA1 ID: "
-#~ msgstr "SHA1:"
-
-#~ msgid "next"
-#~ msgstr "След."
-
-#~ msgid "prev"
-#~ msgstr "Пред."
-
-#~ msgid "Use all refs"
-#~ msgstr "Использовать все ветви"
-
-#~ msgid "Max count:"
-#~ msgstr "Макс. количество:"
-
-#~ msgid "Skip:"
-#~ msgstr "Пропустить:"
-
-#~ msgid "Name"
-#~ msgstr "Имя"
-
-#~ msgid "CDate"
-#~ msgstr "Дата ввода"
-
-#~ msgid "Tag/Head %s is not known"
-#~ msgstr "Метка или ветвь %s не найдена"
-
-#~ msgid "Cannot find the git directory \"%s\"."
-#~ msgstr "Git-репозитарий \"%s\" не найден."
index 0bd46d7881017506e1d5e8afc4a4225b0d840e2e..75317f43965ba4a6f9b173f476e13851ca069e52 100644 (file)
@@ -92,71 +92,71 @@ msgid "Cancel"
 msgstr "Avbryt"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Uppdatera"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Ladda om"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Läs om referenser"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Visa referenser"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Starta git gui"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Avsluta"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Arkiv"
 
 #: gitk:2080
-msgid "Preferences"
+msgid "&Preferences"
 msgstr "Inställningar"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Redigera"
 
 #: gitk:2084
-msgid "New view..."
+msgid "&New view..."
 msgstr "Ny vy..."
 
 #: gitk:2085
-msgid "Edit view..."
+msgid "&Edit view..."
 msgstr "Ändra vy..."
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Ta bort vy"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Alla filer"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Visa"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Om gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Tangentbordsbindningar"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Hjälp"
 
 #: gitk:2185 gitk:8652
index 7133c422b7363b08fbb49fd6526c8db9af81b0eb..8966812368a626e8da9f42012b69f233b4181642 100644 (file)
@@ -1,14 +1,14 @@
 # Vietnamese translations for gitk package.
 # Bản dịch tiếng Việt cho gói gitk.
 # This file is distributed under the same license as the gitk package.
-# Trần Ngọc Quân <vnwildman@gmail.com>, 2013.
+# Trần Ngọc Quân <vnwildman@gmail.com>, 2013, 2015.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: gitk @@GIT_VERSION@@\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2015-05-17 14:32+1000\n"
-"PO-Revision-Date: 2013-12-14 14:40+0700\n"
+"PO-Revision-Date: 2015-09-15 07:33+0700\n"
 "Last-Translator: Trần Ngọc Quân <vnwildman@gmail.com>\n"
 "Language-Team: Vietnamese <translation-team-vi@lists.sourceforge.net>\n"
 "Language: vi\n"
@@ -16,6 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Gtranslator 2.91.7\n"
 
 #: gitk:140
 msgid "Couldn't get list of unmerged files:"
@@ -60,7 +61,7 @@ msgstr "Đang đọc"
 
 #: gitk:496 gitk:4525
 msgid "Reading commits..."
-msgstr "Đang đọc các lần chuyển giao..."
+msgstr "Đang đọc các lần chuyển giao"
 
 #: gitk:499 gitk:1637 gitk:4528
 msgid "No commits selected"
@@ -88,71 +89,71 @@ msgid "Cancel"
 msgstr "Thôi"
 
 #: gitk:2069
-msgid "Update"
+msgid "&Update"
 msgstr "Cập nhật"
 
 #: gitk:2070
-msgid "Reload"
+msgid "&Reload"
 msgstr "Tải lại"
 
 #: gitk:2071
-msgid "Reread references"
+msgid "Reread re&ferences"
 msgstr "Đọc lại tham chiếu"
 
 #: gitk:2072
-msgid "List references"
+msgid "&List references"
 msgstr "Liệt kê các tham chiếu"
 
 #: gitk:2074
-msgid "Start git gui"
+msgid "Start git &gui"
 msgstr "Khởi chạy git gui"
 
 #: gitk:2076
-msgid "Quit"
+msgid "&Quit"
 msgstr "Thoát"
 
 #: gitk:2068
-msgid "File"
+msgid "&File"
 msgstr "Chính"
 
 #: gitk:2080
-msgid "Preferences"
-msgstr "Cá nhân hóa"
+msgid "&Preferences"
+msgstr "Tùy thích"
 
 #: gitk:2079
-msgid "Edit"
+msgid "&Edit"
 msgstr "Chỉnh sửa"
 
 #: gitk:2084
-msgid "New view..."
-msgstr "Thêm trình bày mới..."
+msgid "&New view..."
+msgstr "Thêm trình bày mới"
 
 #: gitk:2085
-msgid "Edit view..."
-msgstr "Sửa cách trình bày..."
+msgid "&Edit view..."
+msgstr "Sửa cách trình bày"
 
 #: gitk:2086
-msgid "Delete view"
+msgid "&Delete view"
 msgstr "Xóa cách trình bày"
 
 #: gitk:2088 gitk:4043
-msgid "All files"
+msgid "&All files"
 msgstr "Mọi tập tin"
 
 #: gitk:2083 gitk:4067
-msgid "View"
+msgid "&View"
 msgstr "Trình bày"
 
 #: gitk:2093 gitk:2103 gitk:3012
-msgid "About gitk"
+msgid "&About gitk"
 msgstr "Giới thiệu về gitk"
 
 #: gitk:2094 gitk:2108
-msgid "Key bindings"
+msgid "&Key bindings"
 msgstr "Tổ hợp phím"
 
 #: gitk:2092 gitk:2107
-msgid "Help"
+msgid "&Help"
 msgstr "Trợ giúp"
 
 #: gitk:2185 gitk:8652
@@ -319,7 +320,7 @@ msgstr "Hoàn lại lần chuyển giao này"
 
 #: gitk:2647
 msgid "Check out this branch"
-msgstr "Checkout nhánh này"
+msgstr "Lấy ra nhánh này"
 
 #: gitk:2648
 msgid "Remove this branch"
@@ -327,7 +328,7 @@ msgstr "Gỡ bỏ nhánh này"
 
 #: gitk:2649
 msgid "Copy branch name"
-msgstr ""
+msgstr "Chép tên nhánh"
 
 #: gitk:2656
 msgid "Highlight this too"
@@ -347,7 +348,7 @@ msgstr "Xem công trạng lần chuyển giao cha mẹ"
 
 #: gitk:2660
 msgid "Copy path"
-msgstr ""
+msgstr "Chép đường dẫn"
 
 #: gitk:2667
 msgid "Show origin of this line"
@@ -358,7 +359,6 @@ msgid "Run git gui blame on this line"
 msgstr "Chạy lệnh git gui blame cho dòng này"
 
 #: gitk:3014
-#, fuzzy
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
@@ -368,9 +368,9 @@ msgid ""
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
-"Gitk - phần mềm xem các lần chuyển giao dành cho git\n"
+"Gitk - ứng dụng để xem các lần chuyển giao dành cho git\n"
 "\n"
-"Bản quyền © 2005-2011 Paul Mackerras\n"
+"Bản quyền © 2005-2014 Paul Mackerras\n"
 "\n"
 "Dùng và phân phối lại phần mềm này theo các điều khoản của Giấy Phép Công GNU"
 
@@ -424,6 +424,7 @@ msgstr "<Right>, x, l\tDi chuyển tiếp trong danh sách lịch sử"
 #, tcl-format
 msgid "<%s-n>\tGo to n-th parent of current commit in history list"
 msgstr ""
+"<%s-n>\tĐến cha thứ n của lần chuyển giao hiện tại trong danh sách lịch sử"
 
 #: gitk:3057
 msgid "<PageUp>\tMove up one page in commit list"
@@ -507,9 +508,8 @@ msgid "<Return>\tMove to next find hit"
 msgstr "<Return>\t\tDi chuyển đến chỗ gặp kế tiếp"
 
 #: gitk:3075
-#, fuzzy
 msgid "g\t\tGo to commit"
-msgstr "<End>\t\tChuyển đến lần chuyển giao cuối"
+msgstr "g\t\tChuyển đến lần chuyển giao"
 
 #: gitk:3076
 msgid "/\t\tFocus the search box"
@@ -666,9 +666,8 @@ msgid "Matches all Commit Info criteria"
 msgstr "Khớp mọi điều kiện Thông tin Chuyển giao"
 
 #: gitk:4086
-#, fuzzy
 msgid "Matches no Commit Info criteria"
-msgstr "Khớp mọi điều kiện Thông tin Chuyển giao"
+msgstr "Khớp không điều kiện Thông tin Chuyển giao"
 
 #: gitk:4087
 msgid "Changes to Files:"
@@ -716,7 +715,7 @@ msgstr "Số lượng sẽ bỏ qua:"
 
 #: gitk:4097
 msgid "Miscellaneous options:"
-msgstr "Tuỳ chọn hỗn hợp:"
+msgstr "Tùy chọn hỗn hợp:"
 
 #: gitk:4098
 msgid "Strictly sort by date"
@@ -971,7 +970,7 @@ msgstr "Gặp lỗi khi tạo miếng vá:"
 
 #: gitk:9256 gitk:9373 gitk:9430
 msgid "ID:"
-msgstr "ID:"
+msgstr "Mã số:"
 
 #: gitk:9265
 msgid "Tag name:"
@@ -1186,7 +1185,7 @@ msgstr "Độ rộng biểu đồ tối đa (dòng)"
 #: gitk:11355
 #, no-tcl-format
 msgid "Maximum graph width (% of pane)"
-msgstr "Độ rộng biểu đồ tối đa (% của bảng)"
+msgstr "Độ rộng đồ thị tối đa (% của bảng)"
 
 #: gitk:11358
 msgid "Show local changes"
@@ -1194,7 +1193,7 @@ msgstr "Hiển thị các thay đổi nội bộ"
 
 #: gitk:11361
 msgid "Auto-select SHA1 (length)"
-msgstr "Tự chọn SHA1 (độ dài)"
+msgstr "Tự chọn (độ dài) SHA1"
 
 #: gitk:11365
 msgid "Hide remote refs"
@@ -1230,7 +1229,7 @@ msgstr "Công cụ so sánh từ bên ngoài"
 
 #: gitk:11390
 msgid "Choose..."
-msgstr "Chọn..."
+msgstr "Chọn"
 
 #: gitk:11395
 msgid "General options"
@@ -1354,6 +1353,8 @@ msgid ""
 "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
 " Gitk requires at least Tcl/Tk 8.4."
 msgstr ""
+"Rất tiếc, gitk không thể chạy Tcl/Tk phiên bản này.\n"
+" Gitk cần ít nhất là Tcl/Tk 8.4."
 
 #: gitk:12269
 msgid "Cannot find a git repository here."
@@ -1366,7 +1367,7 @@ msgstr "Đối số “%s” chưa rõ ràng: vừa là điểm xét duyệt v
 
 #: gitk:12328
 msgid "Bad arguments to gitk:"
-msgstr "Đối số không hợp lệ cho gitk:"
+msgstr "Đối số cho gitk không hợp lệ:"
 
 #~ msgid "mc"
 #~ msgstr "mc"
diff --git a/grep.c b/grep.c
index b58c7c64342698737f9c11b20457bd30ca1fc727..7b2b96a4376efe51e41f9311c6fa5250a2dcd13d 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -31,14 +31,14 @@ void init_grep_defaults(void)
        opt->max_depth = -1;
        opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
        opt->extended_regexp_option = 0;
-       strcpy(opt->color_context, "");
-       strcpy(opt->color_filename, "");
-       strcpy(opt->color_function, "");
-       strcpy(opt->color_lineno, "");
-       strcpy(opt->color_match_context, GIT_COLOR_BOLD_RED);
-       strcpy(opt->color_match_selected, GIT_COLOR_BOLD_RED);
-       strcpy(opt->color_selected, "");
-       strcpy(opt->color_sep, GIT_COLOR_CYAN);
+       color_set(opt->color_context, "");
+       color_set(opt->color_filename, "");
+       color_set(opt->color_function, "");
+       color_set(opt->color_lineno, "");
+       color_set(opt->color_match_context, GIT_COLOR_BOLD_RED);
+       color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);
+       color_set(opt->color_selected, "");
+       color_set(opt->color_sep, GIT_COLOR_CYAN);
        opt->color = -1;
 }
 
@@ -151,14 +151,14 @@ void grep_init(struct grep_opt *opt, const char *prefix)
        opt->regflags = def->regflags;
        opt->relative = def->relative;
 
-       strcpy(opt->color_context, def->color_context);
-       strcpy(opt->color_filename, def->color_filename);
-       strcpy(opt->color_function, def->color_function);
-       strcpy(opt->color_lineno, def->color_lineno);
-       strcpy(opt->color_match_context, def->color_match_context);
-       strcpy(opt->color_match_selected, def->color_match_selected);
-       strcpy(opt->color_selected, def->color_selected);
-       strcpy(opt->color_sep, def->color_sep);
+       color_set(opt->color_context, def->color_context);
+       color_set(opt->color_filename, def->color_filename);
+       color_set(opt->color_function, def->color_function);
+       color_set(opt->color_lineno, def->color_lineno);
+       color_set(opt->color_match_context, def->color_match_context);
+       color_set(opt->color_match_selected, def->color_match_selected);
+       color_set(opt->color_selected, def->color_selected);
+       color_set(opt->color_sep, def->color_sep);
 }
 
 void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
@@ -306,9 +306,9 @@ static NORETURN void compile_regexp_failed(const struct grep_pat *p,
        char where[1024];
 
        if (p->no)
-               sprintf(where, "In '%s' at %d, ", p->origin, p->no);
+               xsnprintf(where, sizeof(where), "In '%s' at %d, ", p->origin, p->no);
        else if (p->origin)
-               sprintf(where, "%s, ", p->origin);
+               xsnprintf(where, sizeof(where), "%s, ", p->origin);
        else
                where[0] = 0;
 
diff --git a/hex.c b/hex.c
index 899b74a08cb5298d2b0dd3205a98f5912a700e66..0519f853b26e527aa529c75da6c302663881102e 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -61,12 +61,10 @@ int get_oid_hex(const char *hex, struct object_id *oid)
        return get_sha1_hex(hex, oid->hash);
 }
 
-char *sha1_to_hex(const unsigned char *sha1)
+char *sha1_to_hex_r(char *buffer, const unsigned char *sha1)
 {
-       static int bufno;
-       static char hexbuffer[4][GIT_SHA1_HEXSZ + 1];
        static const char hex[] = "0123456789abcdef";
-       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
+       char *buf = buffer;
        int i;
 
        for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
@@ -79,6 +77,13 @@ char *sha1_to_hex(const unsigned char *sha1)
        return buffer;
 }
 
+char *sha1_to_hex(const unsigned char *sha1)
+{
+       static int bufno;
+       static char hexbuffer[4][GIT_SHA1_HEXSZ + 1];
+       return sha1_to_hex_r(hexbuffer[3 & ++bufno], sha1);
+}
+
 char *oid_to_hex(const struct object_id *oid)
 {
        return sha1_to_hex(oid->hash);
index c98dad23dfd86875209295fadda39c1f1b4f5224..48f39b7f71c632aae6a42d65be929a9e44874d84 100644 (file)
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "list-objects.h"
 #include "sigchain.h"
+#include "argv-array.h"
 
 #ifdef EXPAT_NEEDS_XMLPARSE_H
 #include <xmlparse.h>
@@ -361,7 +362,7 @@ static void start_put(struct transfer_request *request)
        git_zstream stream;
 
        unpacked = read_sha1_file(request->obj->sha1, &type, &len);
-       hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
+       hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1;
 
        /* Set it up */
        git_deflate_init(&stream, zlib_compression_level);
@@ -786,21 +787,21 @@ xml_start_tag(void *userData, const char *name, const char **atts)
 {
        struct xml_ctx *ctx = (struct xml_ctx *)userData;
        const char *c = strchr(name, ':');
-       int new_len;
+       int old_namelen, new_len;
 
        if (c == NULL)
                c = name;
        else
                c++;
 
-       new_len = strlen(ctx->name) + strlen(c) + 2;
+       old_namelen = strlen(ctx->name);
+       new_len = old_namelen + strlen(c) + 2;
 
        if (new_len > ctx->len) {
                ctx->name = xrealloc(ctx->name, new_len);
                ctx->len = new_len;
        }
-       strcat(ctx->name, ".");
-       strcat(ctx->name, c);
+       xsnprintf(ctx->name + old_namelen, ctx->len - old_namelen, ".%s", c);
 
        free(ctx->cdata);
        ctx->cdata = NULL;
@@ -881,7 +882,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped);
        free(escaped);
 
-       sprintf(timeout_header, "Timeout: Second-%ld", timeout);
+       xsnprintf(timeout_header, sizeof(timeout_header), "Timeout: Second-%ld", timeout);
        dav_headers = curl_slist_append(dav_headers, timeout_header);
        dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
 
@@ -1459,8 +1460,6 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
 {
        struct strbuf *buf = (struct strbuf *)ls->userData;
        struct object *o;
-       int len;
-       char *ref_info;
        struct ref *ref;
 
        ref = alloc_ref(ls->dentry_name);
@@ -1484,23 +1483,14 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
                return;
        }
 
-       len = strlen(ls->dentry_name) + 42;
-       ref_info = xcalloc(len + 1, 1);
-       sprintf(ref_info, "%s   %s\n",
-               sha1_to_hex(ref->old_sha1), ls->dentry_name);
-       fwrite_buffer(ref_info, 1, len, buf);
-       free(ref_info);
+       strbuf_addf(buf, "%s\t%s\n",
+                   sha1_to_hex(ref->old_sha1), ls->dentry_name);
 
        if (o->type == OBJ_TAG) {
                o = deref_tag(o, ls->dentry_name, 0);
-               if (o) {
-                       len = strlen(ls->dentry_name) + 45;
-                       ref_info = xcalloc(len + 1, 1);
-                       sprintf(ref_info, "%s   %s^{}\n",
-                               sha1_to_hex(o->sha1), ls->dentry_name);
-                       fwrite_buffer(ref_info, 1, len, buf);
-                       free(ref_info);
-               }
+               if (o)
+                       strbuf_addf(buf, "%s\t%s^{}\n",
+                                   sha1_to_hex(o->sha1), ls->dentry_name);
        }
        free(ref);
 }
@@ -1866,10 +1856,7 @@ int main(int argc, char **argv)
 
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
-               char old_hex[60], *new_hex;
-               const char *commit_argv[5];
-               int commit_argc;
-               char *new_sha1_hex, *old_sha1_hex;
+               struct argv_array commit_argv = ARGV_ARRAY_INIT;
 
                if (!ref->peer_ref)
                        continue;
@@ -1923,13 +1910,12 @@ int main(int argc, char **argv)
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
                new_refs++;
-               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
-               new_hex = sha1_to_hex(ref->new_sha1);
 
                fprintf(stderr, "updating '%s'", ref->name);
                if (strcmp(ref->name, ref->peer_ref->name))
                        fprintf(stderr, " using '%s'", ref->peer_ref->name);
-               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+               fprintf(stderr, "\n  from %s\n  to   %s\n",
+                       sha1_to_hex(ref->old_sha1), sha1_to_hex(ref->new_sha1));
                if (dry_run) {
                        if (helper_status)
                                printf("ok %s\n", ref->name);
@@ -1948,27 +1934,15 @@ int main(int argc, char **argv)
                }
 
                /* Set up revision info for this refspec */
-               commit_argc = 3;
-               new_sha1_hex = xstrdup(sha1_to_hex(ref->new_sha1));
-               old_sha1_hex = NULL;
-               commit_argv[1] = "--objects";
-               commit_argv[2] = new_sha1_hex;
-               if (!push_all && !is_null_sha1(ref->old_sha1)) {
-                       old_sha1_hex = xmalloc(42);
-                       sprintf(old_sha1_hex, "^%s",
-                               sha1_to_hex(ref->old_sha1));
-                       commit_argv[3] = old_sha1_hex;
-                       commit_argc++;
-               }
-               commit_argv[commit_argc] = NULL;
+               argv_array_push(&commit_argv, ""); /* ignored */
+               argv_array_push(&commit_argv, "--objects");
+               argv_array_push(&commit_argv, sha1_to_hex(ref->new_sha1));
+               if (!push_all && !is_null_sha1(ref->old_sha1))
+                       argv_array_pushf(&commit_argv, "^%s",
+                                        sha1_to_hex(ref->old_sha1));
                init_revisions(&revs, setup_git_directory());
-               setup_revisions(commit_argc, commit_argv, &revs, NULL);
+               setup_revisions(commit_argv.argc, commit_argv.argv, &revs, NULL);
                revs.edge_hint = 0; /* just in case */
-               free(new_sha1_hex);
-               if (old_sha1_hex) {
-                       free(old_sha1_hex);
-                       commit_argv[1] = NULL;
-               }
 
                /* Generate a list of objects that need to be pushed */
                pushing = 0;
@@ -1997,6 +1971,7 @@ int main(int argc, char **argv)
                        printf("%s %s\n", !rc ? "ok" : "error", ref->name);
                unlock_remote(ref_lock);
                check_locks();
+               argv_array_clear(&commit_argv);
        }
 
        /* Update remote server info if appropriate */
index 88da5468e77f5a543edb7583e2846f19eb509653..2c721f0c30d786e043dec819dc421788311c7e4b 100644 (file)
@@ -29,7 +29,7 @@ struct object_request {
 struct alternates_request {
        struct walker *walker;
        const char *base;
-       char *url;
+       struct strbuf *url;
        struct strbuf *buffer;
        struct active_request_slot *slot;
        int http_specific;
@@ -195,10 +195,11 @@ static void process_alternates_response(void *callback_data)
 
                        /* Try reusing the slot to get non-http alternates */
                        alt_req->http_specific = 0;
-                       sprintf(alt_req->url, "%s/objects/info/alternates",
-                               base);
+                       strbuf_reset(alt_req->url);
+                       strbuf_addf(alt_req->url, "%s/objects/info/alternates",
+                                   base);
                        curl_easy_setopt(slot->curl, CURLOPT_URL,
-                                        alt_req->url);
+                                        alt_req->url->buf);
                        active_requests++;
                        slot->in_use = 1;
                        if (slot->finished != NULL)
@@ -312,7 +313,7 @@ static void process_alternates_response(void *callback_data)
 static void fetch_alternates(struct walker *walker, const char *base)
 {
        struct strbuf buffer = STRBUF_INIT;
-       char *url;
+       struct strbuf url = STRBUF_INIT;
        struct active_request_slot *slot;
        struct alternates_request alt_req;
        struct walker_data *cdata = walker->data;
@@ -338,7 +339,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
        if (walker->get_verbosely)
                fprintf(stderr, "Getting alternates list for %s\n", base);
 
-       url = xstrfmt("%s/objects/info/http-alternates", base);
+       strbuf_addf(&url, "%s/objects/info/http-alternates", base);
 
        /*
         * Use a callback to process the result, since another request
@@ -351,10 +352,10 @@ static void fetch_alternates(struct walker *walker, const char *base)
 
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf);
 
        alt_req.base = base;
-       alt_req.url = url;
+       alt_req.url = &url;
        alt_req.buffer = &buffer;
        alt_req.http_specific = 1;
        alt_req.slot = slot;
@@ -365,7 +366,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
                cdata->got_alternates = -1;
 
        strbuf_release(&buffer);
-       free(url);
+       strbuf_release(&url);
 }
 
 static int fetch_indices(struct walker *walker, struct alt_base *repo)
diff --git a/http.c b/http.c
index 0f924a8b48f3e30fd4a646ef48acb8d066988a53..7da76edda16abea8a53299f2770ad2dce55c1b60 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1122,7 +1122,7 @@ static void write_accept_language(struct strbuf *buf)
                     decimal_places++, max_q *= 10)
                        ;
 
-               sprintf(q_format, ";q=0.%%0%dd", decimal_places);
+               xsnprintf(q_format, sizeof(q_format), ";q=0.%%0%dd", decimal_places);
 
                strbuf_addstr(buf, "Accept-Language: ");
 
@@ -1529,6 +1529,7 @@ int finish_http_pack_request(struct http_pack_request *preq)
        struct packed_git **lst;
        struct packed_git *p = preq->target;
        char *tmp_idx;
+       size_t len;
        struct child_process ip = CHILD_PROCESS_INIT;
        const char *ip_argv[8];
 
@@ -1542,9 +1543,9 @@ int finish_http_pack_request(struct http_pack_request *preq)
                lst = &((*lst)->next);
        *lst = (*lst)->next;
 
-       tmp_idx = xstrdup(preq->tmpfile);
-       strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"),
-              ".idx.temp");
+       if (!strip_suffix(preq->tmpfile, ".pack.temp", &len))
+               die("BUG: pack tmpfile does not end in .pack.temp?");
+       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
 
        ip_argv[0] = "index-pack";
        ip_argv[1] = "-o";
@@ -1619,7 +1620,7 @@ struct http_pack_request *new_http_pack_request(
                        fprintf(stderr,
                                "Resuming fetch of pack %s at byte %ld\n",
                                sha1_to_hex(target->sha1), prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn);
                preq->range_header = curl_slist_append(NULL, range);
                curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
                        preq->range_header);
@@ -1779,7 +1780,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
                        fprintf(stderr,
                                "Resuming fetch of object %s at byte %ld\n",
                                hex, prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn);
                range_header = curl_slist_append(range_header, range);
                curl_easy_setopt(freq->slot->curl,
                                 CURLOPT_HTTPHEADER, range_header);
index 37ac4aa86a740ecc8430afff3b9d3c7a0c753a34..4d3b7737a99de02c42945951e91b4d7ed865611d 100644 (file)
@@ -889,9 +889,8 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
        }
 
        /* response: "<user> <digest in hex>" */
-       resp_len = strlen(user) + 1 + strlen(hex) + 1;
-       response = xmalloc(resp_len);
-       sprintf(response, "%s %s", user, hex);
+       response = xstrfmt("%s %s", user, hex);
+       resp_len = strlen(response) + 1;
 
        response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
        encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
@@ -1422,11 +1421,15 @@ static CURL *setup_curl(struct imap_server_conf *srvc)
        curl_easy_setopt(curl, CURLOPT_PORT, server.port);
 
        if (server.auth_method) {
+#if LIBCURL_VERSION_NUM < 0x072200
+               warning("No LOGIN_OPTIONS support in this cURL version");
+#else
                struct strbuf auth = STRBUF_INIT;
                strbuf_addstr(&auth, "AUTH=");
                strbuf_addstr(&auth, server.auth_method);
                curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
                strbuf_release(&auth);
+#endif
        }
 
        if (!server.use_ssl)
index bf83290793059dc42d89db07a12e3bc3c14a48dc..0338630fc2a5378ed33afa828db4940b34aabe82 100644 (file)
@@ -145,11 +145,11 @@ static struct ll_merge_driver ll_merge_drv[] = {
        { "union", "built-in union merge", ll_union_merge },
 };
 
-static void create_temp(mmfile_t *src, char *path)
+static void create_temp(mmfile_t *src, char *path, size_t len)
 {
        int fd;
 
-       strcpy(path, ".merge_file_XXXXXX");
+       xsnprintf(path, len, ".merge_file_XXXXXX");
        fd = xmkstemp(path);
        if (write_in_full(fd, src->ptr, src->size) != src->size)
                die_errno("unable to write temp-file");
@@ -190,10 +190,10 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
 
        result->ptr = NULL;
        result->size = 0;
-       create_temp(orig, temp[0]);
-       create_temp(src1, temp[1]);
-       create_temp(src2, temp[2]);
-       sprintf(temp[3], "%d", marker_size);
+       create_temp(orig, temp[0], sizeof(temp[0]));
+       create_temp(src1, temp[1], sizeof(temp[1]));
+       create_temp(src2, temp[2], sizeof(temp[2]));
+       xsnprintf(temp[3], sizeof(temp[3]), "%d", marker_size);
 
        strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
 
diff --git a/mailinfo.c b/mailinfo.c
new file mode 100644 (file)
index 0000000..f289941
--- /dev/null
@@ -0,0 +1,1037 @@
+#include "cache.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "mailinfo.h"
+
+static void cleanup_space(struct strbuf *sb)
+{
+       size_t pos, cnt;
+       for (pos = 0; pos < sb->len; pos++) {
+               if (isspace(sb->buf[pos])) {
+                       sb->buf[pos] = ' ';
+                       for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+                       strbuf_remove(sb, pos + 1, cnt);
+               }
+       }
+}
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+       struct strbuf *src = name;
+       if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+               strchr(name->buf, '<') || strchr(name->buf, '>'))
+               src = email;
+       else if (name == out)
+               return;
+       strbuf_reset(out);
+       strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
+{
+       /* John Doe <johndoe> */
+
+       char *bra, *ket;
+       /* This is fallback, so do not bother if we already have an
+        * e-mail address.
+        */
+       if (mi->email.len)
+               return;
+
+       bra = strchr(line->buf, '<');
+       if (!bra)
+               return;
+       ket = strchr(bra, '>');
+       if (!ket)
+               return;
+
+       strbuf_reset(&mi->email);
+       strbuf_add(&mi->email, bra + 1, ket - bra - 1);
+
+       strbuf_reset(&mi->name);
+       strbuf_add(&mi->name, line->buf, bra - line->buf);
+       strbuf_trim(&mi->name);
+       get_sane_name(&mi->name, &mi->name, &mi->email);
+}
+
+static void handle_from(struct mailinfo *mi, const struct strbuf *from)
+{
+       char *at;
+       size_t el;
+       struct strbuf f;
+
+       strbuf_init(&f, from->len);
+       strbuf_addbuf(&f, from);
+
+       at = strchr(f.buf, '@');
+       if (!at) {
+               parse_bogus_from(mi, from);
+               return;
+       }
+
+       /*
+        * If we already have one email, don't take any confusing lines
+        */
+       if (mi->email.len && strchr(at + 1, '@')) {
+               strbuf_release(&f);
+               return;
+       }
+
+       /* Pick up the string around '@', possibly delimited with <>
+        * pair; that is the email part.
+        */
+       while (at > f.buf) {
+               char c = at[-1];
+               if (isspace(c))
+                       break;
+               if (c == '<') {
+                       at[-1] = ' ';
+                       break;
+               }
+               at--;
+       }
+       el = strcspn(at, " \n\t\r\v\f>");
+       strbuf_reset(&mi->email);
+       strbuf_add(&mi->email, at, el);
+       strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+       /* The remainder is name.  It could be
+        *
+        * - "John Doe <john.doe@xz>"                   (a), or
+        * - "john.doe@xz (John Doe)"                   (b), or
+        * - "John (zzz) Doe <john.doe@xz> (Comment)"   (c)
+        *
+        * but we have removed the email part, so
+        *
+        * - remove extra spaces which could stay after email (case 'c'), and
+        * - trim from both ends, possibly removing the () pair at the end
+        *   (cases 'a' and 'b').
+        */
+       cleanup_space(&f);
+       strbuf_trim(&f);
+       if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+               strbuf_remove(&f, 0, 1);
+               strbuf_setlen(&f, f.len - 1);
+       }
+
+       get_sane_name(&mi->name, &f, &mi->email);
+       strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+       if (!*out) {
+               *out = xmalloc(sizeof(struct strbuf));
+               strbuf_init(*out, line->len);
+       } else
+               strbuf_reset(*out);
+
+       strbuf_addbuf(*out, line);
+}
+
+/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists.  For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+{
+       const char *ends, *ap = strcasestr(line, name);
+       size_t sz;
+
+       strbuf_setlen(attr, 0);
+       if (!ap)
+               return 0;
+       ap += strlen(name);
+       if (*ap == '"') {
+               ap++;
+               ends = "\"";
+       }
+       else
+               ends = "; \t";
+       sz = strcspn(ap, ends);
+       strbuf_add(attr, ap, sz);
+       return 1;
+}
+
+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+{
+       struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+       strbuf_init(boundary, line->len);
+
+       if (slurp_attr(line->buf, "boundary=", boundary)) {
+               strbuf_insert(boundary, 0, "--", 2);
+               if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+                       error("Too many boundaries to handle");
+                       mi->input_error = -1;
+                       mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
+                       return;
+               }
+               *(mi->content_top) = boundary;
+               boundary = NULL;
+       }
+       slurp_attr(line->buf, "charset=", &mi->charset);
+
+       if (boundary) {
+               strbuf_release(boundary);
+               free(boundary);
+       }
+}
+
+static void handle_message_id(struct mailinfo *mi, const struct strbuf *line)
+{
+       if (mi->add_message_id)
+               mi->message_id = strdup(line->buf);
+}
+
+static void handle_content_transfer_encoding(struct mailinfo *mi,
+                                            const struct strbuf *line)
+{
+       if (strcasestr(line->buf, "base64"))
+               mi->transfer_encoding = TE_BASE64;
+       else if (strcasestr(line->buf, "quoted-printable"))
+               mi->transfer_encoding = TE_QP;
+       else
+               mi->transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
+{
+       struct strbuf *content_top = *(mi->content_top);
+
+       return ((content_top->len <= line->len) &&
+               !memcmp(line->buf, content_top->buf, content_top->len));
+}
+
+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
+{
+       size_t at = 0;
+
+       while (at < subject->len) {
+               char *pos;
+               size_t remove;
+
+               switch (subject->buf[at]) {
+               case 'r': case 'R':
+                       if (subject->len <= at + 3)
+                               break;
+                       if ((subject->buf[at + 1] == 'e' ||
+                            subject->buf[at + 1] == 'E') &&
+                           subject->buf[at + 2] == ':') {
+                               strbuf_remove(subject, at, 3);
+                               continue;
+                       }
+                       at++;
+                       break;
+               case ' ': case '\t': case ':':
+                       strbuf_remove(subject, at, 1);
+                       continue;
+               case '[':
+                       pos = strchr(subject->buf + at, ']');
+                       if (!pos)
+                               break;
+                       remove = pos - subject->buf + at + 1;
+                       if (!mi->keep_non_patch_brackets_in_subject ||
+                           (7 <= remove &&
+                            memmem(subject->buf + at, remove, "PATCH", 5)))
+                               strbuf_remove(subject, at, remove);
+                       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;
+       }
+       strbuf_trim(subject);
+}
+
+#define MAX_HDR_PARSED 10
+static const char *header[MAX_HDR_PARSED] = {
+       "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+       int len = strlen(hdr);
+       return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+                       line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int is_format_patch_separator(const char *line, int len)
+{
+       static const char SAMPLE[] =
+               "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
+       const char *cp;
+
+       if (len != strlen(SAMPLE))
+               return 0;
+       if (!skip_prefix(line, "From ", &cp))
+               return 0;
+       if (strspn(cp, "0123456789abcdef") != 40)
+               return 0;
+       cp += 40;
+       return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+       const char *in = q_seg->buf;
+       int c;
+       struct strbuf *out = xmalloc(sizeof(struct strbuf));
+       strbuf_init(out, q_seg->len);
+
+       while ((c = *in++) != 0) {
+               if (c == '=') {
+                       int d = *in++;
+                       if (d == '\n' || !d)
+                               break; /* drop trailing newline */
+                       strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
+                       continue;
+               }
+               if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+                       c = 0x20;
+               strbuf_addch(out, c);
+       }
+       return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+       /* Decode in..ep, possibly in-place to ot */
+       int c, pos = 0, acc = 0;
+       const char *in = b_seg->buf;
+       struct strbuf *out = xmalloc(sizeof(struct strbuf));
+       strbuf_init(out, b_seg->len);
+
+       while ((c = *in++) != 0) {
+               if (c == '+')
+                       c = 62;
+               else if (c == '/')
+                       c = 63;
+               else if ('A' <= c && c <= 'Z')
+                       c -= 'A';
+               else if ('a' <= c && c <= 'z')
+                       c -= 'a' - 26;
+               else if ('0' <= c && c <= '9')
+                       c -= '0' - 52;
+               else
+                       continue; /* garbage */
+               switch (pos++) {
+               case 0:
+                       acc = (c << 2);
+                       break;
+               case 1:
+                       strbuf_addch(out, (acc | (c >> 4)));
+                       acc = (c & 15) << 4;
+                       break;
+               case 2:
+                       strbuf_addch(out, (acc | (c >> 2)));
+                       acc = (c & 3) << 6;
+                       break;
+               case 3:
+                       strbuf_addch(out, (acc | c));
+                       acc = pos = 0;
+                       break;
+               }
+       }
+       return out;
+}
+
+static int convert_to_utf8(struct mailinfo *mi,
+                          struct strbuf *line, const char *charset)
+{
+       char *out;
+
+       if (!mi->metainfo_charset || !charset || !*charset)
+               return 0;
+
+       if (same_encoding(mi->metainfo_charset, charset))
+               return 0;
+       out = reencode_string(line->buf, mi->metainfo_charset, charset);
+       if (!out) {
+               mi->input_error = -1;
+               return error("cannot convert from %s to %s",
+                            charset, mi->metainfo_charset);
+       }
+       strbuf_attach(line, out, strlen(out), strlen(out));
+       return 0;
+}
+
+static void decode_header(struct mailinfo *mi, struct strbuf *it)
+{
+       char *in, *ep, *cp;
+       struct strbuf outbuf = STRBUF_INIT, *dec;
+       struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+       int found_error = 1; /* pessimism */
+
+       in = it->buf;
+       while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+               int encoding;
+               strbuf_reset(&charset_q);
+               strbuf_reset(&piecebuf);
+
+               if (in != ep) {
+                       /*
+                        * We are about to process an encoded-word
+                        * that begins at ep, but there is something
+                        * before the encoded word.
+                        */
+                       char *scan;
+                       for (scan = in; scan < ep; scan++)
+                               if (!isspace(*scan))
+                                       break;
+
+                       if (scan != ep || in == it->buf) {
+                               /*
+                                * We should not lose that "something",
+                                * unless we have just processed an
+                                * encoded-word, and there is only LWS
+                                * before the one we are about to process.
+                                */
+                               strbuf_add(&outbuf, in, ep - in);
+                       }
+               }
+               /* E.g.
+                * ep : "=?iso-2022-jp?B?GyR...?= foo"
+                * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+                */
+               ep += 2;
+
+               if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+                       goto release_return;
+
+               if (cp + 3 - it->buf > it->len)
+                       goto release_return;
+               strbuf_add(&charset_q, ep, cp - ep);
+
+               encoding = cp[1];
+               if (!encoding || cp[2] != '?')
+                       goto release_return;
+               ep = strstr(cp + 3, "?=");
+               if (!ep)
+                       goto release_return;
+               strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+               switch (tolower(encoding)) {
+               default:
+                       goto release_return;
+               case 'b':
+                       dec = decode_b_segment(&piecebuf);
+                       break;
+               case 'q':
+                       dec = decode_q_segment(&piecebuf, 1);
+                       break;
+               }
+               if (convert_to_utf8(mi, dec, charset_q.buf))
+                       goto release_return;
+
+               strbuf_addbuf(&outbuf, dec);
+               strbuf_release(dec);
+               free(dec);
+               in = ep + 2;
+       }
+       strbuf_addstr(&outbuf, in);
+       strbuf_reset(it);
+       strbuf_addbuf(it, &outbuf);
+       found_error = 0;
+release_return:
+       strbuf_release(&outbuf);
+       strbuf_release(&charset_q);
+       strbuf_release(&piecebuf);
+
+       if (found_error)
+               mi->input_error = -1;
+}
+
+static int check_header(struct mailinfo *mi,
+                       const struct strbuf *line,
+                       struct strbuf *hdr_data[], int overwrite)
+{
+       int i, ret = 0, len;
+       struct strbuf sb = STRBUF_INIT;
+
+       /* search for the interesting parts */
+       for (i = 0; header[i]; i++) {
+               int len = strlen(header[i]);
+               if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+                       /* Unwrap inline B and Q encoding, and optionally
+                        * normalize the meta information to utf8.
+                        */
+                       strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+                       decode_header(mi, &sb);
+                       handle_header(&hdr_data[i], &sb);
+                       ret = 1;
+                       goto check_header_out;
+               }
+       }
+
+       /* Content stuff */
+       if (cmp_header(line, "Content-Type")) {
+               len = strlen("Content-Type: ");
+               strbuf_add(&sb, line->buf + len, line->len - len);
+               decode_header(mi, &sb);
+               strbuf_insert(&sb, 0, "Content-Type: ", len);
+               handle_content_type(mi, &sb);
+               ret = 1;
+               goto check_header_out;
+       }
+       if (cmp_header(line, "Content-Transfer-Encoding")) {
+               len = strlen("Content-Transfer-Encoding: ");
+               strbuf_add(&sb, line->buf + len, line->len - len);
+               decode_header(mi, &sb);
+               handle_content_transfer_encoding(mi, &sb);
+               ret = 1;
+               goto check_header_out;
+       }
+       if (cmp_header(line, "Message-Id")) {
+               len = strlen("Message-Id: ");
+               strbuf_add(&sb, line->buf + len, line->len - len);
+               decode_header(mi, &sb);
+               handle_message_id(mi, &sb);
+               ret = 1;
+               goto check_header_out;
+       }
+
+       /* for inbody stuff */
+       if (starts_with(line->buf, ">From") && isspace(line->buf[5])) {
+               ret = is_format_patch_separator(line->buf + 1, line->len - 1);
+               goto check_header_out;
+       }
+       if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+               for (i = 0; header[i]; i++) {
+                       if (!strcmp("Subject", header[i])) {
+                               handle_header(&hdr_data[i], line);
+                               ret = 1;
+                               goto check_header_out;
+                       }
+               }
+       }
+
+check_header_out:
+       strbuf_release(&sb);
+       return ret;
+}
+
+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
+{
+       struct strbuf *ret;
+
+       switch (mi->transfer_encoding) {
+       case TE_QP:
+               ret = decode_q_segment(line, 0);
+               break;
+       case TE_BASE64:
+               ret = decode_b_segment(line);
+               break;
+       case TE_DONTCARE:
+       default:
+               return;
+       }
+       strbuf_reset(line);
+       strbuf_addbuf(line, ret);
+       strbuf_release(ret);
+       free(ret);
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+       size_t i;
+
+       /* Beginning of a "diff -" header? */
+       if (starts_with(line->buf, "diff -"))
+               return 1;
+
+       /* CVS "Index: " line? */
+       if (starts_with(line->buf, "Index: "))
+               return 1;
+
+       /*
+        * "--- <filename>" starts patches without headers
+        * "---<sp>*" is a manual separator
+        */
+       if (line->len < 4)
+               return 0;
+
+       if (starts_with(line->buf, "---")) {
+               /* space followed by a filename? */
+               if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+                       return 1;
+               /* Just whitespace? */
+               for (i = 3; i < line->len; i++) {
+                       unsigned char c = line->buf[i];
+                       if (c == '\n')
+                               return 1;
+                       if (!isspace(c))
+                               break;
+               }
+               return 0;
+       }
+       return 0;
+}
+
+static int is_scissors_line(const struct strbuf *line)
+{
+       size_t i, len = line->len;
+       int scissors = 0, gap = 0;
+       int first_nonblank = -1;
+       int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
+       const char *buf = line->buf;
+
+       for (i = 0; i < len; i++) {
+               if (isspace(buf[i])) {
+                       if (in_perforation) {
+                               perforation++;
+                               gap++;
+                       }
+                       continue;
+               }
+               last_nonblank = i;
+               if (first_nonblank < 0)
+                       first_nonblank = i;
+               if (buf[i] == '-') {
+                       in_perforation = 1;
+                       perforation++;
+                       continue;
+               }
+               if (i + 1 < len &&
+                   (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) ||
+                    !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) {
+                       in_perforation = 1;
+                       perforation += 2;
+                       scissors += 2;
+                       i++;
+                       continue;
+               }
+               in_perforation = 0;
+       }
+
+       /*
+        * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+        * Even though there can be arbitrary cruft on the same line
+        * (e.g. "cut here"), in order to avoid misidentification, the
+        * perforation must occupy more than a third of the visible
+        * width of the line, and dashes and scissors must occupy more
+        * than half of the perforation.
+        */
+
+       visible = last_nonblank - first_nonblank + 1;
+       return (scissors && 8 <= visible &&
+               visible < perforation * 3 &&
+               gap * 2 < perforation);
+}
+
+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+{
+       assert(!mi->filter_stage);
+
+       if (mi->header_stage) {
+               if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
+                       return 0;
+       }
+
+       if (mi->use_inbody_headers && mi->header_stage) {
+               mi->header_stage = check_header(mi, line, mi->s_hdr_data, 0);
+               if (mi->header_stage)
+                       return 0;
+       } else
+               /* Only trim the first (blank) line of the commit message
+                * when ignoring in-body headers.
+                */
+               mi->header_stage = 0;
+
+       /* normalize the log message to UTF-8. */
+       if (convert_to_utf8(mi, line, mi->charset.buf))
+               return 0; /* mi->input_error already set */
+
+       if (mi->use_scissors && is_scissors_line(line)) {
+               int i;
+
+               strbuf_setlen(&mi->log_message, 0);
+               mi->header_stage = 1;
+
+               /*
+                * We may have already read "secondary headers"; purge
+                * them to give ourselves a clean restart.
+                */
+               for (i = 0; header[i]; i++) {
+                       if (mi->s_hdr_data[i])
+                               strbuf_release(mi->s_hdr_data[i]);
+                       mi->s_hdr_data[i] = NULL;
+               }
+               return 0;
+       }
+
+       if (patchbreak(line)) {
+               if (mi->message_id)
+                       strbuf_addf(&mi->log_message,
+                                   "Message-Id: %s\n", mi->message_id);
+               return 1;
+       }
+
+       strbuf_addbuf(&mi->log_message, line);
+       return 0;
+}
+
+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
+{
+       fwrite(line->buf, 1, line->len, mi->patchfile);
+       mi->patch_lines++;
+}
+
+static void handle_filter(struct mailinfo *mi, struct strbuf *line)
+{
+       switch (mi->filter_stage) {
+       case 0:
+               if (!handle_commit_msg(mi, line))
+                       break;
+               mi->filter_stage++;
+       case 1:
+               handle_patch(mi, line);
+               break;
+       }
+}
+
+static int is_rfc2822_header(const struct strbuf *line)
+{
+       /*
+        * The section that defines the loosest possible
+        * field name is "3.6.8 Optional fields".
+        *
+        * optional-field = field-name ":" unstructured CRLF
+        * field-name = 1*ftext
+        * ftext = %d33-57 / %59-126
+        */
+       int ch;
+       char *cp = line->buf;
+
+       /* Count mbox From headers as headers */
+       if (starts_with(cp, "From ") || starts_with(cp, ">From "))
+               return 1;
+
+       while ((ch = *cp++)) {
+               if (ch == ':')
+                       return 1;
+               if ((33 <= ch && ch <= 57) ||
+                   (59 <= ch && ch <= 126))
+                       continue;
+               break;
+       }
+       return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+       struct strbuf continuation = STRBUF_INIT;
+
+       /* Get the first part of the line. */
+       if (strbuf_getline(line, in, '\n'))
+               return 0;
+
+       /*
+        * Is it an empty line or not a valid rfc2822 header?
+        * If so, stop here, and return false ("not a header")
+        */
+       strbuf_rtrim(line);
+       if (!line->len || !is_rfc2822_header(line)) {
+               /* Re-add the newline */
+               strbuf_addch(line, '\n');
+               return 0;
+       }
+
+       /*
+        * Now we need to eat all the continuation lines..
+        * Yuck, 2822 header "folding"
+        */
+       for (;;) {
+               int peek;
+
+               peek = fgetc(in); ungetc(peek, in);
+               if (peek != ' ' && peek != '\t')
+                       break;
+               if (strbuf_getline(&continuation, in, '\n'))
+                       break;
+               continuation.buf[0] = ' ';
+               strbuf_rtrim(&continuation);
+               strbuf_addbuf(line, &continuation);
+       }
+       strbuf_release(&continuation);
+
+       return 1;
+}
+
+static int find_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+       while (!strbuf_getline(line, mi->input, '\n')) {
+               if (*(mi->content_top) && is_multipart_boundary(mi, line))
+                       return 1;
+       }
+       return 0;
+}
+
+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+       struct strbuf newline = STRBUF_INIT;
+
+       strbuf_addch(&newline, '\n');
+again:
+       if (line->len >= (*(mi->content_top))->len + 2 &&
+           !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
+               /* we hit an end boundary */
+               /* pop the current boundary off the stack */
+               strbuf_release(*(mi->content_top));
+               free(*(mi->content_top));
+               *(mi->content_top) = NULL;
+
+               /* technically won't happen as is_multipart_boundary()
+                  will fail first.  But just in case..
+                */
+               if (--mi->content_top < mi->content) {
+                       error("Detected mismatched boundaries, can't recover");
+                       mi->input_error = -1;
+                       mi->content_top = mi->content;
+                       return 0;
+               }
+               handle_filter(mi, &newline);
+               strbuf_release(&newline);
+               if (mi->input_error)
+                       return 0;
+
+               /* skip to the next boundary */
+               if (!find_boundary(mi, line))
+                       return 0;
+               goto again;
+       }
+
+       /* set some defaults */
+       mi->transfer_encoding = TE_DONTCARE;
+       strbuf_reset(&mi->charset);
+
+       /* slurp in this section's info */
+       while (read_one_header_line(line, mi->input))
+               check_header(mi, line, mi->p_hdr_data, 0);
+
+       strbuf_release(&newline);
+       /* replenish line */
+       if (strbuf_getline(line, mi->input, '\n'))
+               return 0;
+       strbuf_addch(line, '\n');
+       return 1;
+}
+
+static void handle_body(struct mailinfo *mi, struct strbuf *line)
+{
+       struct strbuf prev = STRBUF_INIT;
+
+       /* Skip up to the first boundary */
+       if (*(mi->content_top)) {
+               if (!find_boundary(mi, line))
+                       goto handle_body_out;
+       }
+
+       do {
+               /* process any boundary lines */
+               if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
+                       /* flush any leftover */
+                       if (prev.len) {
+                               handle_filter(mi, &prev);
+                               strbuf_reset(&prev);
+                       }
+                       if (!handle_boundary(mi, line))
+                               goto handle_body_out;
+               }
+
+               /* Unwrap transfer encoding */
+               decode_transfer_encoding(mi, line);
+
+               switch (mi->transfer_encoding) {
+               case TE_BASE64:
+               case TE_QP:
+               {
+                       struct strbuf **lines, **it, *sb;
+
+                       /* Prepend any previous partial lines */
+                       strbuf_insert(line, 0, prev.buf, prev.len);
+                       strbuf_reset(&prev);
+
+                       /*
+                        * This is a decoded line that may contain
+                        * multiple new lines.  Pass only one chunk
+                        * at a time to handle_filter()
+                        */
+                       lines = strbuf_split(line, '\n');
+                       for (it = lines; (sb = *it); it++) {
+                               if (*(it + 1) == NULL) /* The last line */
+                                       if (sb->buf[sb->len - 1] != '\n') {
+                                               /* Partial line, save it for later. */
+                                               strbuf_addbuf(&prev, sb);
+                                               break;
+                                       }
+                               handle_filter(mi, sb);
+                       }
+                       /*
+                        * The partial chunk is saved in "prev" and will be
+                        * appended by the next iteration of read_line_with_nul().
+                        */
+                       strbuf_list_free(lines);
+                       break;
+               }
+               default:
+                       handle_filter(mi, line);
+               }
+
+               if (mi->input_error)
+                       break;
+       } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
+handle_body_out:
+       strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+       const char *sp = data->buf;
+       while (1) {
+               char *ep = strchr(sp, '\n');
+               int len;
+               if (!ep)
+                       len = strlen(sp);
+               else
+                       len = ep - sp;
+               fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+               if (!ep)
+                       break;
+               sp = ep + 1;
+       }
+}
+
+static void handle_info(struct mailinfo *mi)
+{
+       struct strbuf *hdr;
+       int i;
+
+       for (i = 0; header[i]; i++) {
+               /* only print inbody headers if we output a patch file */
+               if (mi->patch_lines && mi->s_hdr_data[i])
+                       hdr = mi->s_hdr_data[i];
+               else if (mi->p_hdr_data[i])
+                       hdr = mi->p_hdr_data[i];
+               else
+                       continue;
+
+               if (!strcmp(header[i], "Subject")) {
+                       if (!mi->keep_subject) {
+                               cleanup_subject(mi, hdr);
+                               cleanup_space(hdr);
+                       }
+                       output_header_lines(mi->output, "Subject", hdr);
+               } else if (!strcmp(header[i], "From")) {
+                       cleanup_space(hdr);
+                       handle_from(mi, hdr);
+                       fprintf(mi->output, "Author: %s\n", mi->name.buf);
+                       fprintf(mi->output, "Email: %s\n", mi->email.buf);
+               } else {
+                       cleanup_space(hdr);
+                       fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
+               }
+       }
+       fprintf(mi->output, "\n");
+}
+
+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
+{
+       FILE *cmitmsg;
+       int peek;
+       struct strbuf line = STRBUF_INIT;
+
+       cmitmsg = fopen(msg, "w");
+       if (!cmitmsg) {
+               perror(msg);
+               return -1;
+       }
+       mi->patchfile = fopen(patch, "w");
+       if (!mi->patchfile) {
+               perror(patch);
+               fclose(cmitmsg);
+               return -1;
+       }
+
+       mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
+       mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
+
+       do {
+               peek = fgetc(mi->input);
+       } while (isspace(peek));
+       ungetc(peek, mi->input);
+
+       /* process the email header */
+       while (read_one_header_line(&line, mi->input))
+               check_header(mi, &line, mi->p_hdr_data, 1);
+
+       handle_body(mi, &line);
+       fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
+       fclose(cmitmsg);
+       fclose(mi->patchfile);
+
+       handle_info(mi);
+       strbuf_release(&line);
+       return mi->input_error;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *mi_)
+{
+       struct mailinfo *mi = mi_;
+
+       if (!starts_with(var, "mailinfo."))
+               return git_default_config(var, value, NULL);
+       if (!strcmp(var, "mailinfo.scissors")) {
+               mi->use_scissors = git_config_bool(var, value);
+               return 0;
+       }
+       /* perhaps others here */
+       return 0;
+}
+
+void setup_mailinfo(struct mailinfo *mi)
+{
+       memset(mi, 0, sizeof(*mi));
+       strbuf_init(&mi->name, 0);
+       strbuf_init(&mi->email, 0);
+       strbuf_init(&mi->charset, 0);
+       strbuf_init(&mi->log_message, 0);
+       mi->header_stage = 1;
+       mi->use_inbody_headers = 1;
+       mi->content_top = mi->content;
+       git_config(git_mailinfo_config, mi);
+}
+
+void clear_mailinfo(struct mailinfo *mi)
+{
+       int i;
+
+       strbuf_release(&mi->name);
+       strbuf_release(&mi->email);
+       strbuf_release(&mi->charset);
+       free(mi->message_id);
+
+       for (i = 0; mi->p_hdr_data[i]; i++)
+               strbuf_release(mi->p_hdr_data[i]);
+       free(mi->p_hdr_data);
+       for (i = 0; mi->s_hdr_data[i]; i++)
+               strbuf_release(mi->s_hdr_data[i]);
+       free(mi->s_hdr_data);
+
+       while (mi->content < mi->content_top) {
+               free(*(mi->content_top));
+               mi->content_top--;
+       }
+
+       strbuf_release(&mi->log_message);
+}
diff --git a/mailinfo.h b/mailinfo.h
new file mode 100644 (file)
index 0000000..93776a7
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef MAILINFO_H
+#define MAILINFO_H
+
+#define MAX_BOUNDARIES 5
+
+struct mailinfo {
+       FILE *input;
+       FILE *output;
+       FILE *patchfile;
+
+       struct strbuf name;
+       struct strbuf email;
+       int keep_subject;
+       int keep_non_patch_brackets_in_subject;
+       int add_message_id;
+       int use_scissors;
+       int use_inbody_headers;
+       const char *metainfo_charset;
+
+       struct strbuf *content[MAX_BOUNDARIES];
+       struct strbuf **content_top;
+       struct strbuf charset;
+       char *message_id;
+       enum  {
+               TE_DONTCARE, TE_QP, TE_BASE64
+       } transfer_encoding;
+       int patch_lines;
+       int filter_stage; /* still reading log or are we copying patch? */
+       int header_stage; /* still checking in-body headers? */
+       struct strbuf **p_hdr_data;
+       struct strbuf **s_hdr_data;
+
+       struct strbuf log_message;
+       int input_error;
+};
+
+extern void setup_mailinfo(struct mailinfo *);
+extern int mailinfo(struct mailinfo *, const char *msg, const char *patch);
+extern void clear_mailinfo(struct mailinfo *);
+
+#endif /* MAILINFO_H */
index 9e9589730fa44c94d122dfdb50172baf95e2578d..f4a0f1cf27bf0a10626cc5b1fd628e12cbda3a7e 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -162,11 +162,10 @@ static void read_mailmap_line(struct string_list *map, char *buffer,
                        char *cp;
 
                        free(*repo_abbrev);
-                       *repo_abbrev = xmalloc(len);
 
                        for (cp = buffer + abblen; isspace(*cp); cp++)
                                ; /* nothing */
-                       strcpy(*repo_abbrev, cp);
+                       *repo_abbrev = xstrdup(cp);
                }
                return;
        }
index 44d85bea4b678b761d737f0983ed8e7757478cc8..21e680a78e758eeaf03cf03a8b485095d96bcf5d 100644 (file)
@@ -630,25 +630,24 @@ static char *unique_path(struct merge_options *o, const char *path, const char *
 
 static int dir_in_way(const char *path, int check_working_copy)
 {
-       int pos, pathlen = strlen(path);
-       char *dirpath = xmalloc(pathlen + 2);
+       int pos;
+       struct strbuf dirpath = STRBUF_INIT;
        struct stat st;
 
-       strcpy(dirpath, path);
-       dirpath[pathlen] = '/';
-       dirpath[pathlen+1] = '\0';
+       strbuf_addstr(&dirpath, path);
+       strbuf_addch(&dirpath, '/');
 
-       pos = cache_name_pos(dirpath, pathlen+1);
+       pos = cache_name_pos(dirpath.buf, dirpath.len);
 
        if (pos < 0)
                pos = -1 - pos;
        if (pos < active_nr &&
-           !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
-               free(dirpath);
+           !strncmp(dirpath.buf, active_cache[pos]->name, dirpath.len)) {
+               strbuf_release(&dirpath);
                return 1;
        }
 
-       free(dirpath);
+       strbuf_release(&dirpath);
        return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
 }
 
@@ -1531,13 +1530,17 @@ static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
 }
 
 static int blob_unchanged(const unsigned char *o_sha,
+                         unsigned o_mode,
                          const unsigned char *a_sha,
+                         unsigned a_mode,
                          int renormalize, const char *path)
 {
        struct strbuf o = STRBUF_INIT;
        struct strbuf a = STRBUF_INIT;
        int ret = 0; /* assume changed for safety */
 
+       if (a_mode != o_mode)
+               return 0;
        if (sha_eq(o_sha, a_sha))
                return 1;
        if (!renormalize)
@@ -1723,8 +1726,8 @@ static int process_entry(struct merge_options *o,
        } else if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
-                   (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
-                   (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
+                   (!b_sha && blob_unchanged(o_sha, o_mode, a_sha, a_mode, normalize, path)) ||
+                   (!a_sha && blob_unchanged(o_sha, o_mode, b_sha, b_mode, normalize, path))) {
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
diff --git a/notes.c b/notes.c
index eacd2a61daf9b4133e81a936340abc63c1078a48..db77922130b4f7df6ab72122206a76d6c580faac 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -539,6 +539,9 @@ static unsigned char determine_fanout(struct int_node *tree, unsigned char n,
        return fanout + 1;
 }
 
+/* hex SHA1 + 19 * '/' + NUL */
+#define FANOUT_PATH_MAX 40 + 19 + 1
+
 static void construct_path_with_fanout(const unsigned char *sha1,
                unsigned char fanout, char *path)
 {
@@ -551,7 +554,7 @@ static void construct_path_with_fanout(const unsigned char *sha1,
                path[i++] = '/';
                fanout--;
        }
-       strcpy(path + i, hex_sha1 + j);
+       xsnprintf(path + i, FANOUT_PATH_MAX - i, "%s", hex_sha1 + j);
 }
 
 static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
@@ -562,7 +565,7 @@ static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
        void *p;
        int ret = 0;
        struct leaf_node *l;
-       static char path[40 + 19 + 1];  /* hex SHA1 + 19 * '/' + NUL */
+       static char path[FANOUT_PATH_MAX];
 
        fanout = determine_fanout(tree, n, fanout);
        for (i = 0; i < 16; i++) {
@@ -595,7 +598,7 @@ static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
                                /* invoke callback with subtree */
                                unsigned int path_len =
                                        l->key_sha1[19] * 2 + fanout;
-                               assert(path_len < 40 + 19);
+                               assert(path_len < FANOUT_PATH_MAX - 1);
                                construct_path_with_fanout(l->key_sha1, fanout,
                                                           path);
                                /* Create trailing slash, if needed */
index 637770af813eee643c8375051e419715f56a2db5..7dfcb341d61aa93e2c655963571ba1809fc93d40 100644 (file)
@@ -252,16 +252,11 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
 
 static char *pack_bitmap_filename(struct packed_git *p)
 {
-       char *idx_name;
-       int len;
-
-       len = strlen(p->pack_name) - strlen(".pack");
-       idx_name = xmalloc(len + strlen(".bitmap") + 1);
-
-       memcpy(idx_name, p->pack_name, len);
-       memcpy(idx_name + len, ".bitmap", strlen(".bitmap") + 1);
+       size_t len;
 
-       return idx_name;
+       if (!strip_suffix(p->pack_name, ".pack", &len))
+               die("BUG: pack_name does not end in .pack");
+       return xstrfmt("%.*s.bitmap", (int)len, p->pack_name);
 }
 
 static int open_pack_bitmap_1(struct packed_git *packfile)
index 5c8376e9789301b921188e3bf19fba85960a7967..e542ea7703a740916f8d2cac9176674782d37f61 100644 (file)
@@ -21,7 +21,7 @@ static int pack_revindex_hashsz;
 
 static int pack_revindex_ix(struct packed_git *p)
 {
-       unsigned long ui = (unsigned long)p;
+       unsigned long ui = (unsigned long)(intptr_t)p;
        int i;
 
        ui = ui ^ (ui >> 16); /* defeat structure alignment */
index 5ab6ed6b0875ff703e3d55abbd0e9d8b0aa1a3c7..239898d946f06d102030569fd45780f523fdcd5a 100644 (file)
@@ -5,6 +5,7 @@
 #include "color.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "sha1-array.h"
 
 /*----- some often used options -----*/
 
@@ -77,7 +78,7 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
-int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+int parse_opt_commits(const struct option *opt, const char *arg, int unset)
 {
        unsigned char sha1[20];
        struct commit *commit;
@@ -93,6 +94,22 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+int parse_opt_object_name(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(opt->value);
+               return 0;
+       }
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(opt->value, sha1);
+       return 0;
+}
+
 int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 {
        int *target = opt->value;
index 3f1cc3aee0faaed923f736393db45877de14edc3..e8b55ea87aaea6a0e16239fd9b2025c4927d2145 100644 (file)
@@ -223,7 +223,8 @@ extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
 extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
 extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
-extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_object_name(const struct option *, const char *, int);
+extern int parse_opt_commits(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
 extern int parse_opt_noop_cb(const struct option *, const char *, int);
@@ -251,5 +252,12 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru }
 #define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv }
+#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \
+       { OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \
+         PARSE_OPT_LASTARG_DEFAULT | flag, \
+         parse_opt_commits, (intptr_t) "HEAD" \
+       }
+#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0)
+#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN)
 
 #endif
diff --git a/path.c b/path.c
index 69265f42cec4cfb19f6651b0dc845d2ab744711d..c740c4ff9403bc92df4c439839bd5596ac62e48d 100644 (file)
--- a/path.c
+++ b/path.c
@@ -91,58 +91,274 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
                buf->buf[newlen] = '/';
 }
 
-static const char *common_list[] = {
-       "/branches", "/hooks", "/info", "!/logs", "/lost-found",
-       "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
-       "config", "!gc.pid", "packed-refs", "shallow",
-       NULL
+struct common_dir {
+       /* Not considered garbage for report_linked_checkout_garbage */
+       unsigned ignore_garbage:1;
+       unsigned is_dir:1;
+       /* Not common even though its parent is */
+       unsigned exclude:1;
+       const char *dirname;
 };
 
-static void update_common_dir(struct strbuf *buf, int git_dir_len, const char *common_dir)
+static struct common_dir common_list[] = {
+       { 0, 1, 0, "branches" },
+       { 0, 1, 0, "hooks" },
+       { 0, 1, 0, "info" },
+       { 0, 0, 1, "info/sparse-checkout" },
+       { 1, 1, 0, "logs" },
+       { 1, 1, 1, "logs/HEAD" },
+       { 0, 1, 1, "logs/refs/bisect" },
+       { 0, 1, 0, "lost-found" },
+       { 0, 1, 0, "objects" },
+       { 0, 1, 0, "refs" },
+       { 0, 1, 1, "refs/bisect" },
+       { 0, 1, 0, "remotes" },
+       { 0, 1, 0, "worktrees" },
+       { 0, 1, 0, "rr-cache" },
+       { 0, 1, 0, "svn" },
+       { 0, 0, 0, "config" },
+       { 1, 0, 0, "gc.pid" },
+       { 0, 0, 0, "packed-refs" },
+       { 0, 0, 0, "shallow" },
+       { 0, 0, 0, NULL }
+};
+
+/*
+ * A compressed trie.  A trie node consists of zero or more characters that
+ * are common to all elements with this prefix, optionally followed by some
+ * children.  If value is not NULL, the trie node is a terminal node.
+ *
+ * For example, consider the following set of strings:
+ * abc
+ * def
+ * definite
+ * definition
+ *
+ * The trie would look look like:
+ * root: len = 0, children a and d non-NULL, value = NULL.
+ *    a: len = 2, contents = bc, value = (data for "abc")
+ *    d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
+ *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
+ *           e: len = 0, children all NULL, value = (data for "definite")
+ *           i: len = 2, contents = on, children all NULL,
+ *              value = (data for "definition")
+ */
+struct trie {
+       struct trie *children[256];
+       int len;
+       char *contents;
+       void *value;
+};
+
+static struct trie *make_trie_node(const char *key, void *value)
 {
-       char *base = buf->buf + git_dir_len;
-       const char **p;
-
-       if (is_dir_file(base, "logs", "HEAD") ||
-           is_dir_file(base, "info", "sparse-checkout"))
-               return; /* keep this in $GIT_DIR */
-       for (p = common_list; *p; p++) {
-               const char *path = *p;
-               int is_dir = 0;
-               if (*path == '!')
-                       path++;
-               if (*path == '/') {
-                       path++;
-                       is_dir = 1;
+       struct trie *new_node = xcalloc(1, sizeof(*new_node));
+       new_node->len = strlen(key);
+       if (new_node->len) {
+               new_node->contents = xmalloc(new_node->len);
+               memcpy(new_node->contents, key, new_node->len);
+       }
+       new_node->value = value;
+       return new_node;
+}
+
+/*
+ * Add a key/value pair to a trie.  The key is assumed to be \0-terminated.
+ * If there was an existing value for this key, return it.
+ */
+static void *add_to_trie(struct trie *root, const char *key, void *value)
+{
+       struct trie *child;
+       void *old;
+       int i;
+
+       if (!*key) {
+               /* we have reached the end of the key */
+               old = root->value;
+               root->value = value;
+               return old;
+       }
+
+       for (i = 0; i < root->len; i++) {
+               if (root->contents[i] == key[i])
+                       continue;
+
+               /*
+                * Split this node: child will contain this node's
+                * existing children.
+                */
+               child = malloc(sizeof(*child));
+               memcpy(child->children, root->children, sizeof(root->children));
+
+               child->len = root->len - i - 1;
+               if (child->len) {
+                       child->contents = xstrndup(root->contents + i + 1,
+                                                  child->len);
                }
+               child->value = root->value;
+               root->value = NULL;
+               root->len = i;
+
+               memset(root->children, 0, sizeof(root->children));
+               root->children[(unsigned char)root->contents[i]] = child;
 
-               if (!common_dir)
-                       common_dir = get_git_common_dir();
+               /* This is the newly-added child. */
+               root->children[(unsigned char)key[i]] =
+                       make_trie_node(key + i + 1, value);
+               return NULL;
+       }
 
-               if (is_dir && dir_prefix(base, path)) {
-                       replace_dir(buf, git_dir_len, common_dir);
-                       return;
+       /* We have matched the entire compressed section */
+       if (key[i]) {
+               child = root->children[(unsigned char)key[root->len]];
+               if (child) {
+                       return add_to_trie(child, key + root->len + 1, value);
+               } else {
+                       child = make_trie_node(key + root->len + 1, value);
+                       root->children[(unsigned char)key[root->len]] = child;
+                       return NULL;
                }
-               if (!is_dir && !strcmp(base, path)) {
-                       replace_dir(buf, git_dir_len, common_dir);
-                       return;
+       }
+
+       old = root->value;
+       root->value = value;
+       return old;
+}
+
+typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
+
+/*
+ * Search a trie for some key.  Find the longest /-or-\0-terminated
+ * prefix of the key for which the trie contains a value.  Call fn
+ * with the unmatched portion of the key and the found value, and
+ * return its return value.  If there is no such prefix, return -1.
+ *
+ * The key is partially normalized: consecutive slashes are skipped.
+ *
+ * For example, consider the trie containing only [refs,
+ * refs/worktree] (both with values).
+ *
+ * | key             | unmatched  | val from node | return value |
+ * |-----------------|------------|---------------|--------------|
+ * | a               | not called | n/a           | -1           |
+ * | refs            | \0         | refs          | as per fn    |
+ * | refs/           | /          | refs          | as per fn    |
+ * | refs/w          | /w         | refs          | as per fn    |
+ * | refs/worktree   | \0         | refs/worktree | as per fn    |
+ * | refs/worktree/  | /          | refs/worktree | as per fn    |
+ * | refs/worktree/a | /a         | refs/worktree | as per fn    |
+ * |-----------------|------------|---------------|--------------|
+ *
+ */
+static int trie_find(struct trie *root, const char *key, match_fn fn,
+                    void *baton)
+{
+       int i;
+       int result;
+       struct trie *child;
+
+       if (!*key) {
+               /* we have reached the end of the key */
+               if (root->value && !root->len)
+                       return fn(key, root->value, baton);
+               else
+                       return -1;
+       }
+
+       for (i = 0; i < root->len; i++) {
+               /* Partial path normalization: skip consecutive slashes. */
+               if (key[i] == '/' && key[i+1] == '/') {
+                       key++;
+                       continue;
                }
+               if (root->contents[i] != key[i])
+                       return -1;
        }
+
+       /* Matched the entire compressed section */
+       key += i;
+       if (!*key)
+               /* End of key */
+               return fn(key, root->value, baton);
+
+       /* Partial path normalization: skip consecutive slashes */
+       while (key[0] == '/' && key[1] == '/')
+               key++;
+
+       child = root->children[(unsigned char)*key];
+       if (child)
+               result = trie_find(child, key + 1, fn, baton);
+       else
+               result = -1;
+
+       if (result >= 0 || (*key != '/' && *key != 0))
+               return result;
+       if (root->value)
+               return fn(key, root->value, baton);
+       else
+               return -1;
+}
+
+static struct trie common_trie;
+static int common_trie_done_setup;
+
+static void init_common_trie(void)
+{
+       struct common_dir *p;
+
+       if (common_trie_done_setup)
+               return;
+
+       for (p = common_list; p->dirname; p++)
+               add_to_trie(&common_trie, p->dirname, p);
+
+       common_trie_done_setup = 1;
+}
+
+/*
+ * Helper function for update_common_dir: returns 1 if the dir
+ * prefix is common.
+ */
+static int check_common(const char *unmatched, void *value, void *baton)
+{
+       struct common_dir *dir = value;
+
+       if (!dir)
+               return 0;
+
+       if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
+               return !dir->exclude;
+
+       if (!dir->is_dir && unmatched[0] == 0)
+               return !dir->exclude;
+
+       return 0;
+}
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len,
+                             const char *common_dir)
+{
+       char *base = buf->buf + git_dir_len;
+       init_common_trie();
+       if (!common_dir)
+               common_dir = get_git_common_dir();
+       if (trie_find(&common_trie, base, check_common, NULL) > 0)
+               replace_dir(buf, git_dir_len, common_dir);
 }
 
 void report_linked_checkout_garbage(void)
 {
        struct strbuf sb = STRBUF_INIT;
-       const char **p;
+       const struct common_dir *p;
        int len;
 
        if (!git_common_dir_env)
                return;
        strbuf_addf(&sb, "%s/", get_git_dir());
        len = sb.len;
-       for (p = common_list; *p; p++) {
-               const char *path = *p;
-               if (*path == '!')
+       for (p = common_list; p->dirname; p++) {
+               const char *path = p->dirname;
+               if (p->ignore_garbage)
                        continue;
                strbuf_setlen(&sb, len);
                strbuf_addstr(&sb, path);
@@ -179,6 +395,16 @@ static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
        strbuf_cleanup_path(buf);
 }
 
+char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
+{
+       va_list args;
+       strbuf_reset(buf);
+       va_start(args, fmt);
+       do_git_path(buf, fmt, args);
+       va_end(args);
+       return buf->buf;
+}
+
 void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
        va_list args;
@@ -236,8 +462,7 @@ static void do_submodule_path(struct strbuf *buf, const char *path,
        struct strbuf git_submodule_dir = STRBUF_INIT;
 
        strbuf_addstr(buf, path);
-       if (buf->len && buf->buf[buf->len - 1] != '/')
-               strbuf_addch(buf, '/');
+       strbuf_complete(buf, '/');
        strbuf_addstr(buf, ".git");
 
        git_dir = read_gitfile(buf->buf);
@@ -395,8 +620,8 @@ char *expand_user_path(const char *path)
  */
 const char *enter_repo(const char *path, int strict)
 {
-       static char used_path[PATH_MAX];
-       static char validated_path[PATH_MAX];
+       static struct strbuf validated_path = STRBUF_INIT;
+       static struct strbuf used_path = STRBUF_INIT;
 
        if (!path)
                return NULL;
@@ -411,52 +636,57 @@ const char *enter_repo(const char *path, int strict)
                while ((1 < len) && (path[len-1] == '/'))
                        len--;
 
+               /*
+                * We can handle arbitrary-sized buffers, but this remains as a
+                * sanity check on untrusted input.
+                */
                if (PATH_MAX <= len)
                        return NULL;
-               strncpy(used_path, path, len); used_path[len] = 0 ;
-               strcpy(validated_path, used_path);
 
-               if (used_path[0] == '~') {
-                       char *newpath = expand_user_path(used_path);
-                       if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
-                               free(newpath);
+               strbuf_reset(&used_path);
+               strbuf_reset(&validated_path);
+               strbuf_add(&used_path, path, len);
+               strbuf_add(&validated_path, path, len);
+
+               if (used_path.buf[0] == '~') {
+                       char *newpath = expand_user_path(used_path.buf);
+                       if (!newpath)
                                return NULL;
-                       }
-                       /*
-                        * Copy back into the static buffer. A pity
-                        * since newpath was not bounded, but other
-                        * branches of the if are limited by PATH_MAX
-                        * anyway.
-                        */
-                       strcpy(used_path, newpath); free(newpath);
+                       strbuf_attach(&used_path, newpath, strlen(newpath),
+                                     strlen(newpath));
                }
-               else if (PATH_MAX - 10 < len)
-                       return NULL;
-               len = strlen(used_path);
                for (i = 0; suffix[i]; i++) {
                        struct stat st;
-                       strcpy(used_path + len, suffix[i]);
-                       if (!stat(used_path, &st) &&
+                       size_t baselen = used_path.len;
+                       strbuf_addstr(&used_path, suffix[i]);
+                       if (!stat(used_path.buf, &st) &&
                            (S_ISREG(st.st_mode) ||
-                           (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
-                               strcat(validated_path, suffix[i]);
+                           (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {
+                               strbuf_addstr(&validated_path, suffix[i]);
                                break;
                        }
+                       strbuf_setlen(&used_path, baselen);
                }
                if (!suffix[i])
                        return NULL;
-               gitfile = read_gitfile(used_path) ;
+               gitfile = read_gitfile(used_path.buf);
+               if (gitfile) {
+                       strbuf_reset(&used_path);
+                       strbuf_addstr(&used_path, gitfile);
+               }
+               if (chdir(used_path.buf))
+                       return NULL;
+               path = validated_path.buf;
+       }
+       else {
+               const char *gitfile = read_gitfile(path);
                if (gitfile)
-                       strcpy(used_path, gitfile);
-               if (chdir(used_path))
+                       path = gitfile;
+               if (chdir(path))
                        return NULL;
-               path = validated_path;
        }
-       else if (chdir(path))
-               return NULL;
 
-       if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
-           validate_headref("HEAD") == 0) {
+       if (is_git_directory(".")) {
                set_git_dir(".");
                check_repository_format();
                return path;
@@ -635,7 +865,7 @@ const char *relative_path(const char *in, const char *prefix,
  */
 const char *remove_leading_path(const char *in, const char *prefix)
 {
-       static char buf[PATH_MAX + 1];
+       static struct strbuf buf = STRBUF_INIT;
        int i = 0, j = 0;
 
        if (!prefix || !prefix[0])
@@ -664,11 +894,13 @@ const char *remove_leading_path(const char *in, const char *prefix)
                return in;
        while (is_dir_sep(in[j]))
                j++;
+
+       strbuf_reset(&buf);
        if (!in[j])
-               strcpy(buf, ".");
+               strbuf_addstr(&buf, ".");
        else
-               strcpy(buf, in + j);
-       return buf;
+               strbuf_addstr(&buf, in + j);
+       return buf.buf;
 }
 
 /*
index 08a1427c0d4f6743182b235ec9d9c710c3c99b5b..62fdb37079fa31698846044448b5cfb398e45528 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pkt-line.h"
+#include "run-command.h"
 
 char packet_buffer[LARGE_PACKET_MAX];
 static const char *packet_trace_prefix = "git";
@@ -11,6 +12,11 @@ void packet_trace_identity(const char *prog)
        packet_trace_prefix = xstrdup(prog);
 }
 
+static const char *get_trace_prefix(void)
+{
+       return in_async() ? "sideband" : packet_trace_prefix;
+}
+
 static int packet_trace_pack(const char *buf, unsigned int len, int sideband)
 {
        if (!sideband) {
@@ -57,7 +63,7 @@ static void packet_trace(const char *buf, unsigned int len, int write)
        strbuf_init(&out, len+32);
 
        strbuf_addf(&out, "packet: %12s%c ",
-                   packet_trace_prefix, write ? '>' : '<');
+                   get_trace_prefix(), write ? '>' : '<');
 
        /* XXX we should really handle printable utf8 */
        for (i = 0; i < len; i++) {
index 2e31bec60f5c98dc7dac69227759cbf2ba5f9974..353bd37416e65f6dba733ac9d14999f1e293b4b9 100644 (file)
@@ -25,7 +25,7 @@ struct throughput {
        unsigned int last_bytes[TP_IDX_MAX];
        unsigned int last_misecs[TP_IDX_MAX];
        unsigned int idx;
-       char display[32];
+       struct strbuf display;
 };
 
 struct progress {
@@ -98,7 +98,7 @@ static int display(struct progress *progress, unsigned n, const char *done)
        }
 
        progress->last_value = n;
-       tp = (progress->throughput) ? progress->throughput->display : "";
+       tp = (progress->throughput) ? progress->throughput->display.buf : "";
        eol = done ? done : "   \r";
        if (progress->total) {
                unsigned percent = n * 100 / progress->total;
@@ -129,6 +129,7 @@ static int display(struct progress *progress, unsigned n, const char *done)
 static void throughput_string(struct strbuf *buf, off_t total,
                              unsigned int rate)
 {
+       strbuf_reset(buf);
        strbuf_addstr(buf, ", ");
        strbuf_humanise_bytes(buf, total);
        strbuf_addstr(buf, " | ");
@@ -141,7 +142,6 @@ void display_throughput(struct progress *progress, off_t total)
        struct throughput *tp;
        uint64_t now_ns;
        unsigned int misecs, count, rate;
-       struct strbuf buf = STRBUF_INIT;
 
        if (!progress)
                return;
@@ -154,6 +154,7 @@ void display_throughput(struct progress *progress, off_t total)
                if (tp) {
                        tp->prev_total = tp->curr_total = total;
                        tp->prev_ns = now_ns;
+                       strbuf_init(&tp->display, 0);
                }
                return;
        }
@@ -193,9 +194,7 @@ void display_throughput(struct progress *progress, off_t total)
        tp->last_misecs[tp->idx] = misecs;
        tp->idx = (tp->idx + 1) % TP_IDX_MAX;
 
-       throughput_string(&buf, total, rate);
-       strncpy(tp->display, buf.buf, sizeof(tp->display));
-       strbuf_release(&buf);
+       throughput_string(&tp->display, total, rate);
        if (progress->last_value != -1 && progress_update)
                display(progress, progress->last_value, NULL);
 }
@@ -250,20 +249,19 @@ void stop_progress_msg(struct progress **p_progress, const char *msg)
 
                bufp = (len < sizeof(buf)) ? buf : xmalloc(len + 1);
                if (tp) {
-                       struct strbuf strbuf = STRBUF_INIT;
                        unsigned int rate = !tp->avg_misecs ? 0 :
                                        tp->avg_bytes / tp->avg_misecs;
-                       throughput_string(&strbuf, tp->curr_total, rate);
-                       strncpy(tp->display, strbuf.buf, sizeof(tp->display));
-                       strbuf_release(&strbuf);
+                       throughput_string(&tp->display, tp->curr_total, rate);
                }
                progress_update = 1;
-               sprintf(bufp, ", %s.\n", msg);
+               xsnprintf(bufp, len + 1, ", %s.\n", msg);
                display(progress, progress->last_value, bufp);
                if (buf != bufp)
                        free(bufp);
        }
        clear_progress_signal();
+       if (progress->throughput)
+               strbuf_release(&progress->throughput->display);
        free(progress->throughput);
        free(progress);
 }
index de22df2e20166e9c0a0d3da69525612259a99363..84616c8e219bf3cf902643a8da62a2060d335c36 100644 (file)
@@ -17,7 +17,6 @@
 #include "strbuf.h"
 #include "varint.h"
 #include "split-index.h"
-#include "sigchain.h"
 #include "utf8.h"
 
 static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
index f38dee4f605df344315e0202487aeebc623f6582..e205dd2f689ee5aeafe6ea66343fd01b85d2e354 100644 (file)
@@ -9,6 +9,10 @@
 #include "tag.h"
 #include "quote.h"
 #include "ref-filter.h"
+#include "revision.h"
+#include "utf8.h"
+#include "git-compat-util.h"
+#include "version.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -43,15 +47,48 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
-       { "contents:subject" },
-       { "contents:body" },
-       { "contents:signature" },
        { "upstream" },
        { "push" },
        { "symref" },
        { "flag" },
        { "HEAD" },
        { "color" },
+       { "align" },
+       { "end" },
+};
+
+#define REF_FORMATTING_STATE_INIT  { 0, NULL }
+
+struct align {
+       align_type position;
+       unsigned int width;
+};
+
+struct contents {
+       unsigned int lines;
+       struct object_id oid;
+};
+
+struct ref_formatting_stack {
+       struct ref_formatting_stack *prev;
+       struct strbuf output;
+       void (*at_end)(struct ref_formatting_stack *stack);
+       void *at_end_data;
+};
+
+struct ref_formatting_state {
+       int quote_style;
+       struct ref_formatting_stack *stack;
+};
+
+struct atom_value {
+       const char *s;
+       union {
+               struct align align;
+               struct contents contents;
+       } u;
+       void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
+       unsigned long ul; /* used for sorting when not FIELD_STR */
 };
 
 /*
@@ -123,6 +160,120 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
        return at;
 }
 
+static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
+{
+       switch (quote_style) {
+       case QUOTE_NONE:
+               strbuf_addstr(s, str);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_buf(s, str);
+               break;
+       case QUOTE_PERL:
+               perl_quote_buf(s, str);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_buf(s, str);
+               break;
+       case QUOTE_TCL:
+               tcl_quote_buf(s, str);
+               break;
+       }
+}
+
+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+{
+       /*
+        * Quote formatting is only done when the stack has a single
+        * element. Otherwise quote formatting is done on the
+        * element's entire output strbuf when the %(end) atom is
+        * encountered.
+        */
+       if (!state->stack->prev)
+               quote_formatting(&state->stack->output, v->s, state->quote_style);
+       else
+               strbuf_addstr(&state->stack->output, v->s);
+}
+
+static void push_stack_element(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));
+
+       strbuf_init(&s->output, 0);
+       s->prev = *stack;
+       *stack = s;
+}
+
+static void pop_stack_element(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *current = *stack;
+       struct ref_formatting_stack *prev = current->prev;
+
+       if (prev)
+               strbuf_addbuf(&prev->output, &current->output);
+       strbuf_release(&current->output);
+       free(current);
+       *stack = prev;
+}
+
+static void end_align_handler(struct ref_formatting_stack *stack)
+{
+       struct align *align = (struct align *)stack->at_end_data;
+       struct strbuf s = STRBUF_INIT;
+
+       strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
+       strbuf_swap(&stack->output, &s);
+       strbuf_release(&s);
+}
+
+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *new;
+
+       push_stack_element(&state->stack);
+       new = state->stack;
+       new->at_end = end_align_handler;
+       new->at_end_data = &atomv->u.align;
+}
+
+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *current = state->stack;
+       struct strbuf s = STRBUF_INIT;
+
+       if (!current->at_end)
+               die(_("format: %%(end) atom used without corresponding atom"));
+       current->at_end(current);
+
+       /*
+        * Perform quote formatting when the stack element is that of
+        * a supporting atom. If nested then perform quote formatting
+        * only on the topmost supporting atom.
+        */
+       if (!state->stack->prev->prev) {
+               quote_formatting(&s, current->output.buf, state->quote_style);
+               strbuf_swap(&current->output, &s);
+       }
+       strbuf_release(&s);
+       pop_stack_element(&state->stack);
+}
+
+static int match_atom_name(const char *name, const char *atom_name, const char **val)
+{
+       const char *body;
+
+       if (!skip_prefix(name, atom_name, &body))
+               return 0; /* doesn't even begin with "atom_name" */
+       if (!body[0]) {
+               *val = NULL; /* %(atom_name) and no customization */
+               return 1;
+       }
+       if (body[0] != ':')
+               return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */
+       *val = body + 1; /* "atom_name:val" */
+       return 1;
+}
+
 /*
  * In a format string, find the next occurrence of %(atom).
  */
@@ -192,9 +343,7 @@ static int grab_objectname(const char *name, const unsigned char *sha1,
                            struct atom_value *v)
 {
        if (!strcmp(name, "objectname")) {
-               char *s = xmalloc(41);
-               strcpy(s, sha1_to_hex(sha1));
-               v->s = s;
+               v->s = xstrdup(sha1_to_hex(sha1));
                return 1;
        }
        if (!strcmp(name, "objectname:short")) {
@@ -219,10 +368,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object
                if (!strcmp(name, "objecttype"))
                        v->s = typename(obj->type);
                else if (!strcmp(name, "objectsize")) {
-                       char *s = xmalloc(40);
-                       sprintf(s, "%lu", sz);
                        v->ul = sz;
-                       v->s = s;
+                       v->s = xstrfmt("%lu", sz);
                }
                else if (deref)
                        grab_objectname(name, obj->sha1, v);
@@ -246,11 +393,8 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
                        v->s = tag->tag;
                else if (!strcmp(name, "type") && tag->tagged)
                        v->s = typename(tag->tagged->type);
-               else if (!strcmp(name, "object") && tag->tagged) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
-                       v->s = s;
-               }
+               else if (!strcmp(name, "object") && tag->tagged)
+                       v->s = xstrdup(sha1_to_hex(tag->tagged->sha1));
        }
 }
 
@@ -268,32 +412,22 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
                if (deref)
                        name++;
                if (!strcmp(name, "tree")) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
-                       v->s = s;
+                       v->s = xstrdup(sha1_to_hex(commit->tree->object.sha1));
                }
-               if (!strcmp(name, "numparent")) {
-                       char *s = xmalloc(40);
+               else if (!strcmp(name, "numparent")) {
                        v->ul = commit_list_count(commit->parents);
-                       sprintf(s, "%lu", v->ul);
-                       v->s = s;
+                       v->s = xstrfmt("%lu", v->ul);
                }
                else if (!strcmp(name, "parent")) {
-                       int num = commit_list_count(commit->parents);
-                       int i;
                        struct commit_list *parents;
-                       char *s = xmalloc(41 * num + 1);
-                       v->s = s;
-                       for (i = 0, parents = commit->parents;
-                            parents;
-                            parents = parents->next, i = i + 41) {
+                       struct strbuf s = STRBUF_INIT;
+                       for (parents = commit->parents; parents; parents = parents->next) {
                                struct commit *parent = parents->item;
-                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
-                               if (parents->next)
-                                       s[i+40] = ' ';
+                               if (parents != commit->parents)
+                                       strbuf_addch(&s, ' ');
+                               strbuf_addstr(&s, sha1_to_hex(parent->object.sha1));
                        }
-                       if (!i)
-                               *s = '\0';
+                       v->s = strbuf_detach(&s, NULL);
                }
        }
 }
@@ -497,6 +631,30 @@ static void find_subpos(const char *buf, unsigned long sz,
        *nonsiglen = *sig - buf;
 }
 
+/*
+ * If 'lines' is greater than 0, append that many lines from the given
+ * 'buf' of length 'size' to the given strbuf.
+ */
+static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)
+{
+       int i;
+       const char *sp, *eol;
+       size_t len;
+
+       sp = buf;
+
+       for (i = 0; i < lines && sp < buf + size; i++) {
+               if (i)
+                       strbuf_addstr(out, "\n    ");
+               eol = memchr(sp, '\n', size - (sp - buf));
+               len = eol ? eol - sp : size - (sp - buf);
+               strbuf_add(out, sp, len);
+               if (!eol)
+                       break;
+               sp = eol + 1;
+       }
+}
+
 /* See grab_values */
 static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 {
@@ -507,6 +665,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
                struct atom_value *v = &val[i];
+               const char *valp = NULL;
                if (!!deref != (*name == '*'))
                        continue;
                if (deref)
@@ -516,7 +675,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                    strcmp(name, "contents") &&
                    strcmp(name, "contents:subject") &&
                    strcmp(name, "contents:body") &&
-                   strcmp(name, "contents:signature"))
+                   strcmp(name, "contents:signature") &&
+                   !starts_with(name, "contents:lines="))
                        continue;
                if (!subpos)
                        find_subpos(buf, sz,
@@ -536,6 +696,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
+               else if (skip_prefix(name, "contents:lines=", &valp)) {
+                       struct strbuf s = STRBUF_INIT;
+                       const char *contents_end = bodylen + bodypos - siglen;
+
+                       if (strtoul_ui(valp, 10, &v->u.contents.lines))
+                               die(_("positive value expected contents:lines=%s"), valp);
+                       /*  Size is the length of the message after removing the signature */
+                       append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);
+                       v->s = strbuf_detach(&s, NULL);
+               }
        }
 }
 
@@ -621,8 +791,11 @@ static void populate_value(struct ref_array_item *ref)
                int deref = 0;
                const char *refname;
                const char *formatp;
+               const char *valp;
                struct branch *branch = NULL;
 
+               v->handler = append_atom;
+
                if (*name == '*') {
                        deref = 1;
                        name++;
@@ -653,10 +826,12 @@ static void populate_value(struct ref_array_item *ref)
                        refname = branch_get_push(branch, NULL);
                        if (!refname)
                                continue;
-               } else if (starts_with(name, "color:")) {
+               } else if (match_atom_name(name, "color", &valp)) {
                        char color[COLOR_MAXLEN] = "";
 
-                       if (color_parse(name + 6, color) < 0)
+                       if (!valp)
+                               die(_("expected format: %%(color:<color>)"));
+                       if (color_parse(valp, color) < 0)
                                die(_("unable to parse format"));
                        v->s = xstrdup(color);
                        continue;
@@ -686,6 +861,48 @@ static void populate_value(struct ref_array_item *ref)
                        else
                                v->s = " ";
                        continue;
+               } else if (match_atom_name(name, "align", &valp)) {
+                       struct align *align = &v->u.align;
+                       struct strbuf **s, **to_free;
+                       int width = -1;
+
+                       if (!valp)
+                               die(_("expected format: %%(align:<width>,<position>)"));
+
+                       /*
+                        * TODO: Implement a function similar to strbuf_split_str()
+                        * which would omit the separator from the end of each value.
+                        */
+                       s = to_free = strbuf_split_str(valp, ',', 0);
+
+                       align->position = ALIGN_LEFT;
+
+                       while (*s) {
+                               /*  Strip trailing comma */
+                               if (s[1])
+                                       strbuf_setlen(s[0], s[0]->len - 1);
+                               if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))
+                                       ;
+                               else if (!strcmp(s[0]->buf, "left"))
+                                       align->position = ALIGN_LEFT;
+                               else if (!strcmp(s[0]->buf, "right"))
+                                       align->position = ALIGN_RIGHT;
+                               else if (!strcmp(s[0]->buf, "middle"))
+                                       align->position = ALIGN_MIDDLE;
+                               else
+                                       die(_("improper format entered align:%s"), s[0]->buf);
+                               s++;
+                       }
+
+                       if (width < 0)
+                               die(_("positive width expected with the %%(align) atom"));
+                       align->width = width;
+                       strbuf_list_free(to_free);
+                       v->handler = align_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "end")) {
+                       v->handler = end_atom_handler;
+                       continue;
                } else
                        continue;
 
@@ -700,7 +917,6 @@ static void populate_value(struct ref_array_item *ref)
                        else if (!strcmp(formatp, "track") &&
                                 (starts_with(name, "upstream") ||
                                  starts_with(name, "push"))) {
-                               char buf[40];
 
                                if (stat_tracking_info(branch, &num_ours,
                                                       &num_theirs, NULL))
@@ -708,17 +924,13 @@ static void populate_value(struct ref_array_item *ref)
 
                                if (!num_ours && !num_theirs)
                                        v->s = "";
-                               else if (!num_ours) {
-                                       sprintf(buf, "[behind %d]", num_theirs);
-                                       v->s = xstrdup(buf);
-                               } else if (!num_theirs) {
-                                       sprintf(buf, "[ahead %d]", num_ours);
-                                       v->s = xstrdup(buf);
-                               } else {
-                                       sprintf(buf, "[ahead %d, behind %d]",
-                                               num_ours, num_theirs);
-                                       v->s = xstrdup(buf);
-                               }
+                               else if (!num_ours)
+                                       v->s = xstrfmt("[behind %d]", num_theirs);
+                               else if (!num_theirs)
+                                       v->s = xstrfmt("[ahead %d]", num_ours);
+                               else
+                                       v->s = xstrfmt("[ahead %d, behind %d]",
+                                                      num_ours, num_theirs);
                                continue;
                        } else if (!strcmp(formatp, "trackshort") &&
                                   (starts_with(name, "upstream") ||
@@ -745,12 +957,8 @@ static void populate_value(struct ref_array_item *ref)
 
                if (!deref)
                        v->s = refname;
-               else {
-                       int len = strlen(refname);
-                       char *s = xmalloc(len + 4);
-                       sprintf(s, "%s^{}", refname);
-                       v->s = s;
-               }
+               else
+                       v->s = xstrfmt("%s^{}", refname);
        }
 
        for (i = 0; i < used_atom_cnt; i++) {
@@ -817,11 +1025,143 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
        *v = &ref->value[atom];
 }
 
+enum contains_result {
+       CONTAINS_UNKNOWN = -1,
+       CONTAINS_NO = 0,
+       CONTAINS_YES = 1
+};
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct contains_stack {
+       int nr, alloc;
+       struct contains_stack_entry {
+               struct commit *commit;
+               struct commit_list *parents;
+       } *contains_stack;
+};
+
+static int in_commit_list(const struct commit_list *want, struct commit *c)
+{
+       for (; want; want = want->next)
+               if (!hashcmp(want->item->object.sha1, c->object.sha1))
+                       return 1;
+       return 0;
+}
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
+                           const struct commit_list *want)
+{
+       /* was it previously marked as containing a want commit? */
+       if (candidate->object.flags & TMP_MARK)
+               return 1;
+       /* or marked as not possibly containing a want commit? */
+       if (candidate->object.flags & UNINTERESTING)
+               return 0;
+       /* or are we it? */
+       if (in_commit_list(want, candidate)) {
+               candidate->object.flags |= TMP_MARK;
+               return 1;
+       }
+
+       if (parse_commit(candidate) < 0)
+               return 0;
+
+       return -1;
+}
+
+static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
+{
+       ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
+       contains_stack->contains_stack[contains_stack->nr].commit = candidate;
+       contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
+}
+
+static enum contains_result contains_tag_algo(struct commit *candidate,
+               const struct commit_list *want)
+{
+       struct contains_stack contains_stack = { 0, 0, NULL };
+       int result = contains_test(candidate, want);
+
+       if (result != CONTAINS_UNKNOWN)
+               return result;
+
+       push_to_contains_stack(candidate, &contains_stack);
+       while (contains_stack.nr) {
+               struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
+               struct commit *commit = entry->commit;
+               struct commit_list *parents = entry->parents;
+
+               if (!parents) {
+                       commit->object.flags |= UNINTERESTING;
+                       contains_stack.nr--;
+               }
+               /*
+                * If we just popped the stack, parents->item has been marked,
+                * therefore contains_test will return a meaningful 0 or 1.
+                */
+               else switch (contains_test(parents->item, want)) {
+               case CONTAINS_YES:
+                       commit->object.flags |= TMP_MARK;
+                       contains_stack.nr--;
+                       break;
+               case CONTAINS_NO:
+                       entry->parents = parents->next;
+                       break;
+               case CONTAINS_UNKNOWN:
+                       push_to_contains_stack(parents->item, &contains_stack);
+                       break;
+               }
+       }
+       free(contains_stack.contains_stack);
+       return contains_test(candidate, want);
+}
+
+static int commit_contains(struct ref_filter *filter, struct commit *commit)
+{
+       if (filter->with_commit_tag_algo)
+               return contains_tag_algo(commit, filter->with_commit);
+       return is_descendant_of(commit, filter->with_commit);
+}
+
+/*
+ * Return 1 if the refname matches one of the patterns, otherwise 0.
+ * A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
+ * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
+ * matches "refs/heads/mas*", too).
+ */
+static int match_pattern(const char **patterns, const char *refname)
+{
+       /*
+        * When no '--format' option is given we need to skip the prefix
+        * for matching refs of tags and branches.
+        */
+       (void)(skip_prefix(refname, "refs/tags/", &refname) ||
+              skip_prefix(refname, "refs/heads/", &refname) ||
+              skip_prefix(refname, "refs/remotes/", &refname) ||
+              skip_prefix(refname, "refs/", &refname));
+
+       for (; *patterns; patterns++) {
+               if (!wildmatch(*patterns, refname, 0, NULL))
+                       return 1;
+       }
+       return 0;
+}
+
 /*
  * Return 1 if the refname matches one of the patterns, otherwise 0.
  * A pattern can be path prefix (e.g. a refname "refs/heads/master"
- * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref
- * matches "refs/heads/m*",too).
+ * matches a pattern "refs/heads/" but not "refs/heads/m") or a
+ * wildcard (e.g. the same ref matches "refs/heads/m*", too).
  */
 static int match_name_as_path(const char **pattern, const char *refname)
 {
@@ -842,6 +1182,48 @@ static int match_name_as_path(const char **pattern, const char *refname)
        return 0;
 }
 
+/* Return 1 if the refname matches one of the patterns, otherwise 0. */
+static int filter_pattern_match(struct ref_filter *filter, const char *refname)
+{
+       if (!*filter->name_patterns)
+               return 1; /* No pattern always matches */
+       if (filter->match_as_path)
+               return match_name_as_path(filter->name_patterns, refname);
+       return match_pattern(filter->name_patterns, refname);
+}
+
+/*
+ * Given a ref (sha1, refname), check if the ref belongs to the array
+ * of sha1s. If the given ref is a tag, check if the given tag points
+ * at one of the sha1s in the given sha1 array.
+ * the given sha1_array.
+ * NEEDSWORK:
+ * 1. Only a single level of inderection is obtained, we might want to
+ * change this to account for multiple levels (e.g. annotated tags
+ * pointing to annotated tags pointing to a commit.)
+ * 2. As the refs are cached we might know what refname peels to without
+ * the need to parse the object via parse_object(). peel_ref() might be a
+ * more efficient alternative to obtain the pointee.
+ */
+static const unsigned char *match_points_at(struct sha1_array *points_at,
+                                           const unsigned char *sha1,
+                                           const char *refname)
+{
+       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;
+}
+
 /* Allocate space for a new ref_array_item and copy the objectname and flag to it */
 static struct ref_array_item *new_ref_array_item(const char *refname,
                                                 const unsigned char *objectname,
@@ -857,6 +1239,34 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
        return ref;
 }
 
+static int filter_ref_kind(struct ref_filter *filter, const char *refname)
+{
+       unsigned int i;
+
+       static struct {
+               const char *prefix;
+               unsigned int kind;
+       } ref_kind[] = {
+               { "refs/heads/" , FILTER_REFS_BRANCHES },
+               { "refs/remotes/" , FILTER_REFS_REMOTES },
+               { "refs/tags/", FILTER_REFS_TAGS}
+       };
+
+       if (filter->kind == FILTER_REFS_BRANCHES ||
+           filter->kind == FILTER_REFS_REMOTES ||
+           filter->kind == FILTER_REFS_TAGS)
+               return filter->kind;
+       else if (!strcmp(refname, "HEAD"))
+               return FILTER_REFS_DETACHED_HEAD;
+
+       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+               if (starts_with(refname, ref_kind[i].prefix))
+                       return ref_kind[i].kind;
+       }
+
+       return FILTER_REFS_OTHERS;
+}
+
 /*
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
@@ -866,6 +1276,8 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
        struct ref_filter_cbdata *ref_cbdata = cb_data;
        struct ref_filter *filter = ref_cbdata->filter;
        struct ref_array_item *ref;
+       struct commit *commit = NULL;
+       unsigned int kind;
 
        if (flag & REF_BAD_NAME) {
                warning("ignoring ref with broken name %s", refname);
@@ -877,18 +1289,43 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
                return 0;
        }
 
-       if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
+       /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
+       kind = filter_ref_kind(filter, refname);
+       if (!(kind & filter->kind))
+               return 0;
+
+       if (!filter_pattern_match(filter, refname))
+               return 0;
+
+       if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
                return 0;
 
+       /*
+        * A merge filter is applied on refs pointing to commits. Hence
+        * obtain the commit using the 'oid' available and discard all
+        * non-commits early. The actual filtering is done later.
+        */
+       if (filter->merge_commit || filter->with_commit || filter->verbose) {
+               commit = lookup_commit_reference_gently(oid->hash, 1);
+               if (!commit)
+                       return 0;
+               /* We perform the filtering for the '--contains' option */
+               if (filter->with_commit &&
+                   !commit_contains(filter, commit))
+                       return 0;
+       }
+
        /*
         * We do not open the object yet; sort may only need refname
         * to do its job and the resulting list may yet to be pruned
         * by maxcount logic.
         */
        ref = new_ref_array_item(refname, oid->hash, flag);
+       ref->commit = commit;
 
        REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
        ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
+       ref->kind = kind;
        return 0;
 }
 
@@ -911,6 +1348,50 @@ void ref_array_clear(struct ref_array *array)
        array->nr = array->alloc = 0;
 }
 
+static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
+{
+       struct rev_info revs;
+       int i, old_nr;
+       struct ref_filter *filter = ref_cbdata->filter;
+       struct ref_array *array = ref_cbdata->array;
+       struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
+
+       init_revisions(&revs, NULL);
+
+       for (i = 0; i < array->nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               add_pending_object(&revs, &item->commit->object, item->refname);
+               to_clear[i] = item->commit;
+       }
+
+       filter->merge_commit->object.flags |= UNINTERESTING;
+       add_pending_object(&revs, &filter->merge_commit->object, "");
+
+       revs.limited = 1;
+       if (prepare_revision_walk(&revs))
+               die(_("revision walk setup failed"));
+
+       old_nr = array->nr;
+       array->nr = 0;
+
+       for (i = 0; i < old_nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               struct commit *commit = item->commit;
+
+               int is_merged = !!(commit->object.flags & UNINTERESTING);
+
+               if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
+                       array->items[array->nr++] = array->items[i];
+               else
+                       free_array_item(item);
+       }
+
+       for (i = 0; i < old_nr; i++)
+               clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
+       clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
+       free(to_clear);
+}
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -920,17 +1401,44 @@ void ref_array_clear(struct ref_array *array)
 int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
 {
        struct ref_filter_cbdata ref_cbdata;
+       int ret = 0;
+       unsigned int broken = 0;
 
        ref_cbdata.array = array;
        ref_cbdata.filter = filter;
 
-       if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
-               return for_each_rawref(ref_filter_handler, &ref_cbdata);
-       else if (type & FILTER_REFS_ALL)
-               return for_each_ref(ref_filter_handler, &ref_cbdata);
-       else
+       if (type & FILTER_REFS_INCLUDE_BROKEN)
+               broken = 1;
+       filter->kind = type & FILTER_REFS_KIND_MASK;
+
+       /*  Simple per-ref filtering */
+       if (!filter->kind)
                die("filter_refs: invalid type");
-       return 0;
+       else {
+               /*
+                * For common cases where we need only branches or remotes or tags,
+                * we only iterate through those refs. If a mix of refs is needed,
+                * we iterate over all refs and filter out required refs with the help
+                * of filter_ref_kind().
+                */
+               if (filter->kind == FILTER_REFS_BRANCHES)
+                       ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind == FILTER_REFS_REMOTES)
+                       ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind == FILTER_REFS_TAGS)
+                       ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind & FILTER_REFS_ALL)
+                       ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken);
+               if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
+                       head_ref(ref_filter_handler, &ref_cbdata);
+       }
+
+
+       /*  Filters that need revision walking */
+       if (filter->merge_commit)
+               do_merge_filter(&ref_cbdata);
+
+       return ret;
 }
 
 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
@@ -941,19 +1449,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
 
        get_ref_atom_value(a, s->atom, &va);
        get_ref_atom_value(b, s->atom, &vb);
-       switch (cmp_type) {
-       case FIELD_STR:
+       if (s->version)
+               cmp = versioncmp(va->s, vb->s);
+       else if (cmp_type == FIELD_STR)
                cmp = strcmp(va->s, vb->s);
-               break;
-       default:
+       else {
                if (va->ul < vb->ul)
                        cmp = -1;
                else if (va->ul == vb->ul)
-                       cmp = 0;
+                       cmp = strcmp(a->refname, b->refname);
                else
                        cmp = 1;
-               break;
        }
+
        return (s->reverse) ? -cmp : cmp;
 }
 
@@ -978,32 +1486,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
        qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
 }
 
-static void print_value(struct atom_value *v, int quote_style)
-{
-       struct strbuf sb = STRBUF_INIT;
-       switch (quote_style) {
-       case QUOTE_NONE:
-               fputs(v->s, stdout);
-               break;
-       case QUOTE_SHELL:
-               sq_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PERL:
-               perl_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PYTHON:
-               python_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_TCL:
-               tcl_quote_buf(&sb, v->s);
-               break;
-       }
-       if (quote_style != QUOTE_NONE) {
-               fputs(sb.buf, stdout);
-               strbuf_release(&sb);
-       }
-}
-
 static int hex1(char ch)
 {
        if ('0' <= ch && ch <= '9')
@@ -1022,8 +1504,10 @@ static int hex2(const char *cp)
                return -1;
 }
 
-static void emit(const char *cp, const char *ep)
+static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
 {
+       struct strbuf *s = &state->stack->output;
+
        while (*cp && (!ep || cp < ep)) {
                if (*cp == '%') {
                        if (cp[1] == '%')
@@ -1031,13 +1515,13 @@ static void emit(const char *cp, const char *ep)
                        else {
                                int ch = hex2(cp + 1);
                                if (0 <= ch) {
-                                       putchar(ch);
+                                       strbuf_addch(s, ch);
                                        cp += 3;
                                        continue;
                                }
                        }
                }
-               putchar(*cp);
+               strbuf_addch(s, *cp);
                cp++;
        }
 }
@@ -1045,19 +1529,24 @@ static void emit(const char *cp, const char *ep)
 void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
 {
        const char *cp, *sp, *ep;
+       struct strbuf *final_buf;
+       struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
+
+       state.quote_style = quote_style;
+       push_stack_element(&state.stack);
 
        for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
                struct atom_value *atomv;
 
                ep = strchr(sp, ')');
                if (cp < sp)
-                       emit(cp, sp);
+                       append_literal(cp, sp, &state);
                get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
-               print_value(atomv, quote_style);
+               atomv->handler(atomv, &state);
        }
        if (*cp) {
                sp = cp + strlen(cp);
-               emit(cp, sp);
+               append_literal(cp, sp, &state);
        }
        if (need_color_reset_at_eol) {
                struct atom_value resetv;
@@ -1066,8 +1555,13 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
                if (color_parse("reset", color) < 0)
                        die("BUG: couldn't parse 'reset' as a color");
                resetv.s = color;
-               print_value(&resetv, quote_style);
+               append_atom(&resetv, &state);
        }
+       if (state.stack->prev)
+               die(_("format: %%(end) atom missing"));
+       final_buf = &state.stack->output;
+       fwrite(final_buf->buf, 1, final_buf->len, stdout);
+       pop_stack_element(&state.stack);
        putchar('\n');
 }
 
@@ -1100,7 +1594,29 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
                s->reverse = 1;
                arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
        len = strlen(arg);
        s->atom = parse_ref_filter_atom(arg, arg+len);
        return 0;
 }
+
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_filter *rf = opt->value;
+       unsigned char sha1[20];
+
+       rf->merge = starts_with(opt->long_name, "no")
+               ? REF_FILTER_MERGED_OMIT
+               : REF_FILTER_MERGED_INCLUDE;
+
+       if (get_sha1(arg, sha1))
+               die(_("malformed object name %s"), arg);
+
+       rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
+       if (!rf->merge_commit)
+               return opterror(opt, "must point to a commit", 0);
+
+       return 0;
+}
index 699798400b329a12bc5dfa09590853a18616f876..14d435e2ccf0204312d1f72555f1c2c36fabd215 100644 (file)
 #define QUOTE_PYTHON 4
 #define QUOTE_TCL 8
 
-#define FILTER_REFS_INCLUDE_BROKEN 0x1
-#define FILTER_REFS_ALL 0x2
+#define FILTER_REFS_INCLUDE_BROKEN 0x0001
+#define FILTER_REFS_TAGS           0x0002
+#define FILTER_REFS_BRANCHES       0x0004
+#define FILTER_REFS_REMOTES        0x0008
+#define FILTER_REFS_OTHERS         0x0010
+#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
+                                   FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
+#define FILTER_REFS_DETACHED_HEAD  0x0020
+#define FILTER_REFS_KIND_MASK      (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)
 
-struct atom_value {
-       const char *s;
-       unsigned long ul; /* used for sorting when not FIELD_STR */
-};
+struct atom_value;
 
 struct ref_sorting {
        struct ref_sorting *next;
        int atom; /* index into used_atom array (internal) */
-       unsigned reverse : 1;
+       unsigned reverse : 1,
+               version : 1;
 };
 
 struct ref_array_item {
        unsigned char objectname[20];
        int flag;
+       unsigned int kind;
        const char *symref;
+       struct commit *commit;
        struct atom_value *value;
        char refname[FLEX_ARRAY];
 };
@@ -38,10 +45,28 @@ struct ref_array_item {
 struct ref_array {
        int nr, alloc;
        struct ref_array_item **items;
+       struct rev_info *revs;
 };
 
 struct ref_filter {
        const char **name_patterns;
+       struct sha1_array points_at;
+       struct commit_list *with_commit;
+
+       enum {
+               REF_FILTER_MERGED_NONE = 0,
+               REF_FILTER_MERGED_INCLUDE,
+               REF_FILTER_MERGED_OMIT
+       } merge;
+       struct commit *merge_commit;
+
+       unsigned int with_commit_tag_algo : 1,
+               match_as_path : 1,
+               detached : 1;
+       unsigned int kind,
+               lines;
+       int abbrev,
+               verbose;
 };
 
 struct ref_filter_cbdata {
@@ -49,6 +74,15 @@ struct ref_filter_cbdata {
        struct ref_filter *filter;
 };
 
+/*  Macros for checking --merged and --no-merged options */
+#define _OPT_MERGED_NO_MERGED(option, filter, h) \
+       { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \
+         PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \
+         parse_opt_merge_filter, (intptr_t) "HEAD" \
+       }
+#define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h)
+#define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h)
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -70,5 +104,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
 /*  Default sort option based on refname */
 struct ref_sorting *ref_default_sorting(void);
+/*  Function to parse --merged and --no-merged options */
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
 
 #endif /*  REF_FILTER_H  */
index f8e743a23bef06bbf144339dc79f2186ec40805b..85b8a54241048bb2cc037c2c114b49e7c7e7968b 100644 (file)
@@ -56,12 +56,11 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
                }
        }
        if (reflogs->nr == 0) {
-               int len = strlen(ref);
-               char *refname = xmalloc(len + 12);
-               sprintf(refname, "refs/%s", ref);
+               char *refname = xstrfmt("refs/%s", ref);
                for_each_reflog_ent(refname, read_one_reflog, reflogs);
                if (reflogs->nr == 0) {
-                       sprintf(refname, "refs/heads/%s", ref);
+                       free(refname);
+                       refname = xstrfmt("refs/heads/%s", ref);
                        for_each_reflog_ent(refname, read_one_reflog, reflogs);
                }
                free(refname);
diff --git a/refs.c b/refs.c
index 4e15f60d98ea8affdef226bce199935fa694b195..132eff52ca4092eae4c2e1c0f86a8c380f88778a 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -304,6 +304,11 @@ struct ref_entry {
 };
 
 static void read_loose_refs(const char *dirname, struct ref_dir *dir);
+static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
+static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
+                                         const char *dirname, size_t len,
+                                         int incomplete);
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
 
 static struct ref_dir *get_ref_dir(struct ref_entry *entry)
 {
@@ -312,6 +317,24 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
        dir = &entry->u.subdir;
        if (entry->flag & REF_INCOMPLETE) {
                read_loose_refs(entry->name, dir);
+
+               /*
+                * Manually add refs/bisect, which, being
+                * per-worktree, might not appear in the directory
+                * listing for refs/ in the main repo.
+                */
+               if (!strcmp(entry->name, "refs/")) {
+                       int pos = search_ref_dir(dir, "refs/bisect/", 12);
+                       if (pos < 0) {
+                               struct ref_entry *child_entry;
+                               child_entry = create_dir_entry(dir->ref_cache,
+                                                              "refs/bisect/",
+                                                              12, 1);
+                               add_entry_to_dir(dir, child_entry);
+                               read_loose_refs("refs/bisect",
+                                               &child_entry->u.subdir);
+                       }
+               }
                entry->flag &= ~REF_INCOMPLETE;
        }
        return dir;
@@ -1579,16 +1602,15 @@ static int resolve_missing_loose_ref(const char *refname,
 }
 
 /* This function needs to return a meaningful errno on failure */
-static const char *resolve_ref_unsafe_1(const char *refname,
-                                       int resolve_flags,
-                                       unsigned char *sha1,
-                                       int *flags,
-                                       struct strbuf *sb_path)
+static const char *resolve_ref_1(const char *refname,
+                                int resolve_flags,
+                                unsigned char *sha1,
+                                int *flags,
+                                struct strbuf *sb_refname,
+                                struct strbuf *sb_path,
+                                struct strbuf *sb_contents)
 {
        int depth = MAXDEPTH;
-       ssize_t len;
-       char buffer[256];
-       static char refname_buffer[256];
        int bad_name = 0;
 
        if (flags)
@@ -1654,19 +1676,18 @@ static const char *resolve_ref_unsafe_1(const char *refname,
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
-                       len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len < 0) {
+                       strbuf_reset(sb_contents);
+                       if (strbuf_readlink(sb_contents, path, 0) < 0) {
                                if (errno == ENOENT || errno == EINVAL)
                                        /* inconsistent with lstat; retry */
                                        goto stat_ref;
                                else
                                        return NULL;
                        }
-                       buffer[len] = 0;
-                       if (starts_with(buffer, "refs/") &&
-                                       !check_refname_format(buffer, 0)) {
-                               strcpy(refname_buffer, buffer);
-                               refname = refname_buffer;
+                       if (starts_with(sb_contents->buf, "refs/") &&
+                           !check_refname_format(sb_contents->buf, 0)) {
+                               strbuf_swap(sb_refname, sb_contents);
+                               refname = sb_refname->buf;
                                if (flags)
                                        *flags |= REF_ISSYMREF;
                                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
@@ -1695,28 +1716,26 @@ static const char *resolve_ref_unsafe_1(const char *refname,
                        else
                                return NULL;
                }
-               len = read_in_full(fd, buffer, sizeof(buffer)-1);
-               if (len < 0) {
+               strbuf_reset(sb_contents);
+               if (strbuf_read(sb_contents, fd, 256) < 0) {
                        int save_errno = errno;
                        close(fd);
                        errno = save_errno;
                        return NULL;
                }
                close(fd);
-               while (len && isspace(buffer[len-1]))
-                       len--;
-               buffer[len] = '\0';
+               strbuf_rtrim(sb_contents);
 
                /*
                 * Is it a symbolic ref?
                 */
-               if (!starts_with(buffer, "ref:")) {
+               if (!starts_with(sb_contents->buf, "ref:")) {
                        /*
                         * Please note that FETCH_HEAD has a second
                         * line containing other data.
                         */
-                       if (get_sha1_hex(buffer, sha1) ||
-                           (buffer[40] != '\0' && !isspace(buffer[40]))) {
+                       if (get_sha1_hex(sb_contents->buf, sha1) ||
+                           (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) {
                                if (flags)
                                        *flags |= REF_ISBROKEN;
                                errno = EINVAL;
@@ -1731,10 +1750,12 @@ static const char *resolve_ref_unsafe_1(const char *refname,
                }
                if (flags)
                        *flags |= REF_ISSYMREF;
-               buf = buffer + 4;
+               buf = sb_contents->buf + 4;
                while (isspace(*buf))
                        buf++;
-               refname = strcpy(refname_buffer, buf);
+               strbuf_reset(sb_refname);
+               strbuf_addstr(sb_refname, buf);
+               refname = sb_refname->buf;
                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                        hashclr(sha1);
                        return refname;
@@ -1756,10 +1777,15 @@ static const char *resolve_ref_unsafe_1(const char *refname,
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               unsigned char *sha1, int *flags)
 {
+       static struct strbuf sb_refname = STRBUF_INIT;
+       struct strbuf sb_contents = STRBUF_INIT;
        struct strbuf sb_path = STRBUF_INIT;
-       const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
-                                              sha1, flags, &sb_path);
+       const char *ret;
+
+       ret = resolve_ref_1(refname, resolve_flags, sha1, flags,
+                           &sb_refname, &sb_path, &sb_contents);
        strbuf_release(&sb_path);
+       strbuf_release(&sb_contents);
        return ret;
 }
 
@@ -2108,6 +2134,15 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
        return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
 }
 
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+{
+       unsigned int flag = 0;
+
+       if (broken)
+               flag = DO_FOR_EACH_INCLUDE_BROKEN;
+       return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
+}
+
 int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data)
 {
@@ -2190,8 +2225,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 
        if (!has_glob_specials(pattern)) {
                /* Append implied '/' '*' if not present. */
-               if (real_pattern.buf[real_pattern.len - 1] != '/')
-                       strbuf_addch(&real_pattern, '/');
+               strbuf_complete(&real_pattern, '/');
                /* No need to check for '*', there is none. */
                strbuf_addch(&real_pattern, '*');
        }
@@ -2649,6 +2683,8 @@ struct pack_refs_cb_data {
        struct ref_to_prune *ref_to_prune;
 };
 
+static int is_per_worktree_ref(const char *refname);
+
 /*
  * An each_ref_entry_fn that is run over loose references only.  If
  * the loose reference can be packed, add an entry in the packed ref
@@ -2662,6 +2698,10 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
        struct ref_entry *packed_entry;
        int is_tag_ref = starts_with(entry->name, "refs/tags/");
 
+       /* Do not pack per-worktree refs: */
+       if (is_per_worktree_ref(entry->name))
+               return 0;
+
        /* ALWAYS pack tags */
        if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
                return 0;
@@ -2692,7 +2732,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
                int namelen = strlen(entry->name) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
                hashcpy(n->sha1, entry->u.value.oid.hash);
-               strcpy(n->name, entry->name);
+               memcpy(n->name, entry->name, namelen); /* includes NUL */
                n->next = cb->ref_to_prune;
                cb->ref_to_prune = n;
        }
@@ -2856,7 +2896,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 
 static int is_per_worktree_ref(const char *refname)
 {
-       return !strcmp(refname, "HEAD");
+       return !strcmp(refname, "HEAD") ||
+               starts_with(refname, "refs/bisect/");
 }
 
 static int is_pseudoref_syntax(const char *refname)
@@ -3326,10 +3367,10 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
        msglen = msg ? strlen(msg) : 0;
        maxlen = strlen(committer) + msglen + 100;
        logrec = xmalloc(maxlen);
-       len = sprintf(logrec, "%s %s %s\n",
-                     sha1_to_hex(old_sha1),
-                     sha1_to_hex(new_sha1),
-                     committer);
+       len = xsnprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(old_sha1),
+                       sha1_to_hex(new_sha1),
+                       committer);
        if (msglen)
                len += copy_msg(logrec + len - 1, msg) - 1;
 
@@ -3981,10 +4022,10 @@ void ref_transaction_free(struct ref_transaction *transaction)
 static struct ref_update *add_update(struct ref_transaction *transaction,
                                     const char *refname)
 {
-       size_t len = strlen(refname);
-       struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+       size_t len = strlen(refname) + 1;
+       struct ref_update *update = xcalloc(1, sizeof(*update) + len);
 
-       strcpy((char *)update->refname, refname);
+       memcpy((char *)update->refname, refname, len); /* includes NUL */
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
        transaction->updates[transaction->nr++] = update;
        return update;
diff --git a/refs.h b/refs.h
index e9a5f3230ab09b667e7ae28b2b0bcd26bd2f067e..6d30c980d182f27ab78d43342c0865df7906693c 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -173,6 +173,7 @@ typedef int each_ref_fn(const char *refname,
 extern int head_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
+extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);
 extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
index 71fbbb694fc78471343f568a5e93b8013a7bb9cb..cc7a8a66faa96ba9bc8ae82e72d0202c0a802ba1 100644 (file)
@@ -168,10 +168,7 @@ static struct ref *parse_info_refs(struct discovery *heads)
                                    url.buf);
                        data[i] = 0;
                        ref_name = mid + 1;
-                       ref = xmalloc(sizeof(struct ref) +
-                                     strlen(ref_name) + 1);
-                       memset(ref, 0, sizeof(struct ref));
-                       strcpy(ref->name, ref_name);
+                       ref = alloc_ref(ref_name);
                        get_sha1_hex(start, ref->old_sha1);
                        if (!refs)
                                refs = ref;
index 26504b744786c65ea4d6e1e0abbf5c6409af5358..10f1ffce0b00a7bef0b156678ab7c04431827f0e 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -8,6 +8,7 @@
 #include "tag.h"
 #include "string-list.h"
 #include "mergesort.h"
+#include "argv-array.h"
 
 enum map_direction { FROM_SRC, FROM_DST };
 
@@ -54,9 +55,6 @@ static const char *pushremote_name;
 static struct rewrites rewrites;
 static struct rewrites rewrites_push;
 
-#define BUF_SIZE (2048)
-static char buffer[BUF_SIZE];
-
 static int valid_remote(const struct remote *remote)
 {
        return (!!remote->url) || (!!remote->foreign_vcs);
@@ -65,7 +63,6 @@ static int valid_remote(const struct remote *remote)
 static const char *alias_url(const char *url, struct rewrites *r)
 {
        int i, j;
-       char *ret;
        struct counted_string *longest;
        int longest_i;
 
@@ -86,11 +83,7 @@ static const char *alias_url(const char *url, struct rewrites *r)
        if (!longest)
                return url;
 
-       ret = xmalloc(r->rewrite[longest_i]->baselen +
-                    (strlen(url) - longest->len) + 1);
-       strcpy(ret, r->rewrite[longest_i]->base);
-       strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len);
-       return ret;
+       return xstrfmt("%s%s", r->rewrite[longest_i]->base, url + longest->len);
 }
 
 static void add_push_refspec(struct remote *remote, const char *ref)
@@ -248,106 +241,77 @@ static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
        rewrite->instead_of_nr++;
 }
 
+static const char *skip_spaces(const char *s)
+{
+       while (isspace(*s))
+               s++;
+       return s;
+}
+
 static void read_remotes_file(struct remote *remote)
 {
+       struct strbuf buf = STRBUF_INIT;
        FILE *f = fopen(git_path("remotes/%s", remote->name), "r");
 
        if (!f)
                return;
        remote->origin = REMOTE_REMOTES;
-       while (fgets(buffer, BUF_SIZE, f)) {
-               int value_list;
-               char *s, *p;
-
-               if (starts_with(buffer, "URL:")) {
-                       value_list = 0;
-                       s = buffer + 4;
-               } else if (starts_with(buffer, "Push:")) {
-                       value_list = 1;
-                       s = buffer + 5;
-               } else if (starts_with(buffer, "Pull:")) {
-                       value_list = 2;
-                       s = buffer + 5;
-               } else
-                       continue;
-
-               while (isspace(*s))
-                       s++;
-               if (!*s)
-                       continue;
+       while (strbuf_getline(&buf, f, '\n') != EOF) {
+               const char *v;
 
-               p = s + strlen(s);
-               while (isspace(p[-1]))
-                       *--p = 0;
+               strbuf_rtrim(&buf);
 
-               switch (value_list) {
-               case 0:
-                       add_url_alias(remote, xstrdup(s));
-                       break;
-               case 1:
-                       add_push_refspec(remote, xstrdup(s));
-                       break;
-               case 2:
-                       add_fetch_refspec(remote, xstrdup(s));
-                       break;
-               }
+               if (skip_prefix(buf.buf, "URL:", &v))
+                       add_url_alias(remote, xstrdup(skip_spaces(v)));
+               else if (skip_prefix(buf.buf, "Push:", &v))
+                       add_push_refspec(remote, xstrdup(skip_spaces(v)));
+               else if (skip_prefix(buf.buf, "Pull:", &v))
+                       add_fetch_refspec(remote, xstrdup(skip_spaces(v)));
        }
+       strbuf_release(&buf);
        fclose(f);
 }
 
 static void read_branches_file(struct remote *remote)
 {
        char *frag;
-       struct strbuf branch = STRBUF_INIT;
-       int n = 1000;
-       FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
-       char *s, *p;
-       int len;
+       struct strbuf buf = STRBUF_INIT;
+       FILE *f = fopen(git_path("branches/%s", remote->name), "r");
 
        if (!f)
                return;
-       s = fgets(buffer, BUF_SIZE, f);
+
+       strbuf_getline(&buf, f, '\n');
        fclose(f);
-       if (!s)
-               return;
-       while (isspace(*s))
-               s++;
-       if (!*s)
+       strbuf_trim(&buf);
+       if (!buf.len) {
+               strbuf_release(&buf);
                return;
+       }
+
        remote->origin = REMOTE_BRANCHES;
-       p = s + strlen(s);
-       while (isspace(p[-1]))
-               *--p = 0;
-       len = p - s;
-       p = xmalloc(len + 1);
-       strcpy(p, s);
 
        /*
         * The branches file would have URL and optionally
         * #branch specified.  The "master" (or specified) branch is
-        * fetched and stored in the local branch of the same name.
+        * fetched and stored in the local branch matching the
+        * remote name.
         */
-       frag = strchr(p, '#');
-       if (frag) {
+       frag = strchr(buf.buf, '#');
+       if (frag)
                *(frag++) = '\0';
-               strbuf_addf(&branch, "refs/heads/%s", frag);
-       } else
-               strbuf_addstr(&branch, "refs/heads/master");
+       else
+               frag = "master";
+
+       add_url_alias(remote, strbuf_detach(&buf, NULL));
+       add_fetch_refspec(remote, xstrfmt("refs/heads/%s:refs/heads/%s",
+                                         frag, remote->name));
 
-       strbuf_addf(&branch, ":refs/heads/%s", remote->name);
-       add_url_alias(remote, p);
-       add_fetch_refspec(remote, strbuf_detach(&branch, NULL));
        /*
         * Cogito compatible push: push current HEAD to remote #branch
         * (master if missing)
         */
-       strbuf_init(&branch, 0);
-       strbuf_addstr(&branch, "HEAD");
-       if (frag)
-               strbuf_addf(&branch, ":refs/heads/%s", frag);
-       else
-               strbuf_addstr(&branch, ":refs/heads/master");
-       add_push_refspec(remote, strbuf_detach(&branch, NULL));
+       add_push_refspec(remote, xstrfmt("HEAD:refs/heads/%s", frag));
        remote->fetch_tags = 1; /* always auto-follow */
 }
 
@@ -1975,10 +1939,8 @@ int resolve_remote_symref(struct ref *ref, struct ref *list)
 static void unmark_and_free(struct commit_list *list, unsigned int mark)
 {
        while (list) {
-               struct commit_list *temp = list;
-               temp->item->object.flags &= ~mark;
-               list = temp->next;
-               free(temp);
+               struct commit *commit = pop_commit(&list);
+               commit->object.flags &= ~mark;
        }
 }
 
@@ -2035,10 +1997,9 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 {
        unsigned char sha1[20];
        struct commit *ours, *theirs;
-       char symmetric[84];
        struct rev_info revs;
-       const char *rev_argv[10], *base;
-       int rev_argc;
+       const char *base;
+       struct argv_array argv = ARGV_ARRAY_INIT;
 
        /* Cannot stat unless we are marked to build on top of somebody else. */
        base = branch_get_upstream(branch, NULL);
@@ -2067,19 +2028,15 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
        }
 
        /* Run "rev-list --left-right ours...theirs" internally... */
-       rev_argc = 0;
-       rev_argv[rev_argc++] = NULL;
-       rev_argv[rev_argc++] = "--left-right";
-       rev_argv[rev_argc++] = symmetric;
-       rev_argv[rev_argc++] = "--";
-       rev_argv[rev_argc] = NULL;
-
-       strcpy(symmetric, sha1_to_hex(ours->object.sha1));
-       strcpy(symmetric + 40, "...");
-       strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
+       argv_array_push(&argv, ""); /* ignored */
+       argv_array_push(&argv, "--left-right");
+       argv_array_pushf(&argv, "%s...%s",
+                        sha1_to_hex(ours->object.sha1),
+                        sha1_to_hex(theirs->object.sha1));
+       argv_array_push(&argv, "--");
 
        init_revisions(&revs, NULL);
-       setup_revisions(rev_argc, rev_argv, &revs, NULL);
+       setup_revisions(argv.argc, argv.argv, &revs, NULL);
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
 
@@ -2099,6 +2056,8 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
        /* clear object flags smudged by the above traversal */
        clear_commit_marks(ours, ALL_REV_FLAGS);
        clear_commit_marks(theirs, ALL_REV_FLAGS);
+
+       argv_array_clear(&argv);
        return 0;
 }
 
index d90057b7695918e260e1778a522011dabaa3a37b..403c700c326e4551aea183523af6ed46e1476432 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -20,45 +20,74 @@ static int rerere_enabled = -1;
 /* automatically update cleanly resolved paths to the index */
 static int rerere_autoupdate;
 
-const char *rerere_path(const char *hex, const char *file)
+static void free_rerere_id(struct string_list_item *item)
 {
-       return git_path("rr-cache/%s/%s", hex, file);
+       free(item->util);
 }
 
-static int has_rerere_resolution(const char *hex)
+static const char *rerere_id_hex(const struct rerere_id *id)
+{
+       return id->hex;
+}
+
+const char *rerere_path(const struct rerere_id *id, const char *file)
+{
+       if (!file)
+               return git_path("rr-cache/%s", rerere_id_hex(id));
+
+       return git_path("rr-cache/%s/%s", rerere_id_hex(id), file);
+}
+
+static int has_rerere_resolution(const struct rerere_id *id)
 {
        struct stat st;
-       return !stat(rerere_path(hex, "postimage"), &st);
+
+       return !stat(rerere_path(id, "postimage"), &st);
+}
+
+static struct rerere_id *new_rerere_id_hex(char *hex)
+{
+       struct rerere_id *id = xmalloc(sizeof(*id));
+       strcpy(id->hex, hex);
+       return id;
+}
+
+static struct rerere_id *new_rerere_id(unsigned char *sha1)
+{
+       return new_rerere_id_hex(sha1_to_hex(sha1));
 }
 
+/*
+ * $GIT_DIR/MERGE_RR file is a collection of records, each of which is
+ * "conflict ID", a HT and pathname, terminated with a NUL, and is
+ * used to keep track of the set of paths that "rerere" may need to
+ * work on (i.e. what is left by the previous invocation of "git
+ * rerere" during the current conflict resolution session).
+ */
 static void read_rr(struct string_list *rr)
 {
-       unsigned char sha1[20];
-       char buf[PATH_MAX];
+       struct strbuf buf = STRBUF_INIT;
        FILE *in = fopen(git_path_merge_rr(), "r");
+
        if (!in)
                return;
-       while (fread(buf, 40, 1, in) == 1) {
-               int i;
-               char *name;
-               if (get_sha1_hex(buf, sha1))
+       while (!strbuf_getwholeline(&buf, in, '\0')) {
+               char *path;
+               unsigned char sha1[20];
+               struct rerere_id *id;
+
+               /* There has to be the hash, tab, path and then NUL */
+               if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
                        die("corrupt MERGE_RR");
-               buf[40] = '\0';
-               name = xstrdup(buf);
-               if (fgetc(in) != '\t')
+
+               if (buf.buf[40] != '\t')
                        die("corrupt MERGE_RR");
-               for (i = 0; i < sizeof(buf); i++) {
-                       int c = fgetc(in);
-                       if (c < 0)
-                               die("corrupt MERGE_RR");
-                       buf[i] = c;
-                       if (c == 0)
-                                break;
-               }
-               if (i == sizeof(buf))
-                       die("filename too long");
-               string_list_insert(rr, buf)->util = name;
+               buf.buf[40] = '\0';
+               path = buf.buf + 41;
+               id = new_rerere_id_hex(buf.buf);
+               string_list_insert(rr, path)->util = id;
        }
+       strbuf_release(&buf);
        fclose(in);
 }
 
@@ -68,22 +97,42 @@ static int write_rr(struct string_list *rr, int out_fd)
 {
        int i;
        for (i = 0; i < rr->nr; i++) {
-               const char *path;
-               int length;
-               if (!rr->items[i].util)
+               struct strbuf buf = STRBUF_INIT;
+               struct rerere_id *id;
+
+               assert(rr->items[i].util != RERERE_RESOLVED);
+
+               id = rr->items[i].util;
+               if (!id)
                        continue;
-               path = rr->items[i].string;
-               length = strlen(path) + 1;
-               if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
-                   write_str_in_full(out_fd, "\t") != 1 ||
-                   write_in_full(out_fd, path, length) != length)
+               strbuf_addf(&buf, "%s\t%s%c",
+                           rerere_id_hex(id),
+                           rr->items[i].string, 0);
+               if (write_in_full(out_fd, buf.buf, buf.len) != buf.len)
                        die("unable to write rerere record");
+
+               strbuf_release(&buf);
        }
        if (commit_lock_file(&write_lock) != 0)
                die("unable to write rerere record");
        return 0;
 }
 
+/*
+ * "rerere" interacts with conflicted file contents using this I/O
+ * abstraction.  It reads a conflicted contents from one place via
+ * "getline()" method, and optionally can write it out after
+ * normalizing the conflicted hunks to the "output".  Subclasses of
+ * rerere_io embed this structure at the beginning of their own
+ * rerere_io object.
+ */
+struct rerere_io {
+       int (*getline)(struct strbuf *, struct rerere_io *);
+       FILE *output;
+       int wrerror;
+       /* some more stuff */
+};
+
 static void ferr_write(const void *p, size_t count, FILE *fp, int *err)
 {
        if (!count || *err)
@@ -97,31 +146,34 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
        ferr_write(s, strlen(s), fp, err);
 }
 
-struct rerere_io {
-       int (*getline)(struct strbuf *, struct rerere_io *);
-       FILE *output;
-       int wrerror;
-       /* some more stuff */
-};
-
 static void rerere_io_putstr(const char *str, struct rerere_io *io)
 {
        if (io->output)
                ferr_puts(str, io->output, &io->wrerror);
 }
 
+/*
+ * Write a conflict marker to io->output (if defined).
+ */
 static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
 {
        char buf[64];
 
        while (size) {
-               if (size < sizeof(buf) - 2) {
+               if (size <= sizeof(buf) - 2) {
                        memset(buf, ch, size);
                        buf[size] = '\n';
                        buf[size + 1] = '\0';
                        size = 0;
                } else {
                        int sz = sizeof(buf) - 1;
+
+                       /*
+                        * Make sure we will not write everything out
+                        * in this round by leaving at least 1 byte
+                        * for the next round, giving the next round
+                        * a chance to add the terminating LF.  Yuck.
+                        */
                        if (size <= sz)
                                sz -= (sz - size) + 1;
                        memset(buf, ch, sz);
@@ -138,19 +190,42 @@ static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
                ferr_write(mem, sz, io->output, &io->wrerror);
 }
 
+/*
+ * Subclass of rerere_io that reads from an on-disk file
+ */
 struct rerere_io_file {
        struct rerere_io io;
        FILE *input;
 };
 
+/*
+ * ... and its getline() method implementation
+ */
 static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
 {
        struct rerere_io_file *io = (struct rerere_io_file *)io_;
        return strbuf_getwholeline(sb, io->input, '\n');
 }
 
-static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+/*
+ * Require the exact number of conflict marker letters, no more, no
+ * less, followed by SP or any whitespace
+ * (including LF).
+ */
+static int is_cmarker(char *buf, int marker_char, int marker_size)
 {
+       int want_sp;
+
+       /*
+        * The beginning of our version and the end of their version
+        * always are labeled like "<<<<< ours" or ">>>>> theirs",
+        * hence we set want_sp for them.  Note that the version from
+        * the common ancestor in diff3-style output is not always
+        * labelled (e.g. "||||| common" is often seen but "|||||"
+        * alone is also valid), so we do not set want_sp.
+        */
+       want_sp = (marker_char == '<') || (marker_char == '>');
+
        while (marker_size--)
                if (*buf++ != marker_char)
                        return 0;
@@ -159,6 +234,21 @@ static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
        return isspace(*buf);
 }
 
+/*
+ * Read contents a file with conflicts, normalize the conflicts
+ * by (1) discarding the common ancestor version in diff3-style,
+ * (2) reordering our side and their side so that whichever sorts
+ * alphabetically earlier comes before the other one, while
+ * computing the "conflict ID", which is just an SHA-1 hash of
+ * one side of the conflict, NUL, the other side of the conflict,
+ * and NUL concatenated together.
+ *
+ * Return the number of conflict hunks found.
+ *
+ * NEEDSWORK: the logic and theory of operation behind this conflict
+ * normalization may deserve to be documented somewhere, perhaps in
+ * Documentation/technical/rerere.txt.
+ */
 static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
 {
        git_SHA_CTX ctx;
@@ -173,19 +263,19 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
                git_SHA1_Init(&ctx);
 
        while (!io->getline(&buf, io)) {
-               if (is_cmarker(buf.buf, '<', marker_size, 1)) {
+               if (is_cmarker(buf.buf, '<', marker_size)) {
                        if (hunk != RR_CONTEXT)
                                goto bad;
                        hunk = RR_SIDE_1;
-               } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
+               } else if (is_cmarker(buf.buf, '|', marker_size)) {
                        if (hunk != RR_SIDE_1)
                                goto bad;
                        hunk = RR_ORIGINAL;
-               } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
+               } else if (is_cmarker(buf.buf, '=', marker_size)) {
                        if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
                                goto bad;
                        hunk = RR_SIDE_2;
-               } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
+               } else if (is_cmarker(buf.buf, '>', marker_size)) {
                        if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
@@ -229,6 +319,10 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
        return hunk_no;
 }
 
+/*
+ * Scan the path for conflicts, do the "handle_path()" thing above, and
+ * return the number of conflict hunks found.
+ */
 static int handle_file(const char *path, unsigned char *sha1, const char *output)
 {
        int hunk_no = 0;
@@ -270,11 +364,18 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output
        return hunk_no;
 }
 
+/*
+ * Subclass of rerere_io that reads from an in-core buffer that is a
+ * strbuf
+ */
 struct rerere_io_mem {
        struct rerere_io io;
        struct strbuf input;
 };
 
+/*
+ * ... and its getline() method implementation
+ */
 static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
 {
        struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
@@ -313,24 +414,23 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
                return -1;
        pos = -pos - 1;
 
-       for (i = 0; i < 3; i++) {
+       while (pos < active_nr) {
                enum object_type type;
                unsigned long size;
-               int j;
 
-               if (active_nr <= pos)
-                       break;
                ce = active_cache[pos++];
                if (ce_namelen(ce) != len || memcmp(ce->name, path, len))
-                       continue;
-               j = ce_stage(ce) - 1;
-               mmfile[j].ptr = read_sha1_file(ce->sha1, &type, &size);
-               mmfile[j].size = size;
+                       break;
+               i = ce_stage(ce) - 1;
+               if (!mmfile[i].ptr) {
+                       mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+                       mmfile[i].size = size;
+               }
        }
-       for (i = 0; i < 3; i++) {
+       for (i = 0; i < 3; i++)
                if (!mmfile[i].ptr && !mmfile[i].size)
                        mmfile[i].ptr = xstrdup("");
-       }
+
        /*
         * NEEDSWORK: handle conflicts from merges with
         * merge.renormalize set, too
@@ -350,6 +450,10 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        strbuf_init(&io.input, 0);
        strbuf_attach(&io.input, result.ptr, result.size, result.size);
 
+       /*
+        * Grab the conflict ID and optionally write the original
+        * contents with conflict markers out.
+        */
        hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
        strbuf_release(&io.input);
        if (io.io.output)
@@ -357,6 +461,14 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        return hunk_no;
 }
 
+/*
+ * Look at a cache entry at "i" and see if it is not conflicting,
+ * conflicting and we are willing to handle, or conflicting and
+ * we are unable to handle, and return the determination in *type.
+ * Return the cache index to be looked at next, by skipping the
+ * stages we have already looked at in this invocation of this
+ * function.
+ */
 static int check_one_conflict(int i, int *type)
 {
        const struct cache_entry *e = active_cache[i];
@@ -367,10 +479,8 @@ static int check_one_conflict(int i, int *type)
        }
 
        *type = PUNTED;
-       if (ce_stage(e) == 1) {
-               if (active_nr <= ++i)
-                       return i + 1;
-       }
+       while (ce_stage(active_cache[i]) == 1)
+               i++;
 
        /* Only handle regular files with both stages #2 and #3 */
        if (i + 1 < active_nr) {
@@ -390,6 +500,17 @@ static int check_one_conflict(int i, int *type)
        return i;
 }
 
+/*
+ * Scan the index and find paths that have conflicts that rerere can
+ * handle, i.e. the ones that has both stages #2 and #3.
+ *
+ * NEEDSWORK: we do not record or replay a previous "resolve by
+ * deletion" for a delete-modify conflict, as that is inherently risky
+ * without knowing what modification is being discarded.  The only
+ * safe case, i.e. both side doing the deletion and modification that
+ * are identical to the previous round, might want to be handled,
+ * though.
+ */
 static int find_conflict(struct string_list *conflict)
 {
        int i;
@@ -406,6 +527,21 @@ static int find_conflict(struct string_list *conflict)
        return 0;
 }
 
+/*
+ * The merge_rr list is meant to hold outstanding conflicted paths
+ * that rerere could handle.  Abuse the list by adding other types of
+ * entries to allow the caller to show "rerere remaining".
+ *
+ * - Conflicted paths that rerere does not handle are added
+ * - Conflicted paths that have been resolved are marked as such
+ *   by storing RERERE_RESOLVED to .util field (where conflict ID
+ *   is expected to be stored).
+ *
+ * Do *not* write MERGE_RR file out after calling this function.
+ *
+ * NEEDSWORK: we may want to fix the caller that implements "rerere
+ * remaining" to do this without abusing merge_rr.
+ */
 int rerere_remaining(struct string_list *merge_rr)
 {
        int i;
@@ -424,7 +560,7 @@ int rerere_remaining(struct string_list *merge_rr)
                        struct string_list_item *it;
                        it = string_list_lookup(merge_rr, (const char *)e->name);
                        if (it != NULL) {
-                               free(it->util);
+                               free_rerere_id(it);
                                it->util = RERERE_RESOLVED;
                        }
                }
@@ -432,39 +568,66 @@ int rerere_remaining(struct string_list *merge_rr)
        return 0;
 }
 
-static int merge(const char *name, const char *path)
+/*
+ * Find the conflict identified by "id"; the change between its
+ * "preimage" (i.e. a previous contents with conflict markers) and its
+ * "postimage" (i.e. the corresponding contents with conflicts
+ * resolved) may apply cleanly to the contents stored in "path", i.e.
+ * the conflict this time around.
+ *
+ * Returns 0 for successful replay of recorded resolution, or non-zero
+ * for failure.
+ */
+static int merge(const struct rerere_id *id, const char *path)
 {
+       FILE *f;
        int ret;
        mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};
        mmbuffer_t result = {NULL, 0};
 
-       if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
-               return 1;
+       /*
+        * Normalize the conflicts in path and write it out to
+        * "thisimage" temporary file.
+        */
+       if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) {
+               ret = 1;
+               goto out;
+       }
 
-       if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
-                       read_mmfile(&base, rerere_path(name, "preimage")) ||
-                       read_mmfile(&other, rerere_path(name, "postimage"))) {
+       if (read_mmfile(&cur, rerere_path(id, "thisimage")) ||
+           read_mmfile(&base, rerere_path(id, "preimage")) ||
+           read_mmfile(&other, rerere_path(id, "postimage"))) {
                ret = 1;
                goto out;
        }
+
+       /*
+        * A three-way merge. Note that this honors user-customizable
+        * low-level merge driver settings.
+        */
        ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL);
-       if (!ret) {
-               FILE *f;
-
-               if (utime(rerere_path(name, "postimage"), NULL) < 0)
-                       warning("failed utime() on %s: %s",
-                                       rerere_path(name, "postimage"),
-                                       strerror(errno));
-               f = fopen(path, "w");
-               if (!f)
-                       return error("Could not open %s: %s", path,
-                                    strerror(errno));
-               if (fwrite(result.ptr, result.size, 1, f) != 1)
-                       error("Could not write %s: %s", path, strerror(errno));
-               if (fclose(f))
-                       return error("Writing %s failed: %s", path,
-                                    strerror(errno));
-       }
+       if (ret)
+               goto out;
+
+       /*
+        * A successful replay of recorded resolution.
+        * Mark that "postimage" was used to help gc.
+        */
+       if (utime(rerere_path(id, "postimage"), NULL) < 0)
+               warning("failed utime() on %s: %s",
+                       rerere_path(id, "postimage"),
+                       strerror(errno));
+
+       /* Update "path" with the resolution */
+       f = fopen(path, "w");
+       if (!f)
+               return error("Could not open %s: %s", path,
+                            strerror(errno));
+       if (fwrite(result.ptr, result.size, 1, f) != 1)
+               error("Could not write %s: %s", path, strerror(errno));
+       if (fclose(f))
+               return error("Writing %s failed: %s", path,
+                            strerror(errno));
 
 out:
        free(cur.ptr);
@@ -487,6 +650,8 @@ static void update_paths(struct string_list *update)
                struct string_list_item *item = &update->items[i];
                if (add_file_to_cache(item->string, 0))
                        exit(128);
+               fprintf(stderr, "Staged '%s' using previous resolution.\n",
+                       item->string);
        }
 
        if (active_cache_changed) {
@@ -496,6 +661,41 @@ static void update_paths(struct string_list *update)
                rollback_lock_file(&index_lock);
 }
 
+/*
+ * The path indicated by rr_item may still have conflict for which we
+ * have a recorded resolution, in which case replay it and optionally
+ * update it.  Or it may have been resolved by the user and we may
+ * only have the preimage for that conflict, in which case the result
+ * needs to be recorded as a resolution in a postimage file.
+ */
+static void do_rerere_one_path(struct string_list_item *rr_item,
+                              struct string_list *update)
+{
+       const char *path = rr_item->string;
+       const struct rerere_id *id = rr_item->util;
+
+       /* Is there a recorded resolution we could attempt to apply? */
+       if (has_rerere_resolution(id)) {
+               if (merge(id, path))
+                       return; /* failed to replay */
+
+               if (rerere_autoupdate)
+                       string_list_insert(update, path);
+               else
+                       fprintf(stderr,
+                               "Resolved '%s' using previous resolution.\n",
+                               path);
+       } else if (!handle_file(path, NULL, NULL)) {
+               /* The user has resolved it. */
+               copy_file(rerere_path(id, "postimage"), path, 0666);
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+       } else {
+               return;
+       }
+       free_rerere_id(rr_item);
+       rr_item->util = NULL;
+}
+
 static int do_plain_rerere(struct string_list *rr, int fd)
 {
        struct string_list conflict = STRING_LIST_INIT_DUP;
@@ -505,65 +705,55 @@ static int do_plain_rerere(struct string_list *rr, int fd)
        find_conflict(&conflict);
 
        /*
-        * MERGE_RR records paths with conflicts immediately after merge
-        * failed.  Some of the conflicted paths might have been hand resolved
-        * in the working tree since then, but the initial run would catch all
-        * and register their preimages.
+        * MERGE_RR records paths with conflicts immediately after
+        * merge failed.  Some of the conflicted paths might have been
+        * hand resolved in the working tree since then, but the
+        * initial run would catch all and register their preimages.
         */
-
        for (i = 0; i < conflict.nr; i++) {
+               struct rerere_id *id;
+               unsigned char sha1[20];
                const char *path = conflict.items[i].string;
-               if (!string_list_has_string(rr, path)) {
-                       unsigned char sha1[20];
-                       char *hex;
-                       int ret;
-                       ret = handle_file(path, sha1, NULL);
-                       if (ret < 1)
-                               continue;
-                       hex = xstrdup(sha1_to_hex(sha1));
-                       string_list_insert(rr, path)->util = hex;
-                       if (mkdir_in_gitdir(git_path("rr-cache/%s", hex)))
-                               continue;
-                       handle_file(path, NULL, rerere_path(hex, "preimage"));
-                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
-               }
-       }
+               int ret;
 
-       /*
-        * Now some of the paths that had conflicts earlier might have been
-        * hand resolved.  Others may be similar to a conflict already that
-        * was resolved before.
-        */
+               if (string_list_has_string(rr, path))
+                       continue;
 
-       for (i = 0; i < rr->nr; i++) {
-               int ret;
-               const char *path = rr->items[i].string;
-               const char *name = (const char *)rr->items[i].util;
-
-               if (has_rerere_resolution(name)) {
-                       if (!merge(name, path)) {
-                               const char *msg;
-                               if (rerere_autoupdate) {
-                                       string_list_insert(&update, path);
-                                       msg = "Staged '%s' using previous resolution.\n";
-                               } else
-                                       msg = "Resolved '%s' using previous resolution.\n";
-                               fprintf(stderr, msg, path);
-                               goto mark_resolved;
-                       }
-               }
+               /*
+                * Ask handle_file() to scan and assign a
+                * conflict ID.  No need to write anything out
+                * yet.
+                */
+               ret = handle_file(path, sha1, NULL);
+               if (ret < 1)
+                       continue;
 
-               /* Let's see if we have resolved it. */
-               ret = handle_file(path, NULL, NULL);
-               if (ret)
+               id = new_rerere_id(sha1);
+               string_list_insert(rr, path)->util = id;
+
+               /*
+                * If the directory does not exist, create
+                * it.  mkdir_in_gitdir() will fail with
+                * EEXIST if there already is one.
+                *
+                * NEEDSWORK: make sure "gc" does not remove
+                * preimage without removing the directory.
+                */
+               if (mkdir_in_gitdir(rerere_path(id, NULL)))
                        continue;
 
-               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rerere_path(name, "postimage"), path, 0666);
-       mark_resolved:
-               rr->items[i].util = NULL;
+               /*
+                * We are the first to encounter this
+                * conflict.  Ask handle_file() to write the
+                * normalized contents to the "preimage" file.
+                */
+               handle_file(path, NULL, rerere_path(id, "preimage"));
+               fprintf(stderr, "Recorded preimage for '%s'\n", path);
        }
 
+       for (i = 0; i < rr->nr; i++)
+               do_rerere_one_path(&rr->items[i], &update);
+
        if (update.nr)
                update_paths(&update);
 
@@ -614,6 +804,11 @@ int setup_rerere(struct string_list *merge_rr, int flags)
        return fd;
 }
 
+/*
+ * The main entry point that is called internally from codepaths that
+ * perform mergy operations, possibly leaving conflicted index entries
+ * and working tree files.
+ */
 int rerere(int flags)
 {
        struct string_list merge_rr = STRING_LIST_INIT_DUP;
@@ -628,25 +823,42 @@ int rerere(int flags)
 static int rerere_forget_one_path(const char *path, struct string_list *rr)
 {
        const char *filename;
-       char *hex;
+       struct rerere_id *id;
        unsigned char sha1[20];
        int ret;
+       struct string_list_item *item;
 
+       /*
+        * Recreate the original conflict from the stages in the
+        * index and compute the conflict ID
+        */
        ret = handle_cache(path, sha1, NULL);
        if (ret < 1)
                return error("Could not parse conflict hunks in '%s'", path);
-       hex = xstrdup(sha1_to_hex(sha1));
-       filename = rerere_path(hex, "postimage");
+
+       /* Nuke the recorded resolution for the conflict */
+       id = new_rerere_id(sha1);
+       filename = rerere_path(id, "postimage");
        if (unlink(filename))
                return (errno == ENOENT
                        ? error("no remembered resolution for %s", path)
                        : error("cannot unlink %s: %s", filename, strerror(errno)));
 
-       handle_cache(path, sha1, rerere_path(hex, "preimage"));
+       /*
+        * Update the preimage so that the user can resolve the
+        * conflict in the working tree, run us again to record
+        * the postimage.
+        */
+       handle_cache(path, sha1, rerere_path(id, "preimage"));
        fprintf(stderr, "Updated preimage for '%s'\n", path);
 
-
-       string_list_insert(rr, path)->util = hex;
+       /*
+        * And remember that we can record resolution for this
+        * conflict when the user is done.
+        */
+       item = string_list_insert(rr, path);
+       free_rerere_id(item);
+       item->util = id;
        fprintf(stderr, "Forgot resolution for %s\n", path);
        return 0;
 }
@@ -664,6 +876,11 @@ int rerere_forget(struct pathspec *pathspec)
        if (fd < 0)
                return 0;
 
+       /*
+        * The paths may have been resolved (incorrectly);
+        * recover the original conflicted state and then
+        * find the conflicted paths.
+        */
        unmerge_cache(pathspec);
        find_conflict(&conflict);
        for (i = 0; i < conflict.nr; i++) {
@@ -676,24 +893,51 @@ int rerere_forget(struct pathspec *pathspec)
        return write_rr(&merge_rr, fd);
 }
 
-static time_t rerere_created_at(const char *name)
+/*
+ * Garbage collection support
+ */
+
+/*
+ * Note that this is not reentrant but is used only one-at-a-time
+ * so it does not matter right now.
+ */
+static struct rerere_id *dirname_to_id(const char *name)
+{
+       static struct rerere_id id;
+       strcpy(id.hex, name);
+       return &id;
+}
+
+static time_t rerere_created_at(const char *dir_name)
 {
        struct stat st;
-       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+       struct rerere_id *id = dirname_to_id(dir_name);
+
+       return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static time_t rerere_last_used_at(const char *name)
+static time_t rerere_last_used_at(const char *dir_name)
 {
        struct stat st;
-       return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+       struct rerere_id *id = dirname_to_id(dir_name);
+
+       return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static void unlink_rr_item(const char *name)
+/*
+ * Remove the recorded resolution for a given conflict ID
+ */
+static void unlink_rr_item(struct rerere_id *id)
 {
-       unlink(rerere_path(name, "thisimage"));
-       unlink(rerere_path(name, "preimage"));
-       unlink(rerere_path(name, "postimage"));
-       rmdir(git_path("rr-cache/%s", name));
+       unlink(rerere_path(id, "thisimage"));
+       unlink(rerere_path(id, "preimage"));
+       unlink(rerere_path(id, "postimage"));
+       /*
+        * NEEDSWORK: what if this rmdir() fails?  Wouldn't we then
+        * assume that we already have preimage recorded in
+        * do_plain_rerere()?
+        */
+       rmdir(rerere_path(id, NULL));
 }
 
 void rerere_gc(struct string_list *rr)
@@ -715,6 +959,7 @@ void rerere_gc(struct string_list *rr)
        dir = opendir(git_path("rr-cache"));
        if (!dir)
                die_errno("unable to open rr-cache directory");
+       /* Collect stale conflict IDs ... */
        while ((e = readdir(dir))) {
                if (is_dot_or_dotdot(e->d_name))
                        continue;
@@ -732,12 +977,20 @@ void rerere_gc(struct string_list *rr)
                        string_list_append(&to_remove, e->d_name);
        }
        closedir(dir);
+       /* ... and then remove them one-by-one */
        for (i = 0; i < to_remove.nr; i++)
-               unlink_rr_item(to_remove.items[i].string);
+               unlink_rr_item(dirname_to_id(to_remove.items[i].string));
        string_list_clear(&to_remove, 0);
        rollback_lock_file(&write_lock);
 }
 
+/*
+ * During a conflict resolution, after "rerere" recorded the
+ * preimages, abandon them if the user did not resolve them or
+ * record their resolutions.  And drop $GIT_DIR/MERGE_RR.
+ *
+ * NEEDSWORK: shouldn't we be calling this from "reset --hard"?
+ */
 void rerere_clear(struct string_list *merge_rr)
 {
        int i;
@@ -746,9 +999,9 @@ void rerere_clear(struct string_list *merge_rr)
                return;
 
        for (i = 0; i < merge_rr->nr; i++) {
-               const char *name = (const char *)merge_rr->items[i].util;
-               if (!has_rerere_resolution(name))
-                       unlink_rr_item(name);
+               struct rerere_id *id = merge_rr->items[i].util;
+               if (!has_rerere_resolution(id))
+                       unlink_rr_item(id);
        }
        unlink_or_warn(git_path_merge_rr());
        rollback_lock_file(&write_lock);
index 407d59996d97ae307d5b4a124f78ef79666e1feb..1222e91921882b41ac9c1648960eca9305b27d8d 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -16,9 +16,19 @@ struct pathspec;
  */
 extern void *RERERE_RESOLVED;
 
+struct rerere_id {
+       char hex[41];
+};
+
 extern int setup_rerere(struct string_list *, int);
 extern int rerere(int);
-extern const char *rerere_path(const char *hex, const char *file);
+/*
+ * Given the conflict ID and the name of a "file" used for replaying
+ * the recorded resolution (e.g. "preimage", "postimage"), return the
+ * path to that filesystem entity.  With "file" specified with NULL,
+ * return the path to the directory that houses these files.
+ */
+extern const char *rerere_path(const struct rerere_id *, const char *file);
 extern int rerere_forget(struct pathspec *);
 extern int rerere_remaining(struct string_list *);
 extern void rerere_clear(struct string_list *);
index af2a18ed7485ea83170409ce55f81473ee606221..0fbb6841bf14389148474bff7bd702f5ec02e558 100644 (file)
@@ -38,7 +38,7 @@ char *path_name(const struct name_path *path, const char *name)
        }
        n = xmalloc(len);
        m = n + len - (nlen + 1);
-       strcpy(m, name);
+       memcpy(m, name, nlen + 1);
        for (p = path; p; p = p->up) {
                if (p->elem_len) {
                        m -= p->elem_len + 1;
@@ -153,10 +153,7 @@ void mark_parents_uninteresting(struct commit *commit)
                commit_list_insert(l->item, &parents);
 
        while (parents) {
-               struct commit *commit = parents->item;
-               l = parents;
-               parents = parents->next;
-               free(l);
+               struct commit *commit = pop_commit(&parents);
 
                while (commit) {
                        /*
@@ -1102,14 +1099,10 @@ static int limit_list(struct rev_info *revs)
        }
 
        while (list) {
-               struct commit_list *entry = list;
-               struct commit *commit = list->item;
+               struct commit *commit = pop_commit(&list);
                struct object *obj = &commit->object;
                show_early_output_fn_t show;
 
-               list = list->next;
-               free(entry);
-
                if (commit == interesting_cache)
                        interesting_cache = NULL;
 
@@ -2733,10 +2726,7 @@ static void simplify_merges(struct rev_info *revs)
                yet_to_do = NULL;
                tail = &yet_to_do;
                while (list) {
-                       commit = list->item;
-                       next = list->next;
-                       free(list);
-                       list = next;
+                       commit = pop_commit(&list);
                        tail = simplify_one(revs, commit, tail);
                }
        }
@@ -2748,10 +2738,7 @@ static void simplify_merges(struct rev_info *revs)
        while (list) {
                struct merge_simplify_state *st;
 
-               commit = list->item;
-               next = list->next;
-               free(list);
-               list = next;
+               commit = pop_commit(&list);
                st = locate_simplify_state(revs, commit);
                if (st->simplified == commit)
                        tail = &commit_list_insert(commit, tail)->next;
@@ -3125,11 +3112,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
                return NULL;
 
        do {
-               struct commit_list *entry = revs->commits;
-               struct commit *commit = entry->item;
-
-               revs->commits = entry->next;
-               free(entry);
+               struct commit *commit = pop_commit(&revs->commits);
 
                if (revs->reflog_info) {
                        save_parents(revs, commit);
index e09275bd9e360ee7683ef5d3753ccfe753ee8dcc..13fa452e8c3d5a5e20e24a969d53ce7ade4019bf 100644 (file)
@@ -11,6 +11,12 @@ void child_process_init(struct child_process *child)
        argv_array_init(&child->env_array);
 }
 
+void child_process_clear(struct child_process *child)
+{
+       argv_array_clear(&child->args);
+       argv_array_clear(&child->env_array);
+}
+
 struct child_to_clean {
        pid_t pid;
        struct child_to_clean *next;
@@ -327,8 +333,7 @@ int start_command(struct child_process *cmd)
 fail_pipe:
                        error("cannot create %s pipe for %s: %s",
                                str, cmd->argv[0], strerror(failed_errno));
-                       argv_array_clear(&cmd->args);
-                       argv_array_clear(&cmd->env_array);
+                       child_process_clear(cmd);
                        errno = failed_errno;
                        return -1;
                }
@@ -513,8 +518,7 @@ int start_command(struct child_process *cmd)
                        close_pair(fderr);
                else if (cmd->err)
                        close(cmd->err);
-               argv_array_clear(&cmd->args);
-               argv_array_clear(&cmd->env_array);
+               child_process_clear(cmd);
                errno = failed_errno;
                return -1;
        }
@@ -540,8 +544,7 @@ int start_command(struct child_process *cmd)
 int finish_command(struct child_process *cmd)
 {
        int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);
-       argv_array_clear(&cmd->args);
-       argv_array_clear(&cmd->env_array);
+       child_process_clear(cmd);
        return ret;
 }
 
@@ -604,7 +607,7 @@ static NORETURN void die_async(const char *err, va_list params)
 {
        vreportf("fatal: ", err, params);
 
-       if (!pthread_equal(main_thread, pthread_self())) {
+       if (in_async()) {
                struct async *async = pthread_getspecific(async_key);
                if (async->proc_in >= 0)
                        close(async->proc_in);
@@ -623,6 +626,13 @@ static int async_die_is_recursing(void)
        return ret != NULL;
 }
 
+int in_async(void)
+{
+       if (!main_thread_set)
+               return 0; /* no asyncs started yet */
+       return !pthread_equal(main_thread, pthread_self());
+}
+
 #else
 
 static struct {
@@ -662,6 +672,12 @@ int git_atexit(void (*handler)(void))
 }
 #define atexit git_atexit
 
+static int process_is_async;
+int in_async(void)
+{
+       return process_is_async;
+}
+
 #endif
 
 int start_async(struct async *async)
@@ -721,6 +737,7 @@ int start_async(struct async *async)
                if (need_out)
                        close(fdout[0]);
                git_atexit_clear();
+               process_is_async = 1;
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
 
index 275d35c442ac575f8f3f40aeb915cd0d341c776f..12bb26c2a6155750203babfec47b08e8bde0ad27 100644 (file)
@@ -47,6 +47,7 @@ struct child_process {
 
 #define CHILD_PROCESS_INIT { NULL, ARGV_ARRAY_INIT, ARGV_ARRAY_INIT }
 void child_process_init(struct child_process *);
+void child_process_clear(struct child_process *);
 
 int start_command(struct child_process *);
 int finish_command(struct child_process *);
@@ -119,5 +120,6 @@ struct async {
 
 int start_async(struct async *async);
 int finish_async(struct async *async);
+int in_async(void);
 
 #endif
diff --git a/setup.c b/setup.c
index 1a374f2bf4f986b00727ee04da6a65f8f914e35f..d34372520b05a2acee36f02ddc2ef68dde4b65fd 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -100,10 +100,7 @@ char *prefix_path_gently(const char *prefix, int len,
                        return NULL;
                }
        } else {
-               sanitized = xmalloc(len + strlen(path) + 1);
-               if (len)
-                       memcpy(sanitized, prefix, len);
-               strcpy(sanitized + len, path);
+               sanitized = xstrfmt("%.*s%s", len, prefix, path);
                if (remaining_prefix)
                        *remaining_prefix = len;
                if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) {
@@ -508,11 +505,8 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 
        if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
                size_t pathlen = slash+1 - path;
-               size_t dirlen = pathlen + len - 8;
-               dir = xmalloc(dirlen + 1);
-               strncpy(dir, path, pathlen);
-               strncpy(dir + pathlen, buf + 8, len - 8);
-               dir[dirlen] = '\0';
+               dir = xstrfmt("%.*s%.*s", (int)pathlen, path,
+                             (int)(len - 8), buf + 8);
                free(buf);
                buf = dir;
        }
index ca699d7beb067bff83ea15f0a0e724916f33549b..c5b31de9aa579dde37e5345d207995416f261eed 100644 (file)
@@ -208,44 +208,25 @@ const char *sha1_file_name(const unsigned char *sha1)
  * provided by the caller.  which should be "pack" or "idx".
  */
 static char *sha1_get_pack_name(const unsigned char *sha1,
-                               char **name, char **base, const char *which)
+                               struct strbuf *buf,
+                               const char *which)
 {
-       static const char hex[] = "0123456789abcdef";
-       char *buf;
-       int i;
-
-       if (!*base) {
-               const char *sha1_file_directory = get_object_directory();
-               int len = strlen(sha1_file_directory);
-               *base = xmalloc(len + 60);
-               sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.%s",
-                       sha1_file_directory, which);
-               *name = *base + len + 11;
-       }
-
-       buf = *name;
-
-       for (i = 0; i < 20; i++) {
-               unsigned int val = *sha1++;
-               *buf++ = hex[val >> 4];
-               *buf++ = hex[val & 0xf];
-       }
-
-       return *base;
+       strbuf_reset(buf);
+       strbuf_addf(buf, "%s/pack/pack-%s.%s", get_object_directory(),
+                   sha1_to_hex(sha1), which);
+       return buf->buf;
 }
 
 char *sha1_pack_name(const unsigned char *sha1)
 {
-       static char *name, *base;
-
-       return sha1_get_pack_name(sha1, &name, &base, "pack");
+       static struct strbuf buf = STRBUF_INIT;
+       return sha1_get_pack_name(sha1, &buf, "pack");
 }
 
 char *sha1_pack_index_name(const unsigned char *sha1)
 {
-       static char *name, *base;
-
-       return sha1_get_pack_name(sha1, &name, &base, "idx");
+       static struct strbuf buf = STRBUF_INIT;
+       return sha1_get_pack_name(sha1, &buf, "idx");
 }
 
 struct alternate_object_database *alt_odb_list;
@@ -671,13 +652,15 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
 int open_pack_index(struct packed_git *p)
 {
        char *idx_name;
+       size_t len;
        int ret;
 
        if (p->index_data)
                return 0;
 
-       idx_name = xstrdup(p->pack_name);
-       strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx");
+       if (!strip_suffix(p->pack_name, ".pack", &len))
+               die("BUG: pack_name does not end in .pack");
+       idx_name = xstrfmt("%.*s.idx", (int)len, p->pack_name);
        ret = check_packed_git_idx(idx_name, p);
        free(idx_name);
        return ret;
@@ -1161,11 +1144,12 @@ static void try_to_free_pack_memory(size_t size)
        release_pack_memory(size);
 }
 
-struct packed_git *add_packed_git(const char *path, int path_len, int local)
+struct packed_git *add_packed_git(const char *path, size_t path_len, int local)
 {
        static int have_set_try_to_free_routine;
        struct stat st;
-       struct packed_git *p = alloc_packed_git(path_len + 2);
+       size_t alloc;
+       struct packed_git *p;
 
        if (!have_set_try_to_free_routine) {
                have_set_try_to_free_routine = 1;
@@ -1176,18 +1160,22 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
         * Make sure a corresponding .pack file exists and that
         * the index looks sane.
         */
-       path_len -= strlen(".idx");
-       if (path_len < 1) {
-               free(p);
+       if (!strip_suffix_mem(path, &path_len, ".idx"))
                return NULL;
-       }
+
+       /*
+        * ".pack" is long enough to hold any suffix we're adding (and
+        * the use xsnprintf double-checks that)
+        */
+       alloc = path_len + strlen(".pack") + 1;
+       p = alloc_packed_git(alloc);
        memcpy(p->pack_name, path, path_len);
 
-       strcpy(p->pack_name + path_len, ".keep");
+       xsnprintf(p->pack_name + path_len, alloc - path_len, ".keep");
        if (!access(p->pack_name, F_OK))
                p->pack_keep = 1;
 
-       strcpy(p->pack_name + path_len, ".pack");
+       xsnprintf(p->pack_name + path_len, alloc - path_len, ".pack");
        if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) {
                free(p);
                return NULL;
@@ -1207,9 +1195,10 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
 struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path)
 {
        const char *path = sha1_pack_name(sha1);
-       struct packed_git *p = alloc_packed_git(strlen(path) + 1);
+       int alloc = strlen(path) + 1;
+       struct packed_git *p = alloc_packed_git(alloc);
 
-       strcpy(p->pack_name, path);
+       memcpy(p->pack_name, path, alloc); /* includes NUL */
        hashcpy(p->sha1, sha1);
        if (check_packed_git_idx(idx_path, p)) {
                free(p);
@@ -1479,7 +1468,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map,
                return -1;
 
        /* Generate the header */
-       hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1;
+       hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(obj_type), size) + 1;
 
        /* Sha1.. */
        git_SHA1_Init(&c);
@@ -2137,7 +2126,7 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
 {
        unsigned long hash;
 
-       hash = (unsigned long)p + (unsigned long)base_offset;
+       hash = (unsigned long)(intptr_t)p + (unsigned long)base_offset;
        hash += (hash >> 8) + (hash >> 16);
        return hash % MAX_DELTA_CACHE;
 }
@@ -2945,7 +2934,7 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len,
        git_SHA_CTX c;
 
        /* Generate the header */
-       *hdrlen = sprintf(hdr, "%s %lu", type, len)+1;
+       *hdrlen = xsnprintf(hdr, *hdrlen, "%s %lu", type, len)+1;
 
        /* Sha1.. */
        git_SHA1_Init(&c);
@@ -3008,7 +2997,7 @@ int hash_sha1_file(const void *buf, unsigned long len, const char *type,
                    unsigned char *sha1)
 {
        char hdr[32];
-       int hdrlen;
+       int hdrlen = sizeof(hdr);
        write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
        return 0;
 }
@@ -3038,29 +3027,31 @@ static inline int directory_size(const char *filename)
  * We want to avoid cross-directory filename renames, because those
  * can have problems on various filesystems (FAT, NFS, Coda).
  */
-static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
+static int create_tmpfile(struct strbuf *tmp, const char *filename)
 {
        int fd, dirlen = directory_size(filename);
 
-       if (dirlen + 20 > bufsiz) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-       memcpy(buffer, filename, dirlen);
-       strcpy(buffer + dirlen, "tmp_obj_XXXXXX");
-       fd = git_mkstemp_mode(buffer, 0444);
+       strbuf_reset(tmp);
+       strbuf_add(tmp, filename, dirlen);
+       strbuf_addstr(tmp, "tmp_obj_XXXXXX");
+       fd = git_mkstemp_mode(tmp->buf, 0444);
        if (fd < 0 && dirlen && errno == ENOENT) {
-               /* Make sure the directory exists */
-               memcpy(buffer, filename, dirlen);
-               buffer[dirlen-1] = 0;
-               if (mkdir(buffer, 0777) && errno != EEXIST)
+               /*
+                * Make sure the directory exists; note that the contents
+                * of the buffer are undefined after mkstemp returns an
+                * error, so we have to rewrite the whole buffer from
+                * scratch.
+                */
+               strbuf_reset(tmp);
+               strbuf_add(tmp, filename, dirlen - 1);
+               if (mkdir(tmp->buf, 0777) && errno != EEXIST)
                        return -1;
-               if (adjust_shared_perm(buffer))
+               if (adjust_shared_perm(tmp->buf))
                        return -1;
 
                /* Try again */
-               strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX");
-               fd = git_mkstemp_mode(buffer, 0444);
+               strbuf_addstr(tmp, "/tmp_obj_XXXXXX");
+               fd = git_mkstemp_mode(tmp->buf, 0444);
        }
        return fd;
 }
@@ -3073,10 +3064,10 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
-       static char tmp_file[PATH_MAX];
+       static struct strbuf tmp_file = STRBUF_INIT;
        const char *filename = sha1_file_name(sha1);
 
-       fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
+       fd = create_tmpfile(&tmp_file, filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s", get_object_directory());
@@ -3125,12 +3116,12 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
                struct utimbuf utb;
                utb.actime = mtime;
                utb.modtime = mtime;
-               if (utime(tmp_file, &utb) < 0)
+               if (utime(tmp_file.buf, &utb) < 0)
                        warning("failed utime() on %s: %s",
-                               tmp_file, strerror(errno));
+                               tmp_file.buf, strerror(errno));
        }
 
-       return finalize_object_file(tmp_file, filename);
+       return finalize_object_file(tmp_file.buf, filename);
 }
 
 static int freshen_loose_object(const unsigned char *sha1)
@@ -3154,7 +3145,7 @@ static int freshen_packed_object(const unsigned char *sha1)
 int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1)
 {
        char hdr[32];
-       int hdrlen;
+       int hdrlen = sizeof(hdr);
 
        /* Normally if we have it in the pack then we do not bother writing
         * it out into .git/objects/??/?{38} file.
@@ -3172,7 +3163,8 @@ int hash_sha1_file_literally(const void *buf, unsigned long len, const char *typ
        int hdrlen, status = 0;
 
        /* type string, SP, %lu of the length plus NUL must fit this */
-       header = xmalloc(strlen(type) + 32);
+       hdrlen = strlen(type) + 32;
+       header = xmalloc(hdrlen);
        write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen);
 
        if (!(flags & HASH_WRITE_OBJECT))
@@ -3200,7 +3192,7 @@ int force_object_loose(const unsigned char *sha1, time_t mtime)
        buf = read_packed_sha1(sha1, &type, &len);
        if (!buf)
                return error("cannot read sha1_file for %s", sha1_to_hex(sha1));
-       hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
+       hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1;
        ret = write_loose_object(sha1, hdr, hdrlen, buf, len, mtime);
        free(buf);
 
index da6874c15ec51964b6044ae96fc88c282decb0cc..3242c5ea462ce246bbc1b90c4aeb2183b2bcbecb 100644 (file)
@@ -96,11 +96,15 @@ static void find_short_object_filename(int len, const char *hex_pfx, struct disa
        }
        fakeent->next = alt_odb_list;
 
-       sprintf(hex, "%.2s", hex_pfx);
+       xsnprintf(hex, sizeof(hex), "%.2s", hex_pfx);
        for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
                struct dirent *de;
                DIR *dir;
-               sprintf(alt->name, "%.2s/", hex_pfx);
+               /*
+                * every alt_odb struct has 42 extra bytes after the base
+                * for exactly this purpose
+                */
+               xsnprintf(alt->name, 42, "%.2s/", hex_pfx);
                dir = opendir(alt->base);
                if (!dir)
                        continue;
@@ -368,14 +372,13 @@ int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data)
        return ds.ambiguous;
 }
 
-const char *find_unique_abbrev(const unsigned char *sha1, int len)
+int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len)
 {
        int status, exists;
-       static char hex[41];
 
-       memcpy(hex, sha1_to_hex(sha1), 40);
+       sha1_to_hex_r(hex, sha1);
        if (len == 40 || !len)
-               return hex;
+               return 40;
        exists = has_sha1_file(sha1);
        while (len < 40) {
                unsigned char sha1_ret[20];
@@ -384,10 +387,17 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
                    ? !status
                    : status == SHORT_NAME_NOT_FOUND) {
                        hex[len] = 0;
-                       return hex;
+                       return len;
                }
                len++;
        }
+       return len;
+}
+
+const char *find_unique_abbrev(const unsigned char *sha1, int len)
+{
+       static char hex[GIT_SHA1_HEXSZ + 1];
+       find_unique_abbrev_r(hex, sha1, len);
        return hex;
 }
 
@@ -1283,8 +1293,7 @@ static void diagnose_invalid_index_path(int stage,
        const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
-       unsigned fullnamelen;
-       char *fullname;
+       struct strbuf fullname = STRBUF_INIT;
 
        if (!prefix)
                prefix = "";
@@ -1304,21 +1313,19 @@ static void diagnose_invalid_index_path(int stage,
        }
 
        /* Confusion between relative and absolute filenames? */
-       fullnamelen = namelen + strlen(prefix);
-       fullname = xmalloc(fullnamelen + 1);
-       strcpy(fullname, prefix);
-       strcat(fullname, filename);
-       pos = cache_name_pos(fullname, fullnamelen);
+       strbuf_addstr(&fullname, prefix);
+       strbuf_addstr(&fullname, filename);
+       pos = cache_name_pos(fullname.buf, fullname.len);
        if (pos < 0)
                pos = -pos - 1;
        if (pos < active_nr) {
                ce = active_cache[pos];
-               if (ce_namelen(ce) == fullnamelen &&
-                   !memcmp(ce->name, fullname, fullnamelen))
+               if (ce_namelen(ce) == fullname.len &&
+                   !memcmp(ce->name, fullname.buf, fullname.len))
                        die("Path '%s' is in the index, but not '%s'.\n"
                            "Did you mean ':%d:%s' aka ':%d:./%s'?",
-                           fullname, filename,
-                           ce_stage(ce), fullname,
+                           fullname.buf, filename,
+                           ce_stage(ce), fullname.buf,
                            ce_stage(ce), filename);
        }
 
@@ -1328,7 +1335,7 @@ static void diagnose_invalid_index_path(int stage,
                die("Path '%s' does not exist (neither on disk nor in the index).",
                    filename);
 
-       free(fullname);
+       strbuf_release(&fullname);
 }
 
 
index d49a3d6e9f02e292a04981e34f0a2d0b1ffa00d0..46be7897794ab8e350d64f00d3c11390fe993ad8 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -10,7 +10,6 @@
 #include "diff.h"
 #include "revision.h"
 #include "commit-slab.h"
-#include "sigchain.h"
 
 static int is_shallow = -1;
 static struct stat_validity shallow_stat;
@@ -401,13 +400,9 @@ static void paint_down(struct paint_info *info, const unsigned char *sha1,
        commit_list_insert(c, &head);
        while (head) {
                struct commit_list *p;
-               struct commit *c = head->item;
+               struct commit *c = pop_commit(&head);
                uint32_t **refs = ref_bitmap_at(&info->ref_bitmap, c);
 
-               p = head;
-               head = head->next;
-               free(p);
-
                /* XXX check "UNINTERESTING" from pack bitmaps if available */
                if (c->object.flags & (SEEN | UNINTERESTING))
                        continue;
index 7f9dc229fbc82d005b5c2b417c2af2fc9f6b497a..fde8adc000f3167bba7fdf2d7cb3a4e7db612af4 100644 (file)
@@ -137,11 +137,11 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet
                if (packet_max - 5 < n)
                        n = packet_max - 5;
                if (0 <= band) {
-                       sprintf(hdr, "%04x", n + 5);
+                       xsnprintf(hdr, sizeof(hdr), "%04x", n + 5);
                        hdr[4] = band;
                        write_or_die(fd, hdr, 5);
                } else {
-                       sprintf(hdr, "%04x", n + 4);
+                       xsnprintf(hdr, sizeof(hdr), "%04x", n + 4);
                        write_or_die(fd, hdr, 4);
                }
                write_or_die(fd, p, n);
index 958387562184fe27c7e476ebb24d5547a55ae4ad..d76f0aed85c4ec6eafdcfd1a8ee2b22d3d20df96 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -245,8 +245,8 @@ void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size
        static char prefix2[2];
 
        if (prefix1[0] != comment_line_char) {
-               sprintf(prefix1, "%c ", comment_line_char);
-               sprintf(prefix2, "%c", comment_line_char);
+               xsnprintf(prefix1, sizeof(prefix1), "%c ", comment_line_char);
+               xsnprintf(prefix2, sizeof(prefix2), "%c", comment_line_char);
        }
        add_lines(out, prefix1, prefix2, buf, size);
 }
@@ -744,6 +744,15 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm)
        strbuf_setlen(sb, sb->len + len);
 }
 
+void strbuf_add_unique_abbrev(struct strbuf *sb, const unsigned char *sha1,
+                             int abbrev_len)
+{
+       int r;
+       strbuf_grow(sb, GIT_SHA1_HEXSZ + 1);
+       r = find_unique_abbrev_r(sb->buf + sb->len, sha1, abbrev_len);
+       strbuf_setlen(sb, sb->len + r);
+}
+
 /*
  * Returns the length of a line, without trailing spaces.
  *
index 5397d91d7ade0360077c3b2ceb9e6d3cd226820d..7123fca7aff5182b66af6d76991552081139467c 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -483,6 +483,14 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,
  */
 extern void strbuf_list_free(struct strbuf **);
 
+/**
+ * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
+ * the strbuf `sb`.
+ */
+extern void strbuf_add_unique_abbrev(struct strbuf *sb,
+                                    const unsigned char *sha1,
+                                    int abbrev_len);
+
 /**
  * Launch the user preferred editor to edit a file and fill the buffer
  * with the file's contents upon the user completing their editing. The
@@ -500,10 +508,21 @@ extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *
  */
 extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s);
 
+/**
+ * "Complete" the contents of `sb` by ensuring that either it ends with the
+ * character `term`, or it is empty.  This can be used, for example,
+ * to ensure that text ends with a newline, but without creating an empty
+ * blank line if there is no content in the first place.
+ */
+static inline void strbuf_complete(struct strbuf *sb, char term)
+{
+       if (sb->len && sb->buf[sb->len - 1] != term)
+               strbuf_addch(sb, term);
+}
+
 static inline void strbuf_complete_line(struct strbuf *sb)
 {
-       if (sb->len && sb->buf[sb->len - 1] != '\n')
-               strbuf_addch(sb, '\n');
+       strbuf_complete(sb, '\n');
 }
 
 extern int strbuf_branchname(struct strbuf *sb, const char *name);
index 393de5357eb486d8fb9ee3be1d75c4b80771bd02..afe0ea81565a5675bcc3eff3199059240bbf95ee 100644 (file)
@@ -257,78 +257,62 @@ static int parse_config(const char *var, const char *value, void *data)
        if (!name_and_item_from_var(var, &name, &item))
                return 0;
 
-       submodule = lookup_or_create_by_name(me->cache, me->gitmodules_sha1,
-                       name.buf);
+       submodule = lookup_or_create_by_name(me->cache,
+                                            me->gitmodules_sha1,
+                                            name.buf);
 
        if (!strcmp(item.buf, "path")) {
-               struct strbuf path = STRBUF_INIT;
-               if (!value) {
+               if (!value)
                        ret = config_error_nonbool(var);
-                       goto release_return;
-               }
-               if (!me->overwrite && submodule->path != NULL) {
+               else if (!me->overwrite && submodule->path != NULL)
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "path");
-                       goto release_return;
+               else {
+                       if (submodule->path)
+                               cache_remove_path(me->cache, submodule);
+                       free((void *) submodule->path);
+                       submodule->path = xstrdup(value);
+                       cache_put_path(me->cache, submodule);
                }
-
-               if (submodule->path)
-                       cache_remove_path(me->cache, submodule);
-               free((void *) submodule->path);
-               strbuf_addstr(&path, value);
-               submodule->path = strbuf_detach(&path, NULL);
-               cache_put_path(me->cache, submodule);
        } else if (!strcmp(item.buf, "fetchrecursesubmodules")) {
                /* when parsing worktree configurations we can die early */
                int die_on_error = is_null_sha1(me->gitmodules_sha1);
                if (!me->overwrite &&
-                   submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) {
+                   submodule->fetch_recurse != RECURSE_SUBMODULES_NONE)
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "fetchrecursesubmodules");
-                       goto release_return;
-               }
-
-               submodule->fetch_recurse = parse_fetch_recurse(var, value,
+               else
+                       submodule->fetch_recurse = parse_fetch_recurse(
+                                                               var, value,
                                                                die_on_error);
        } else if (!strcmp(item.buf, "ignore")) {
-               struct strbuf ignore = STRBUF_INIT;
-               if (!me->overwrite && submodule->ignore != NULL) {
+               if (!value)
+                       ret = config_error_nonbool(var);
+               else if (!me->overwrite && submodule->ignore != NULL)
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "ignore");
-                       goto release_return;
-               }
-               if (!value) {
-                       ret = config_error_nonbool(var);
-                       goto release_return;
-               }
-               if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
-                   strcmp(value, "all") && strcmp(value, "none")) {
+               else if (strcmp(value, "untracked") &&
+                        strcmp(value, "dirty") &&
+                        strcmp(value, "all") &&
+                        strcmp(value, "none"))
                        warning("Invalid parameter '%s' for config option "
                                        "'submodule.%s.ignore'", value, var);
-                       goto release_return;
+               else {
+                       free((void *) submodule->ignore);
+                       submodule->ignore = xstrdup(value);
                }
-
-               free((void *) submodule->ignore);
-               strbuf_addstr(&ignore, value);
-               submodule->ignore = strbuf_detach(&ignore, NULL);
        } else if (!strcmp(item.buf, "url")) {
-               struct strbuf url = STRBUF_INIT;
                if (!value) {
                        ret = config_error_nonbool(var);
-                       goto release_return;
-               }
-               if (!me->overwrite && submodule->url != NULL) {
+               } else if (!me->overwrite && submodule->url != NULL) {
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "url");
-                       goto release_return;
+               } else {
+                       free((void *) submodule->url);
+                       submodule->url = xstrdup(value);
                }
-
-               free((void *) submodule->url);
-               strbuf_addstr(&url, value);
-               submodule->url = strbuf_detach(&url, NULL);
        }
 
-release_return:
        strbuf_release(&name);
        strbuf_release(&item);
 
index 5e5a46fe2ab765ff5ecec01a9bc46351fe596837..88af54c633851d440f46e21d65b1362b6360d2bb 100644 (file)
@@ -122,6 +122,7 @@ static int add_submodule_odb(const char *path)
        struct strbuf objects_directory = STRBUF_INIT;
        struct alternate_object_database *alt_odb;
        int ret = 0;
+       int alloc;
 
        strbuf_git_path_submodule(&objects_directory, path, "objects/");
        if (!is_directory(objects_directory.buf)) {
@@ -129,15 +130,17 @@ static int add_submodule_odb(const char *path)
                goto done;
        }
        /* avoid adding it twice */
+       prepare_alt_odb();
        for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next)
                if (alt_odb->name - alt_odb->base == objects_directory.len &&
                                !strncmp(alt_odb->base, objects_directory.buf,
                                        objects_directory.len))
                        goto done;
 
-       alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb));
+       alloc = objects_directory.len + 42; /* for "12/345..." sha1 */
+       alt_odb = xmalloc(sizeof(*alt_odb) + alloc);
        alt_odb->next = alt_odb_list;
-       strcpy(alt_odb->base, objects_directory.buf);
+       xsnprintf(alt_odb->base, alloc, "%s", objects_directory.buf);
        alt_odb->name = alt_odb->base + objects_directory.len;
        alt_odb->name[2] = '/';
        alt_odb->name[40] = '\0';
@@ -146,7 +149,6 @@ static int add_submodule_odb(const char *path)
 
        /* add possible alternates from the submodule */
        read_info_alternates(objects_directory.buf, 0);
-       prepare_alt_odb();
 done:
        strbuf_release(&objects_directory);
        return ret;
index 35438bca487337e73fcb3c4fd490072793d21900..1dc908e43a993502bed611023448aa76fc9da6ad 100644 (file)
--- a/t/README
+++ b/t/README
@@ -563,6 +563,11 @@ library for your script to use.
    argument.  This is primarily meant for use during the
    development of a new test script.
 
+ - debug <git-command>
+
+   Run a git command inside a debugger. This is primarily meant for
+   use when debugging a failing test script.
+
  - test_done
 
    Your test script must have test_done at the end.  Its purpose
index 7de8d85ee85e2387463e8d98175c9d8f66ca8959..f91bbcfc853a47e0a314160b79fc4816178f1723 100755 (executable)
@@ -202,8 +202,8 @@ test_expect_success 'init honors global core.sharedRepository' '
        x$(git config -f shared-honor-global/.git/config core.sharedRepository)
 '
 
-test_expect_success 'init rejects insanely long --template' '
-       test_must_fail git init --template=$(printf "x%09999dx" 1) test
+test_expect_success 'init allows insanely long --template' '
+       git init --template=$(printf "x%09999dx" 1) test
 '
 
 test_expect_success 'init creates a new directory' '
index 9393322c3e7028ea7074d2b1ec50fbb293c6728f..9670e8cbe6cb9a9faa3519b0f11dc16713496188 100755 (executable)
@@ -116,4 +116,46 @@ test_expect_success 'setup_git_dir twice in subdir' '
        )
 '
 
+test_expect_success 'enter_repo non-strict mode' '
+       test_create_repo enter_repo &&
+       (
+               cd enter_repo &&
+               test_tick &&
+               test_commit foo &&
+               mv .git .realgit &&
+               echo "gitdir: .realgit" >.git
+       ) &&
+       git ls-remote enter_repo >actual &&
+       cat >expected <<-\EOF &&
+       946e985ab20de757ca5b872b16d64e92ff3803a9        HEAD
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/heads/master
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/tags/foo
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'enter_repo linked checkout' '
+       (
+               cd enter_repo &&
+               git worktree add  ../foo refs/tags/foo
+       ) &&
+       git ls-remote foo >actual &&
+       cat >expected <<-\EOF &&
+       946e985ab20de757ca5b872b16d64e92ff3803a9        HEAD
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/heads/master
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/tags/foo
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'enter_repo strict mode' '
+       git ls-remote --upload-pack="git upload-pack --strict" foo/.git >actual &&
+       cat >expected <<-\EOF &&
+       946e985ab20de757ca5b872b16d64e92ff3803a9        HEAD
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/heads/master
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/tags/foo
+       EOF
+       test_cmp expected actual
+'
+
 test_done
index 1a56e5e82ee807895da0d597ab75c10cb0299082..b3436515044e9d9be04b8164b704106835c31db1 100755 (executable)
@@ -55,6 +55,26 @@ create_gitattributes () {
        esac
 }
 
+create_NNO_files () {
+       lfname=$1
+       crlfname=$2
+       lfmixcrlf=$3
+       lfmixcr=$4
+       crlfnul=$5
+       for crlf in false true input
+       do
+               for attr in "" auto text -text lf crlf
+               do
+                       pfx=NNO_${crlf}_attr_${attr} &&
+                       cp $lfname    ${pfx}_LF.txt &&
+                       cp $crlfname  ${pfx}_CRLF.txt &&
+                       cp $lfmixcrlf ${pfx}_CRLF_mix_LF.txt &&
+                       cp $lfmixcr   ${pfx}_LF_mix_CR.txt &&
+                       cp $crlfnul   ${pfx}_CRLF_nul.txt
+               done
+       done
+}
+
 check_warning () {
        case "$1" in
        LF_CRLF) echo "warning: LF will be replaced by CRLF" >"$2".expect ;;
@@ -62,7 +82,7 @@ check_warning () {
        '')                                                      >"$2".expect ;;
        *) echo >&2 "Illegal 1": "$1" ; return false ;;
        esac
-       grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" >"$2".actual
+       grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" | uniq  >"$2".actual
        test_cmp "$2".expect "$2".actual
 }
 
@@ -71,19 +91,10 @@ commit_check_warn () {
        attr=$2
        lfname=$3
        crlfname=$4
-       repoMIX=$5
-       lfmixcrlf=$6
-       lfmixcr=$7
-       crlfnul=$8
+       lfmixcrlf=$5
+       lfmixcr=$6
+       crlfnul=$7
        pfx=crlf_${crlf}_attr_${attr}
-       # Special handling for repoMIX: It should already be in the repo
-       # with CRLF
-       f=repoMIX
-       fname=${pfx}_$f.txt
-       echo >.gitattributes &&
-       cp $f $fname &&
-       git -c core.autocrlf=false add $fname 2>"${pfx}_$f.err" &&
-       git commit -m "repoMIX" &&
        create_gitattributes "$attr" &&
        for f in LF CRLF repoMIX LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul
        do
@@ -99,6 +110,45 @@ commit_check_warn () {
        check_warning "$crlfnul" ${pfx}_CRLF_nul.err
 }
 
+commit_chk_wrnNNO () {
+       crlf=$1
+       attr=$2
+       lfwarn=$3
+       crlfwarn=$4
+       lfmixcrlf=$5
+       lfmixcr=$6
+       crlfnul=$7
+       pfx=NNO_${crlf}_attr_${attr}
+       #Commit files on top of existing file
+       create_gitattributes "$attr" &&
+       for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
+       do
+               fname=${pfx}_$f.txt &&
+               cp $f $fname &&
+               git -c core.autocrlf=$crlf add $fname 2>/dev/null &&
+               git -c core.autocrlf=$crlf commit -m "commit_$fname" $fname >"${pfx}_$f.err" 2>&1
+       done
+
+       test_expect_success "commit NNO files crlf=$crlf attr=$attr LF" '
+               check_warning "$lfwarn" ${pfx}_LF.err
+       '
+       test_expect_success "commit NNO files crlf=$crlf attr=$attr CRLF" '
+               check_warning "$crlfwarn" ${pfx}_CRLF.err
+       '
+
+       test_expect_success "commit NNO files crlf=$crlf attr=$attr CRLF_mix_LF" '
+               check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err
+       '
+
+       test_expect_success "commit NNO files crlf=$crlf attr=$attr LF_mix_cr" '
+               check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err
+       '
+
+       test_expect_success "commit NNO files crlf=$crlf attr=$attr CRLF_nul" '
+               check_warning "$crlfnul" ${pfx}_CRLF_nul.err
+       '
+}
+
 check_files_in_repo () {
        crlf=$1
        attr=$2
@@ -115,6 +165,31 @@ check_files_in_repo () {
        compare_files $crlfnul ${pfx}CRLF_nul.txt
 }
 
+check_in_repo_NNO () {
+       crlf=$1
+       attr=$2
+       lfname=$3
+       crlfname=$4
+       lfmixcrlf=$5
+       lfmixcr=$6
+       crlfnul=$7
+       pfx=NNO_${crlf}_attr_${attr}_
+       test_expect_success "compare_files $lfname ${pfx}LF.txt" '
+               compare_files $lfname ${pfx}LF.txt
+       '
+       test_expect_success "compare_files $crlfname ${pfx}CRLF.txt" '
+               compare_files $crlfname ${pfx}CRLF.txt
+       '
+       test_expect_success "compare_files $lfmixcrlf ${pfx}CRLF_mix_LF.txt" '
+               compare_files $lfmixcrlf ${pfx}CRLF_mix_LF.txt
+       '
+       test_expect_success "compare_files $lfmixcr ${pfx}LF_mix_CR.txt" '
+               compare_files $lfmixcr ${pfx}LF_mix_CR.txt
+       '
+       test_expect_success "compare_files $crlfnul ${pfx}CRLF_nul.txt" '
+               compare_files $crlfnul ${pfx}CRLF_nul.txt
+       '
+}
 
 checkout_files () {
        eol=$1
@@ -169,7 +244,11 @@ test_expect_success 'setup master' '
        printf "line1\nline2\rline3"     >LF_mix_CR &&
        printf "line1\r\nline2\rline3"   >CRLF_mix_CR &&
        printf "line1Q\r\nline2\r\nline3" | q_to_nul >CRLF_nul &&
-       printf "line1Q\nline2\nline3" | q_to_nul >LF_nul
+       printf "line1Q\nline2\nline3" | q_to_nul >LF_nul &&
+       create_NNO_files CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF &&
+       git -c core.autocrlf=false add NNO_*.txt &&
+       git commit -m "mixed line endings" &&
+       test_tick
 '
 
 
@@ -191,46 +270,72 @@ else
        WAMIX=CRLF_LF
 fi
 
-#                         attr   LF        CRLF      repoMIX   CRLFmixLF LFmixCR   CRLFNUL
+#                         attr   LF        CRLF      CRLFmixLF LFmixCR   CRLFNUL
 test_expect_success 'commit files empty attr' '
-       commit_check_warn false ""     ""        ""        ""        ""        ""        "" &&
-       commit_check_warn true  ""     "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" ""        "" &&
-       commit_check_warn input ""     ""        "CRLF_LF" "CRLF_LF" "CRLF_LF" ""        ""
+       commit_check_warn false ""     ""        ""        ""        ""        "" &&
+       commit_check_warn true  ""     "LF_CRLF" ""        "LF_CRLF" ""        "" &&
+       commit_check_warn input ""     ""        "CRLF_LF" "CRLF_LF" ""        ""
 '
 
 test_expect_success 'commit files attr=auto' '
-       commit_check_warn false "auto" "$WILC"   "$WICL"   "$WAMIX"  "$WAMIX"  ""        "" &&
-       commit_check_warn true  "auto" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" ""        "" &&
-       commit_check_warn input "auto" ""        "CRLF_LF" "CRLF_LF" "CRLF_LF" ""        ""
+       commit_check_warn false "auto" "$WILC"   "$WICL"   "$WAMIX"  ""        "" &&
+       commit_check_warn true  "auto" "LF_CRLF" ""        "LF_CRLF" ""        "" &&
+       commit_check_warn input "auto" ""        "CRLF_LF" "CRLF_LF" ""        ""
 '
 
 test_expect_success 'commit files attr=text' '
-       commit_check_warn false "text" "$WILC"   "$WICL"   "$WAMIX"  "$WAMIX"  "$WILC"   "$WICL"   &&
-       commit_check_warn true  "text" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" "LF_CRLF" ""        &&
-       commit_check_warn input "text" ""        "CRLF_LF" "CRLF_LF" "CRLF_LF" ""        "CRLF_LF"
+       commit_check_warn false "text" "$WILC"   "$WICL"   "$WAMIX"  "$WILC"   "$WICL"   &&
+       commit_check_warn true  "text" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" ""        &&
+       commit_check_warn input "text" ""        "CRLF_LF" "CRLF_LF" ""        "CRLF_LF"
 '
 
 test_expect_success 'commit files attr=-text' '
-       commit_check_warn false "-text" ""       ""        ""        ""        ""        "" &&
-       commit_check_warn true  "-text" ""       ""        ""        ""        ""        "" &&
-       commit_check_warn input "-text" ""       ""        ""        ""        ""        ""
+       commit_check_warn false "-text" ""       ""        ""        ""        "" &&
+       commit_check_warn true  "-text" ""       ""        ""        ""        "" &&
+       commit_check_warn input "-text" ""       ""        ""        ""        ""
 '
 
 test_expect_success 'commit files attr=lf' '
-       commit_check_warn false "lf"    ""       "CRLF_LF" "CRLF_LF" "CRLF_LF"  ""       "CRLF_LF" &&
-       commit_check_warn true  "lf"    ""       "CRLF_LF" "CRLF_LF" "CRLF_LF"  ""       "CRLF_LF" &&
-       commit_check_warn input "lf"    ""       "CRLF_LF" "CRLF_LF" "CRLF_LF"  ""       "CRLF_LF"
+       commit_check_warn false "lf"    ""       "CRLF_LF" "CRLF_LF"  ""       "CRLF_LF" &&
+       commit_check_warn true  "lf"    ""       "CRLF_LF" "CRLF_LF"  ""       "CRLF_LF" &&
+       commit_check_warn input "lf"    ""       "CRLF_LF" "CRLF_LF"  ""       "CRLF_LF"
 '
 
 test_expect_success 'commit files attr=crlf' '
-       commit_check_warn false "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
-       commit_check_warn true  "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" "LF_CRLF" "" &&
-       commit_check_warn input "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" "LF_CRLF" ""
+       commit_check_warn false "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" "" &&
+       commit_check_warn true  "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" "" &&
+       commit_check_warn input "crlf" "LF_CRLF" ""        "LF_CRLF" "LF_CRLF" ""
 '
 
+#                       attr   LF        CRLF      CRLFmixLF    LF_mix_CR   CRLFNUL
+commit_chk_wrnNNO false ""     ""        ""        ""           ""              ""
+commit_chk_wrnNNO true  ""     "LF_CRLF" ""        ""           ""              ""
+commit_chk_wrnNNO input ""     ""        ""        ""           ""              ""
+
+
+commit_chk_wrnNNO false "auto" "$WILC"   "$WICL"   "$WAMIX"     ""              ""
+commit_chk_wrnNNO true  "auto" "LF_CRLF" ""        "LF_CRLF"    ""              ""
+commit_chk_wrnNNO input "auto" ""        "CRLF_LF" "CRLF_LF"    ""              ""
+
+commit_chk_wrnNNO false "text" "$WILC"   "$WICL"   "$WAMIX"     "$WILC"         "$WICL"
+commit_chk_wrnNNO true  "text" "LF_CRLF" ""        "LF_CRLF"    "LF_CRLF"       ""
+commit_chk_wrnNNO input "text" ""        "CRLF_LF" "CRLF_LF"    ""              "CRLF_LF"
+
+commit_chk_wrnNNO false "-text" ""       ""        ""           ""              ""
+commit_chk_wrnNNO true  "-text" ""       ""        ""           ""              ""
+commit_chk_wrnNNO input "-text" ""       ""        ""           ""              ""
+
+commit_chk_wrnNNO false "lf"    ""       "CRLF_LF" "CRLF_LF"     ""             "CRLF_LF"
+commit_chk_wrnNNO true  "lf"    ""       "CRLF_LF" "CRLF_LF"     ""             "CRLF_LF"
+commit_chk_wrnNNO input "lf"    ""       "CRLF_LF" "CRLF_LF"     ""             "CRLF_LF"
+
+commit_chk_wrnNNO false "crlf" "LF_CRLF" ""        "LF_CRLF"    "LF_CRLF"       ""
+commit_chk_wrnNNO true  "crlf" "LF_CRLF" ""        "LF_CRLF"    "LF_CRLF"       ""
+commit_chk_wrnNNO input "crlf" "LF_CRLF" ""        "LF_CRLF"    "LF_CRLF"       ""
+
 test_expect_success 'create files cleanup' '
        rm -f *.txt &&
-       git reset --hard
+       git -c core.autocrlf=false reset --hard
 '
 
 test_expect_success 'commit empty gitattribues' '
@@ -257,6 +362,24 @@ test_expect_success 'commit -text' '
        check_files_in_repo input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul
 '
 
+#                       attr    LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLFNUL
+check_in_repo_NNO false ""      LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLF_nul
+check_in_repo_NNO true  ""      LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLF_nul
+check_in_repo_NNO input ""      LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLF_nul
+
+check_in_repo_NNO false "auto"  LF        LF        LF           LF_mix_CR     CRLF_nul
+check_in_repo_NNO true  "auto"  LF        LF        LF           LF_mix_CR     CRLF_nul
+check_in_repo_NNO input "auto"  LF        LF        LF           LF_mix_CR     CRLF_nul
+
+check_in_repo_NNO false "text"  LF        LF        LF           LF_mix_CR     LF_nul
+check_in_repo_NNO true  "text"  LF        LF        LF           LF_mix_CR     LF_nul
+check_in_repo_NNO input "text"  LF        LF        LF           LF_mix_CR     LF_nul
+
+check_in_repo_NNO false "-text" LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLF_nul
+check_in_repo_NNO true  "-text" LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLF_nul
+check_in_repo_NNO input "-text" LF        CRLF      CRLF_mix_LF  LF_mix_CR     CRLF_nul
+
+
 ################################################################################
 # Check how files in the repo are changed when they are checked out
 # How to read the table below:
index 93605f42f27cef9ef3ffe381c84aea9f6f4be426..627ef854d5c804c0248a728862fdf9d4ea344568 100755 (executable)
@@ -266,15 +266,21 @@ test_expect_success 'setup common repository' 'git --git-dir=bar init'
 test_git_path GIT_COMMON_DIR=bar index                    .git/index
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo      bar/logs/refs/bisectfoo
 test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
 test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
 test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
 test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts
 test_git_path GIT_COMMON_DIR=bar info/sparse-checkout     .git/info/sparse-checkout
+test_git_path GIT_COMMON_DIR=bar info//sparse-checkout    .git/info//sparse-checkout
 test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
 test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
 test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
 test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/bisect/foo          .git/refs/bisect/foo
 test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
 test_git_path GIT_COMMON_DIR=bar config                   bar/config
 test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
index 97406fa4b137afdd99e64172d956718005844120..af1b20dd5c6763e58eccd3f93722987f5f9ce233 100755 (executable)
@@ -1130,4 +1130,23 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches
 )
 '
 
+test_expect_success 'handle per-worktree refs in refs/bisect' '
+       git commit --allow-empty -m "initial commit" &&
+       git worktree add -b branch worktree &&
+       (
+               cd worktree &&
+               git commit --allow-empty -m "test commit"  &&
+               git for-each-ref >for-each-ref.out &&
+               ! grep refs/bisect for-each-ref.out &&
+               git update-ref refs/bisect/something HEAD &&
+               git rev-parse refs/bisect/something >../worktree-head &&
+               git for-each-ref | grep refs/bisect/something
+       ) &&
+       test_path_is_missing .git/refs/bisect &&
+       test_must_fail git rev-parse refs/bisect/something &&
+       git update-ref refs/bisect/something HEAD &&
+       git rev-parse refs/bisect/something >main-head &&
+       ! test_cmp main-head worktree-head
+'
+
 test_done
index 36378b0e3f5b271388fd0935149eea406caea0d0..20b022ae3397552fd3cd4e42553f4cd3575049f5 100755 (executable)
@@ -63,4 +63,33 @@ test_expect_success 'symbolic-ref fails to delete real ref' '
 '
 reset_to_sane
 
+test_expect_success 'create large ref name' '
+       # make 256+ character ref; some systems may not handle that,
+       # so be gentle
+       long=0123456789abcdef &&
+       long=$long/$long/$long/$long &&
+       long=$long/$long/$long/$long &&
+       long_ref=refs/heads/$long &&
+       tree=$(git write-tree) &&
+       commit=$(echo foo | git commit-tree $tree) &&
+       if git update-ref $long_ref $commit; then
+               test_set_prereq LONG_REF
+       else
+               echo >&2 "long refs not supported"
+       fi
+'
+
+test_expect_success LONG_REF 'symbolic-ref can point to large ref name' '
+       git symbolic-ref HEAD $long_ref &&
+       echo $long_ref >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success LONG_REF 'we can parse long symbolic ref' '
+       echo $commit >expect &&
+       git rev-parse --verify HEAD >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 16d0b8bd1a5b6b6a2701fea1c4f35858cec9528b..c465abe8e34936db135c136a6831975b2763e623 100755 (executable)
@@ -38,18 +38,20 @@ test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"'
        test_must_fail git fast-import <input
 '
 
-test_expect_success 'git branch shows badly named ref' '
+test_expect_success 'git branch shows badly named ref as warning' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git branch >output &&
-       grep -e "broken\.\.\.ref" output
+       git branch >output 2>error &&
+       grep -e "broken\.\.\.ref" error &&
+       ! grep -e "broken\.\.\.ref" output
 '
 
 test_expect_success 'branch -d can delete badly named ref' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch -d broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -57,7 +59,8 @@ test_expect_success 'branch -D can delete badly named ref' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch -D broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -85,7 +88,8 @@ test_expect_success 'branch -D cannot delete absolute path' '
 test_expect_success 'git branch cannot create a badly named ref' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        test_must_fail git branch broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -95,7 +99,8 @@ test_expect_success 'branch -m cannot rename to a bad ref name' '
        git branch goodref &&
        test_must_fail git branch -m goodref broken...ref &&
        test_cmp_rev master goodref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -104,14 +109,16 @@ test_expect_failure 'branch -m can rename from a bad ref name' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch -m broken...ref renamed &&
        test_cmp_rev master renamed &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
 test_expect_success 'push cannot create a badly named ref' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -131,7 +138,8 @@ test_expect_failure 'push --mirror can delete badly named ref' '
                cp .git/refs/heads/master .git/refs/heads/broken...ref
        ) &&
        git -C src push --mirror "file://$top/dest" &&
-       git -C dest branch >output &&
+       git -C dest branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -159,7 +167,8 @@ test_expect_success 'update-ref -d can delete broken name' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git update-ref -d refs/heads/broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
index 8267411a0ec129a67cde5381cfd79824471d8fd2..369417498946fcebb901d8593bb584f3767b1e67 100755 (executable)
@@ -193,4 +193,9 @@ test_expect_success '"add" -B/--detach mutually exclusive' '
        test_must_fail git worktree add -B poodle --detach bamboo master
 '
 
+test_expect_success 'local clone from linked checkout' '
+       git clone --local here here-clone &&
+       ( cd here-clone && git fsck )
+'
+
 test_done
diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh
new file mode 100755 (executable)
index 0000000..75ebb1b
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+test_description='test git worktree list'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit init
+'
+
+test_expect_success '"list" all worktrees from main' '
+       echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+       test_when_finished "rm -rf here && git worktree prune" &&
+       git worktree add --detach here master &&
+       echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git worktree list | sed "s/  */ /g" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees from linked' '
+       echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+       test_when_finished "rm -rf here && git worktree prune" &&
+       git worktree add --detach here master &&
+       echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git -C here worktree list | sed "s/  */ /g" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees --porcelain' '
+       echo "worktree $(git rev-parse --show-toplevel)" >expect &&
+       echo "HEAD $(git rev-parse HEAD)" >>expect &&
+       echo "branch $(git symbolic-ref HEAD)" >>expect &&
+       echo >>expect &&
+       test_when_finished "rm -rf here && git worktree prune" &&
+       git worktree add --detach here master &&
+       echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
+       echo "HEAD $(git rev-parse HEAD)" >>expect &&
+       echo "detached" >>expect &&
+       echo >>expect &&
+       git worktree list --porcelain >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'bare repo setup' '
+       git init --bare bare1 &&
+       echo "data" >file1 &&
+       git add file1 &&
+       git commit -m"File1: add data" &&
+       git push bare1 master &&
+       git reset --hard HEAD^
+'
+
+test_expect_success '"list" all worktrees from bare main' '
+       test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
+       git -C bare1 worktree add --detach ../there master &&
+       echo "$(pwd)/bare1 (bare)" >expect &&
+       echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git -C bare1 worktree list | sed "s/  */ /g" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees --porcelain from bare main' '
+       test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
+       git -C bare1 worktree add --detach ../there master &&
+       echo "worktree $(pwd)/bare1" >expect &&
+       echo "bare" >>expect &&
+       echo >>expect &&
+       echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect &&
+       echo "HEAD $(git -C there rev-parse HEAD)" >>expect &&
+       echo "detached" >>expect &&
+       echo >>expect &&
+       git -C bare1 worktree list --porcelain >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees from linked with a bare main' '
+       test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
+       git -C bare1 worktree add --detach ../there master &&
+       echo "$(pwd)/bare1 (bare)" >expect &&
+       echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git -C there worktree list | sed "s/  */ /g" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'bare repo cleanup' '
+       rm -rf bare1
+'
+
+test_done
index e16b15d3e5784eb3c6423318142cb91fe3a58601..314c73c5a708fda8f39e7a86bf3db0329191d7a7 100755 (executable)
@@ -84,6 +84,8 @@ test_expect_success 'non-qualified update in subdir updates from the root' '
        (
                cd dir1 &&
                echo even more >>sub2 &&
+               git --literal-pathspecs add -u &&
+               echo even more >>sub2 &&
                git add -u
        ) &&
        : >expect &&
index fc8b59e7f7796c075b7fd03145e5f9c69625403b..6a5a3166b1823e751dfe19a28bf54c147dd798b3 100755 (executable)
@@ -14,6 +14,7 @@ test_expect_success setup '
                echo expect
                echo ignored
        ) >.gitignore &&
+       git --literal-pathspecs add --all &&
        >will-remove &&
        git add --all &&
        test_tick &&
index 3fc484e8c3f8d910f02e409baf7bf1d5682b4f2f..da257c020fb64d6b8af6b482339afd87f04fea75 100755 (executable)
@@ -305,4 +305,29 @@ test_expect_success 'ls-files with "**" patterns and no slashes' '
        test_cmp expect actual
 '
 
+test_expect_success 'negative patterns' '
+       git init reinclude &&
+       (
+               cd reinclude &&
+               cat >.gitignore <<-\EOF &&
+               /fooo
+               /foo
+               !foo/bar/bar
+               EOF
+               mkdir fooo &&
+               cat >fooo/.gitignore <<-\EOF &&
+               !/*
+               EOF
+               mkdir -p foo/bar &&
+               touch abc foo/def foo/bar/ghi foo/bar/bar &&
+               git ls-files -o --exclude-standard >../actual &&
+               cat >../expected <<-\EOF &&
+               .gitignore
+               abc
+               foo/bar/bar
+               EOF
+               test_cmp ../expected ../actual
+       )
+'
+
 test_done
index 82e18548c39817dec496766d2c8d500668fc4f76..6224187632b105edf1940023eba4a49523d92fe2 100755 (executable)
@@ -629,5 +629,35 @@ test_expect_failure 'merge-recursive rename vs. rename/symlink' '
        test_cmp expected actual
 '
 
+test_expect_success 'merging with triple rename across D/F conflict' '
+       git reset --hard HEAD &&
+       git checkout -b main &&
+       git rm -rf . &&
+
+       echo "just a file" >sub1 &&
+       mkdir -p sub2 &&
+       echo content1 >sub2/file1 &&
+       echo content2 >sub2/file2 &&
+       echo content3 >sub2/file3 &&
+       mkdir simple &&
+       echo base >simple/bar &&
+       git add -A &&
+       test_tick &&
+       git commit -m base &&
+
+       git checkout -b other &&
+       echo more >>simple/bar &&
+       test_tick &&
+       git commit -a -m changesimplefile &&
+
+       git checkout main &&
+       git rm sub1 &&
+       git mv sub2 sub1 &&
+       test_tick &&
+       git commit -m changefiletodir &&
+
+       test_tick &&
+       git merge other
+'
 
 test_done
index 16efe7af03d210cb349337e978c6105cb22d80e8..d3913f9088950a3ca848b8994abfddfecd3f6706 100755 (executable)
@@ -156,4 +156,24 @@ EOF
        test_i18ncmp expect actual
 '
 
+test_expect_success 'git branch `--sort` option' '
+       cat >expect <<-\EOF &&
+       * (HEAD detached from fromtag)
+         branch-two
+         branch-one
+         master
+       EOF
+       git branch --sort=objectsize >actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'git branch --points-at option' '
+       cat >expect <<-\EOF &&
+         branch-one
+         master
+       EOF
+       git branch --points-at=branch-one >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 7b5b6d452e3762c9d0e311602bf39317200c21a7..db244d2f8820cf63fd77e91460631009b20e4029 100755 (executable)
@@ -160,6 +160,13 @@ test_expect_success 'pack ref directly below refs/' '
        test_path_is_missing .git/refs/top
 '
 
+test_expect_success 'do not pack ref in refs/bisect' '
+       git update-ref refs/bisect/local HEAD &&
+       git pack-refs --all --prune &&
+       ! grep refs/bisect/local .git/packed-refs >/dev/null &&
+       test_path_is_file .git/refs/bisect/local
+'
+
 test_expect_success 'disable reflogs' '
        git config core.logallrefupdates false &&
        rm -rf .git/logs
index d783f03d3fc581eed08d8d2593e20ced3cbae200..944154b2e0ad7da5ddacdde5c22847babf1909c9 100755 (executable)
@@ -37,6 +37,16 @@ testrebase() {
        type=$1
        dotest=$2
 
+       test_expect_success "rebase$type: dirty worktree, --no-autostash" '
+               test_config rebase.autostash true &&
+               git reset --hard &&
+               git checkout -b rebased-feature-branch feature-branch &&
+               test_when_finished git branch -D rebased-feature-branch &&
+               test_when_finished git checkout feature-branch &&
+               echo dirty >>file3 &&
+               test_must_fail git rebase$type --no-autostash unrelated-onto-branch
+       '
+
        test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" '
                test_config rebase.autostash true &&
                git reset --hard &&
index 7a8499ce665c74961fdbf6713c0b70c37f95f5e3..dfaf9d9f68939f0c39a512c478265b4aa227c973 100755 (executable)
@@ -919,6 +919,19 @@ test_expect_success 'new remote' '
        cmp expect actual
 '
 
+get_url_test () {
+       cat >expect &&
+       git remote get-url "$@" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'get-url on new remote' '
+       echo foo | get_url_test someremote &&
+       echo foo | get_url_test --all someremote &&
+       echo foo | get_url_test --push someremote &&
+       echo foo | get_url_test --push --all someremote
+'
+
 test_expect_success 'remote set-url bar' '
        git remote set-url someremote bar &&
        echo bar >expect &&
@@ -961,6 +974,13 @@ test_expect_success 'remote set-url --push zot' '
        cmp expect actual
 '
 
+test_expect_success 'get-url with different urls' '
+       echo baz | get_url_test someremote &&
+       echo baz | get_url_test --all someremote &&
+       echo zot | get_url_test --push someremote &&
+       echo zot | get_url_test --push --all someremote
+'
+
 test_expect_success 'remote set-url --push qux zot' '
        git remote set-url --push someremote qux zot &&
        echo qux >expect &&
@@ -995,6 +1015,14 @@ test_expect_success 'remote set-url --push --add aaa' '
        cmp expect actual
 '
 
+test_expect_success 'get-url on multi push remote' '
+       echo foo | get_url_test --push someremote &&
+       get_url_test --push --all someremote <<-\EOF
+       foo
+       aaa
+       EOF
+'
+
 test_expect_success 'remote set-url --push bar aaa' '
        git remote set-url --push someremote bar aaa &&
        echo foo >expect &&
@@ -1039,6 +1067,14 @@ test_expect_success 'remote set-url --add bbb' '
        cmp expect actual
 '
 
+test_expect_success 'get-url on multi fetch remote' '
+       echo baz | get_url_test someremote &&
+       get_url_test --all someremote <<-\EOF
+       baz
+       bbb
+       EOF
+'
+
 test_expect_success 'remote set-url --delete .*' '
        test_must_fail git remote set-url --delete someremote .\* &&
        echo "YYY" >expect &&
@@ -1108,6 +1144,7 @@ test_extra_arg rename origin newname
 test_extra_arg remove origin
 test_extra_arg set-head origin master
 # set-branches takes any number of args
+test_extra_arg get-url origin newurl
 test_extra_arg set-url origin newurl oldurl
 # show takes any number of args
 # prune takes any number of args
index 2250ef4fe245bda17328d46844a4caf49fbde8d9..dfa1bf79c5690289baeddd4498cb4c84dbd19bbe 100755 (executable)
@@ -210,4 +210,15 @@ test_expect_success 'clone, dissociate from partial reference and repack' '
        test_line_count = 1 packs.txt
 '
 
+test_expect_success 'clone, dissociate from alternates' '
+       rm -fr A B C &&
+       test_create_repo A &&
+       commit_in A file1 &&
+       git clone --reference=A A B &&
+       test_line_count = 1 B/.git/objects/info/alternates &&
+       git clone --local --dissociate B C &&
+       ! test -f C/.git/objects/info/alternates &&
+       ( cd C && git fsck )
+'
+
 test_done
index c9d3ed14c3a3238208b44ce815e37123298dea77..362b1581e092826400d78515cdc8d5cb0f7cfc29 100755 (executable)
@@ -242,13 +242,6 @@ clean_mark () {
        sort >$(basename "$1")
 }
 
-cmp_marks () {
-       test_when_finished "rm -rf git.marks testgit.marks" &&
-       clean_mark ".git/testgit/$1/git.marks" &&
-       clean_mark ".git/testgit/$1/testgit.marks" &&
-       test_cmp git.marks testgit.marks
-}
-
 test_expect_success 'proper failure checks for fetching' '
        (cd local &&
        test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error &&
@@ -258,12 +251,15 @@ test_expect_success 'proper failure checks for fetching' '
 '
 
 test_expect_success 'proper failure checks for pushing' '
+       test_when_finished "rm -rf local/git.marks local/testgit.marks" &&
        (cd local &&
        git checkout -b crash master &&
        echo crash >>file &&
        git commit -a -m crash &&
        test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git push --all &&
-       cmp_marks origin
+       clean_mark ".git/testgit/origin/git.marks" &&
+       clean_mark ".git/testgit/origin/testgit.marks" &&
+       test_cmp git.marks testgit.marks
        )
 '
 
index 878faf2b6318412df756f5b8cd2c00410f476e12..b7a7f9d5886f1e2f3fc2d65bfcf1785b890638ac 100755 (executable)
@@ -69,4 +69,32 @@ test_expect_success 'update backfilled tag without primary transfer' '
        test_cmp expect actual
 '
 
+
+test_expect_success 'set up fake git-daemon' '
+       mkdir remote &&
+       git init --bare remote/one.git &&
+       mkdir remote/host &&
+       git init --bare remote/host/two.git &&
+       write_script fake-daemon <<-\EOF &&
+       git daemon --inetd \
+               --informative-errors \
+               --export-all \
+               --base-path="$TRASH_DIRECTORY/remote" \
+               --interpolated-path="$TRASH_DIRECTORY/remote/%H%D" \
+               "$TRASH_DIRECTORY/remote"
+       EOF
+       export TRASH_DIRECTORY &&
+       PATH=$TRASH_DIRECTORY:$PATH
+'
+
+test_expect_success 'ext command can connect to git daemon (no vhost)' '
+       rm -rf dst &&
+       git clone "ext::fake-daemon %G/one.git" dst
+'
+
+test_expect_success 'ext command can connect to git daemon (vhost)' '
+       rm -rf dst &&
+       git clone "ext::fake-daemon %G/two.git %Vhost" dst
+'
+
 test_done
index 9e2c20374732d790edefc4cd87aa28223fbb24c4..e74662ba5c638de5acb4188e2cd0c5ff2456ec13 100755 (executable)
@@ -759,4 +759,139 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' '
        git bisect reset
 '
 
+test_expect_success 'bisect starts with only one new' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect new $HASH4 &&
+       git bisect next
+'
+
+test_expect_success 'bisect does not start with only one old' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect old $HASH1 &&
+       test_must_fail git bisect next
+'
+
+test_expect_success 'bisect start with one new and old' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect old $HASH1 &&
+       git bisect new $HASH4 &&
+       git bisect new &&
+       git bisect new >bisect_result &&
+       grep "$HASH2 is the first new commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect replay with old and new' '
+       git bisect replay log_to_replay.txt >bisect_result &&
+       grep "$HASH2 is the first new commit" bisect_result &&
+       git bisect reset
+'
+
+test_expect_success 'bisect cannot mix old/new and good/bad' '
+       git bisect start &&
+       git bisect bad $HASH4 &&
+       test_must_fail git bisect old $HASH1
+'
+
+test_expect_success 'bisect terms needs 0 or 1 argument' '
+       git bisect reset &&
+       test_must_fail git bisect terms only-one &&
+       test_must_fail git bisect terms 1 2 &&
+       test_must_fail git bisect terms 2>actual &&
+       echo "no terms defined" >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect terms shows good/bad after start' '
+       git bisect reset &&
+       git bisect start HEAD $HASH1 &&
+       git bisect terms --term-good >actual &&
+       echo good >expected &&
+       test_cmp expected actual &&
+       git bisect terms --term-bad >actual &&
+       echo bad >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect start with one term1 and term2' '
+       git bisect reset &&
+       git bisect start --term-old term2 --term-new term1 &&
+       git bisect term2 $HASH1 &&
+       git bisect term1 $HASH4 &&
+       git bisect term1 &&
+       git bisect term1 >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect replay with term1 and term2' '
+       git bisect replay log_to_replay.txt >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect reset
+'
+
+test_expect_success 'bisect start term1 term2' '
+       git bisect reset &&
+       git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 &&
+       git bisect term1 &&
+       git bisect term1 >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect cannot mix terms' '
+       git bisect reset &&
+       git bisect start --term-good term1 --term-bad term2 $HASH4 $HASH1 &&
+       test_must_fail git bisect a &&
+       test_must_fail git bisect b &&
+       test_must_fail git bisect bad &&
+       test_must_fail git bisect good &&
+       test_must_fail git bisect new &&
+       test_must_fail git bisect old
+'
+
+test_expect_success 'bisect terms rejects invalid terms' '
+       git bisect reset &&
+       test_must_fail git bisect start --term-good invalid..term &&
+       test_must_fail git bisect terms --term-bad invalid..term &&
+       test_must_fail git bisect terms --term-good bad &&
+       test_must_fail git bisect terms --term-good old &&
+       test_must_fail git bisect terms --term-good skip &&
+       test_must_fail git bisect terms --term-good reset &&
+       test_path_is_missing .git/BISECT_TERMS
+'
+
+test_expect_success 'bisect start --term-* does store terms' '
+       git bisect reset &&
+       git bisect start --term-bad=one --term-good=two &&
+       git bisect terms >actual &&
+       cat <<-EOF >expected &&
+       Your current terms are two for the old state
+       and one for the new state.
+       EOF
+       test_cmp expected actual &&
+       git bisect terms --term-bad >actual &&
+       echo one >expected &&
+       test_cmp expected actual &&
+       git bisect terms --term-good >actual &&
+       echo two >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect start takes options and revs in any order' '
+       git bisect reset &&
+       git bisect start --term-good one $HASH4 \
+               --term-good two --term-bad bad-term \
+               $HASH1 --term-good three -- &&
+       (git bisect terms --term-bad && git bisect terms --term-good) >actual &&
+       printf "%s\n%s\n" bad-term three >expected &&
+       test_cmp expected actual
+'
+
 test_done
diff --git a/t/t6031-merge-filemode.sh b/t/t6031-merge-filemode.sh
new file mode 100755 (executable)
index 0000000..7d06461
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='merge: handle file mode'
+. ./test-lib.sh
+
+test_expect_success 'set up mode change in one branch' '
+       : >file1 &&
+       git add file1 &&
+       git commit -m initial &&
+       git checkout -b a1 master &&
+       : >dummy &&
+       git add dummy &&
+       git commit -m a &&
+       git checkout -b b1 master &&
+       test_chmod +x file1 &&
+       git add file1 &&
+       git commit -m b1
+'
+
+do_one_mode () {
+       strategy=$1
+       us=$2
+       them=$3
+       test_expect_success "resolve single mode change ($strategy, $us)" '
+               git checkout -f $us &&
+               git merge -s $strategy $them &&
+               git ls-files -s file1 | grep ^100755
+       '
+
+       test_expect_success FILEMODE "verify executable bit on file ($strategy, $us)" '
+               test -x file1
+       '
+}
+
+do_one_mode recursive a1 b1
+do_one_mode recursive b1 a1
+do_one_mode resolve a1 b1
+do_one_mode resolve b1 a1
+
+test_expect_success 'set up mode change in both branches' '
+       git reset --hard HEAD &&
+       git checkout -b a2 master &&
+       : >file2 &&
+       H=$(git hash-object file2) &&
+       test_chmod +x file2 &&
+       git commit -m a2 &&
+       git checkout -b b2 master &&
+       : >file2 &&
+       git add file2 &&
+       git commit -m b2 &&
+       {
+               echo "100755 $H 2       file2"
+               echo "100644 $H 3       file2"
+       } >expect
+'
+
+do_both_modes () {
+       strategy=$1
+       test_expect_success "detect conflict on double mode change ($strategy)" '
+               git reset --hard &&
+               git checkout -f a2 &&
+               test_must_fail git merge -s $strategy b2 &&
+               git ls-files -u >actual &&
+               test_cmp actual expect &&
+               git ls-files -s file2 | grep ^100755
+       '
+
+       test_expect_success FILEMODE "verify executable bit on file ($strategy)" '
+               test -x file2
+       '
+}
+
+# both sides are equivalent, so no need to run both ways
+do_both_modes recursive
+do_both_modes resolve
+
+test_expect_success 'set up delete/modechange scenario' '
+       git reset --hard &&
+       git checkout -b deletion master &&
+       git rm file1 &&
+       git commit -m deletion
+'
+
+do_delete_modechange () {
+       strategy=$1
+       us=$2
+       them=$3
+       test_expect_success "detect delete/modechange conflict ($strategy, $us)" '
+               git reset --hard &&
+               git checkout $us &&
+               test_must_fail git merge -s $strategy $them
+       '
+}
+
+do_delete_modechange recursive b1 deletion
+do_delete_modechange recursive deletion b1
+do_delete_modechange resolve b1 deletion
+do_delete_modechange resolve deletion b1
+
+test_done
diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh
deleted file mode 100755 (executable)
index 6464a16..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/bin/sh
-
-test_description='merge-recursive: handle file mode'
-. ./test-lib.sh
-
-test_expect_success 'mode change in one branch: keep changed version' '
-       : >file1 &&
-       git add file1 &&
-       git commit -m initial &&
-       git checkout -b a1 master &&
-       : >dummy &&
-       git add dummy &&
-       git commit -m a &&
-       git checkout -b b1 master &&
-       test_chmod +x file1 &&
-       git add file1 &&
-       git commit -m b1 &&
-       git checkout a1 &&
-       git merge-recursive master -- a1 b1 &&
-       git ls-files -s file1 | grep ^100755
-'
-
-test_expect_success FILEMODE 'verify executable bit on file' '
-       test -x file1
-'
-
-test_expect_success 'mode change in both branches: expect conflict' '
-       git reset --hard HEAD &&
-       git checkout -b a2 master &&
-       : >file2 &&
-       H=$(git hash-object file2) &&
-       test_chmod +x file2 &&
-       git commit -m a2 &&
-       git checkout -b b2 master &&
-       : >file2 &&
-       git add file2 &&
-       git commit -m b2 &&
-       git checkout a2 &&
-       (
-               git merge-recursive master -- a2 b2
-               test $? = 1
-       ) &&
-       git ls-files -u >actual &&
-       (
-               echo "100755 $H 2       file2"
-               echo "100644 $H 3       file2"
-       ) >expect &&
-       test_cmp actual expect &&
-       git ls-files -s file2 | grep ^100755
-'
-
-test_expect_success FILEMODE 'verify executable bit on file' '
-       test -x file2
-'
-
-test_expect_success 'merging with triple rename across D/F conflict' '
-       git reset --hard HEAD &&
-       git checkout -b main &&
-       git rm -rf . &&
-
-       echo "just a file" >sub1 &&
-       mkdir -p sub2 &&
-       echo content1 >sub2/file1 &&
-       echo content2 >sub2/file2 &&
-       echo content3 >sub2/file3 &&
-       mkdir simple &&
-       echo base >simple/bar &&
-       git add -A &&
-       test_tick &&
-       git commit -m base &&
-
-       git checkout -b other &&
-       echo more >>simple/bar &&
-       test_tick &&
-       git commit -a -m changesimplefile &&
-
-       git checkout main &&
-       git rm sub1 &&
-       git mv sub2 sub1 &&
-       test_tick &&
-       git commit -m changefiletodir &&
-
-       test_tick &&
-       git merge other
-'
-
-test_done
index 7c9bec76302259beebeea7521b9c74acfe6de264..03873b09d1a4480549e1b8ab92fc3780fb874d89 100755 (executable)
@@ -8,8 +8,8 @@ test_description='for-each-ref test'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
 
-# Mon Jul 3 15:18:43 2006 +0000
-datestamp=1151939923
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
 setdate_and_increment () {
     GIT_COMMITTER_DATE="$datestamp +0200"
     datestamp=$(expr "$datestamp" + 1)
@@ -61,21 +61,21 @@ test_atom head object ''
 test_atom head type ''
 test_atom head '*objectname' ''
 test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151939924 +0200'
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
 test_atom head authorname 'A U Thor'
 test_atom head authoremail '<author@example.com>'
-test_atom head authordate 'Mon Jul 3 17:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151939923 +0200'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head committername 'C O Mitter'
 test_atom head committeremail '<committer@example.com>'
-test_atom head committerdate 'Mon Jul 3 17:18:43 2006 +0200'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head tag ''
 test_atom head tagger ''
 test_atom head taggername ''
 test_atom head taggeremail ''
 test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
-test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head subject 'Initial'
 test_atom head contents:subject 'Initial'
 test_atom head body ''
@@ -96,7 +96,7 @@ test_atom tag parent ''
 test_atom tag numparent ''
 test_atom tag object $(git rev-parse refs/tags/testtag^0)
 test_atom tag type 'commit'
-test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
+test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463'
 test_atom tag '*objecttype' 'commit'
 test_atom tag author ''
 test_atom tag authorname ''
@@ -107,18 +107,18 @@ test_atom tag committername ''
 test_atom tag committeremail ''
 test_atom tag committerdate ''
 test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151939925 +0200'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag taggername 'C O Mitter'
 test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
-test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151939927'
-test_atom tag contents:subject 'Tagging at 1151939927'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
 test_atom tag body ''
 test_atom tag contents:body ''
 test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151939927
+test_atom tag contents 'Tagging at 1151968727
 '
 test_atom tag HEAD ' '
 
@@ -146,95 +146,123 @@ test_expect_success 'Check invalid format specifiers are errors' '
        test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200'
-'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200'
-EOF
+test_date () {
+       f=$1 &&
+       committer_date=$2 &&
+       author_date=$3 &&
+       tagger_date=$4 &&
+       cat >expected <<-EOF &&
+       'refs/heads/master' '$committer_date' '$author_date'
+       'refs/tags/testtag' '$tagger_date'
+       EOF
+       (
+               git for-each-ref --shell \
+                       --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+                       refs/heads &&
+               git for-each-ref --shell \
+                       --format="%(refname) %(taggerdate${f:+:$f})" \
+                       refs/tags
+       ) >actual &&
+       test_cmp expected actual
+}
 
 test_expect_success 'Check unformatted date fields output' '
-       (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date "" \
+               "Tue Jul 4 01:18:43 2006 +0200" \
+               "Tue Jul 4 01:18:44 2006 +0200" \
+               "Tue Jul 4 01:18:45 2006 +0200"
 '
 
 test_expect_success 'Check format "default" formatted date fields output' '
-       f=default &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date default \
+               "Tue Jul 4 01:18:43 2006 +0200" \
+               "Tue Jul 4 01:18:44 2006 +0200" \
+               "Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+       test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
 '
 
 # Don't know how to do relative check because I can't know when this script
 # is going to be run and can't fake the current time to git, and hence can't
 # provide expected output.  Instead, I'll just make sure that "relative"
 # doesn't exit in error
-#
-#cat >expected <<\EOF
-#
-#EOF
-#
 test_expect_success 'Check format "relative" date fields output' '
        f=relative &&
        (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
        git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
 '
 
-cat >expected <<\EOF
-'refs/heads/master' '2006-07-03' '2006-07-03'
-'refs/tags/testtag' '2006-07-03'
-EOF
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+       test_date relative-local \
+               "$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
+               "$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
+               "$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
+'
 
 test_expect_success 'Check format "short" date fields output' '
-       f=short &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date short 2006-07-04 2006-07-04 2006-07-04
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006'
-'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006'
-EOF
+test_expect_success 'Check format "short-local" date fields output' '
+       test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
 
 test_expect_success 'Check format "local" date fields output' '
-       f=local &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date local \
+               "Mon Jul 3 23:18:43 2006" \
+               "Mon Jul 3 23:18:44 2006" \
+               "Mon Jul 3 23:18:45 2006"
 '
 
-cat >expected <<\EOF
-'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200'
-'refs/tags/testtag' '2006-07-03 17:18:45 +0200'
-EOF
-
 test_expect_success 'Check format "iso8601" date fields output' '
-       f=iso8601 &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date iso8601 \
+               "2006-07-04 01:18:43 +0200" \
+               "2006-07-04 01:18:44 +0200" \
+               "2006-07-04 01:18:45 +0200"
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200'
-'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200'
-EOF
+test_expect_success 'Check format "iso8601-local" date fields output' '
+       test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
 
 test_expect_success 'Check format "rfc2822" date fields output' '
-       f=rfc2822 &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date rfc2822 \
+               "Tue, 4 Jul 2006 01:18:43 +0200" \
+               "Tue, 4 Jul 2006 01:18:44 +0200" \
+               "Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+       test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+       test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+       test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
 '
 
 test_expect_success 'Check format of strftime date fields' '
-       echo "my date is 2006-07-03" >expected &&
+       echo "my date is 2006-07-04" >expected &&
        git for-each-ref \
          --format="%(authordate:format:my date is %Y-%m-%d)" \
          refs/heads >actual &&
        test_cmp expected actual
 '
 
+test_expect_success 'Check format of strftime-local date fields' '
+       echo "my date is 2006-07-03" >expected &&
+       git for-each-ref \
+         --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+         refs/heads >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'exercise strftime with odd fields' '
        echo >expected &&
        git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
@@ -546,8 +574,8 @@ body contents
 $sig"
 
 cat >expected <<EOF
-$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 $(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
+$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 EOF
 
 test_expect_success 'Verify sort with multiple keys' '
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
new file mode 100755 (executable)
index 0000000..fe4796c
--- /dev/null
@@ -0,0 +1,258 @@
+#!/bin/sh
+
+test_description='test for-each-refs usage of ref-filter APIs'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
+
+if ! test_have_prereq GPG
+then
+       skip_all="skipping for-each-ref tests, GPG not available"
+       test_done
+fi
+
+test_expect_success 'setup some history and refs' '
+       test_commit one &&
+       test_commit two &&
+       test_commit three &&
+       git checkout -b side &&
+       test_commit four &&
+       git tag -s -m "A signed tag message" signed-tag &&
+       git tag -s -m "Annonated doubly" double-tag signed-tag &&
+       git checkout master &&
+       git update-ref refs/odd/spot master
+'
+
+test_expect_success 'filtering with --points-at' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/three
+       EOF
+       git for-each-ref --format="%(refname)" --points-at=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check signed tags with --points-at' '
+       sed -e "s/Z$//" >expect <<-\EOF &&
+       refs/heads/side Z
+       refs/tags/four Z
+       refs/tags/signed-tag four
+       EOF
+       git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/one
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --no-merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/side
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       EOF
+       git for-each-ref --format="%(refname)" --no-merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --contains' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/heads/side
+       refs/odd/spot
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --contains=two >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(color) must fail' '
+       test_must_fail git for-each-ref --format="%(color)%(refname)"
+'
+
+test_expect_success 'left alignment is default' '
+       cat >expect <<-\EOF &&
+       refname is refs/heads/master  |refs/heads/master
+       refname is refs/heads/side    |refs/heads/side
+       refname is refs/odd/spot      |refs/odd/spot
+       refname is refs/tags/double-tag|refs/tags/double-tag
+       refname is refs/tags/four     |refs/tags/four
+       refname is refs/tags/one      |refs/tags/one
+       refname is refs/tags/signed-tag|refs/tags/signed-tag
+       refname is refs/tags/three    |refs/tags/three
+       refname is refs/tags/two      |refs/tags/two
+       EOF
+       git for-each-ref --format="%(align:30)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'middle alignment' '
+       cat >expect <<-\EOF &&
+       | refname is refs/heads/master |refs/heads/master
+       |  refname is refs/heads/side  |refs/heads/side
+       |   refname is refs/odd/spot   |refs/odd/spot
+       |refname is refs/tags/double-tag|refs/tags/double-tag
+       |  refname is refs/tags/four   |refs/tags/four
+       |   refname is refs/tags/one   |refs/tags/one
+       |refname is refs/tags/signed-tag|refs/tags/signed-tag
+       |  refname is refs/tags/three  |refs/tags/three
+       |   refname is refs/tags/two   |refs/tags/two
+       EOF
+       git for-each-ref --format="|%(align:middle,30)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'right alignment' '
+       cat >expect <<-\EOF &&
+       |  refname is refs/heads/master|refs/heads/master
+       |    refname is refs/heads/side|refs/heads/side
+       |      refname is refs/odd/spot|refs/odd/spot
+       |refname is refs/tags/double-tag|refs/tags/double-tag
+       |     refname is refs/tags/four|refs/tags/four
+       |      refname is refs/tags/one|refs/tags/one
+       |refname is refs/tags/signed-tag|refs/tags/signed-tag
+       |    refname is refs/tags/three|refs/tags/three
+       |      refname is refs/tags/two|refs/tags/two
+       EOF
+       git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+# Individual atoms inside %(align:...) and %(end) must not be quoted.
+
+test_expect_success 'alignment with format quote' "
+       cat >expect <<-\EOF &&
+       |'      '\''master| A U Thor'\''      '|
+       |'       '\''side| A U Thor'\''       '|
+       |'     '\''odd/spot| A U Thor'\''     '|
+       |'        '\''double-tag| '\''        '|
+       |'       '\''four| A U Thor'\''       '|
+       |'       '\''one| A U Thor'\''        '|
+       |'        '\''signed-tag| '\''        '|
+       |'      '\''three| A U Thor'\''       '|
+       |'       '\''two| A U Thor'\''        '|
+       EOF
+       git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual &&
+       test_cmp expect actual
+"
+
+test_expect_success 'nested alignment with quote formatting' "
+       cat >expect <<-\EOF &&
+       |'         master               '|
+       |'           side               '|
+       |'       odd/spot               '|
+       |'     double-tag               '|
+       |'           four               '|
+       |'            one               '|
+       |'     signed-tag               '|
+       |'          three               '|
+       |'            two               '|
+       EOF
+       git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&
+       test_cmp expect actual
+"
+
+test_expect_success 'check `%(contents:lines=1)`' '
+       cat >expect <<-\EOF &&
+       master |three
+       side |four
+       odd/spot |three
+       double-tag |Annonated doubly
+       four |four
+       one |one
+       signed-tag |A signed tag message
+       three |three
+       two |two
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=1)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check `%(contents:lines=0)`' '
+       cat >expect <<-\EOF &&
+       master |
+       side |
+       odd/spot |
+       double-tag |
+       four |
+       one |
+       signed-tag |
+       three |
+       two |
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check `%(contents:lines=99999)`' '
+       cat >expect <<-\EOF &&
+       master |three
+       side |four
+       odd/spot |three
+       double-tag |Annonated doubly
+       four |four
+       one |one
+       signed-tag |A signed tag message
+       three |three
+       two |two
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=99999)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '`%(contents:lines=-1)` should fail' '
+       test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)"
+'
+
+test_expect_success 'setup for version sort' '
+       test_commit foo1.3 &&
+       test_commit foo1.6 &&
+       test_commit foo1.10
+'
+
+test_expect_success 'version sort' '
+       git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.3
+       foo1.6
+       foo1.10
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'version sort (shortened)' '
+       git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.3
+       foo1.6
+       foo1.10
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'reverse version sort' '
+       git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.10
+       foo1.6
+       foo1.3
+       EOF
+       test_cmp expect actual
+'
+
+test_done
index d31788cc6ce6a5698c34ffe6ee8a59fccacab90c..3dd2f51e49d7e6824382ac415cf64842fa3b757f 100755 (executable)
@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '
 
 test_expect_success 'invalid sort parameter in configuratoin' '
        git config tag.sort "v:notvalid" &&
-       git tag -l "foo*" >actual &&
-       cat >expect <<-\EOF &&
-       foo1.10
-       foo1.3
-       foo1.6
-       EOF
-       test_cmp expect actual
+       test_must_fail git tag -l "foo*"
 '
 
 test_expect_success 'version sort with prerelease reordering' '
@@ -1525,4 +1519,43 @@ EOF"
        test_cmp expect actual
 '
 
+test_expect_success '--format should list tags as per format given' '
+       cat >expect <<-\EOF &&
+       refname : refs/tags/foo1.10
+       refname : refs/tags/foo1.3
+       refname : refs/tags/foo1.6
+       refname : refs/tags/foo1.6-rc1
+       refname : refs/tags/foo1.6-rc2
+       EOF
+       git tag -l --format="refname : %(refname)" "foo*" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup --merged test tags' '
+       git tag mergetest-1 HEAD~2 &&
+       git tag mergetest-2 HEAD~1 &&
+       git tag mergetest-3 HEAD
+'
+
+test_expect_success '--merged cannot be used in non-list mode' '
+       test_must_fail git tag --merged=mergetest-2 foo
+'
+
+test_expect_success '--merged shows merged tags' '
+       cat >expect <<-\EOF &&
+       mergetest-1
+       mergetest-2
+       EOF
+       git tag -l --merged=mergetest-2 mergetest-* >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--no-merged show unmerged tags' '
+       cat >expect <<-\EOF &&
+       mergetest-3
+       EOF
+       git tag -l --no-merged=mergetest-2 mergetest-* >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 32d81765cb53718e5d26092ccfe970a503edfa02..44bf1d84af574509c5a8f6f05721d7c4f9719439 100755 (executable)
@@ -213,5 +213,19 @@ EOF
        git checkout master
 '
 
+test_expect_success 'status --branch with detached HEAD' '
+       git reset --hard &&
+       git checkout master^0 &&
+       git status --branch --porcelain >actual &&
+       cat >expected <<-EOF &&
+       ## HEAD (no branch)
+       ?? .gitconfig
+       ?? actual
+       ?? expect
+       ?? expected
+       ?? mdconflict/
+       EOF
+       test_i18ncmp expected actual
+'
 
 test_done
index 7eeb207b32b48041037488f42e645b8b12742690..6f12b235b3a9330497e814d5e8f564aec94d0c44 100755 (executable)
@@ -174,9 +174,9 @@ test_expect_success 'mergetool skips autoresolved' '
 '
 
 test_expect_success 'mergetool merges all from subdir' '
+       test_config rerere.enabled false &&
        (
                cd subdir &&
-               test_config rerere.enabled false &&
                test_must_fail git merge master &&
                ( yes "r" | git mergetool ../submod ) &&
                ( yes "d" "d" | git mergetool --no-prompt ) &&
index ea35a0241c201ac12c29603a21421199dd097600..ec8bc8c7656fd7f0fd03226b5491f5bc9d532dd7 100755 (executable)
@@ -492,12 +492,31 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
 
 test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
        git submodule add ./. submod/ule &&
+       test_config -C submod/ule diff.tool checktrees &&
+       test_config -C submod/ule difftool.checktrees.cmd '\''
+               test -d "$LOCAL" && test -d "$REMOTE" && echo good
+               '\'' &&
        (
                cd submod/ule &&
-               test_config diff.tool checktrees &&
-               test_config difftool.checktrees.cmd '\''
-                       test -d "$LOCAL" && test -d "$REMOTE" && echo good
-               '\'' &&
+               echo good >expect &&
+               git difftool --tool=checktrees --dir-diff HEAD~ >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' '
+       git init dirlinks &&
+       (
+               cd dirlinks &&
+               git config diff.tool checktrees &&
+               git config difftool.checktrees.cmd "echo good" &&
+               mkdir foo &&
+               : >foo/bar &&
+               git add foo/bar &&
+               test_commit symlink-one &&
+               ln -s foo link &&
+               git add link &&
+               test_commit symlink-two &&
                echo good >expect &&
                git difftool --tool=checktrees --dir-diff HEAD~ >actual &&
                test_cmp expect actual
diff --git a/t/t8009-blame-vs-topicbranches.sh b/t/t8009-blame-vs-topicbranches.sh
new file mode 100755 (executable)
index 0000000..72596e3
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='blaming trough history with topic branches'
+. ./test-lib.sh
+
+# Creates the history shown below. '*'s mark the first parent in the merges.
+# The only line of file.t is changed in commit B2
+#
+#        +---C1
+#       /      \
+# A0--A1--*A2--*A3
+#   \     /
+#    B1-B2
+#
+test_expect_success setup '
+       test_commit A0 file.t line0 &&
+       test_commit A1 &&
+       git reset --hard A0 &&
+       test_commit B1 &&
+       test_commit B2 file.t line0changed &&
+       git reset --hard A1 &&
+       test_merge A2 B2 &&
+       git reset --hard A1 &&
+       test_commit C1 &&
+       git reset --hard A2 &&
+       test_merge A3 C1
+       '
+
+test_expect_success 'blame --reverse --first-parent finds A1' '
+       git blame --porcelain --reverse --first-parent A0..A3 -- file.t >actual_full &&
+       head -n 1 <actual_full | sed -e "s/ .*//" >actual &&
+       git rev-parse A1 >expect &&
+       test_cmp expect actual
+       '
+
+test_done
index 4cff6a760fc27b4f4f87c5f0387e34b3039e5518..37b42d03a2b4f6392c3b84303c52a839caa6befd 100755 (executable)
@@ -417,11 +417,8 @@ test_expect_success 'cleanup chmod after submit cancel' '
                ! p4 fstat -T action text &&
                test_path_is_file text+x &&
                ! p4 fstat -T action text+x &&
-               if test_have_prereq !CYGWIN
-               then
-                       stat --format=%A text | egrep ^-r-- &&
-                       stat --format=%A text+x | egrep ^-r-x
-               fi
+               ls -l text | egrep ^-r-- &&
+               ls -l text+x | egrep ^-r-x
        )
 '
 
index 78f1d0f92d9e55641a225bc0cefca69822e17f47..d808c008c11700badffdcbbd34faba39428a47f1 100755 (executable)
@@ -4,6 +4,12 @@ test_description='interaction with P4 case-folding'
 
 . ./lib-git-p4.sh
 
+if test_have_prereq CASE_INSENSITIVE_FS
+then
+       skip_all='skipping P4 case-folding tests; case insensitive file system detected'
+       test_done
+fi
+
 test_expect_success 'start p4d with case folding enabled' '
        start_p4d -C1
 '
diff --git a/t/t9822-git-p4-path-encoding.sh b/t/t9822-git-p4-path-encoding.sh
new file mode 100755 (executable)
index 0000000..7b83e69
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='Clone repositories with non ASCII paths'
+
+. ./lib-git-p4.sh
+
+UTF8_ESCAPED="a-\303\244_o-\303\266_u-\303\274.txt"
+ISO8859_ESCAPED="a-\344_o-\366_u-\374.txt"
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'Create a repo containing iso8859-1 encoded paths' '
+       (
+               cd "$cli" &&
+               ISO8859="$(printf "$ISO8859_ESCAPED")" &&
+               echo content123 >"$ISO8859" &&
+               p4 add "$ISO8859" &&
+               p4 submit -d "test commit"
+       )
+'
+
+test_expect_failure 'Clone auto-detects depot with iso8859-1 paths' '
+       git p4 clone --destination="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               UTF8="$(printf "$UTF8_ESCAPED")" &&
+               echo "$UTF8" >expect &&
+               git -c core.quotepath=false ls-files >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'Clone repo containing iso8859-1 encoded paths with git-p4.pathEncoding' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.pathEncoding iso8859-1 &&
+               git p4 clone --use-client-spec --destination="$git" //depot &&
+               UTF8="$(printf "$UTF8_ESCAPED")" &&
+               echo "$UTF8" >expect &&
+               git -c core.quotepath=false ls-files >actual &&
+               test_cmp expect actual &&
+
+               echo content123 >expect &&
+               cat "$UTF8" >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9823-git-p4-mock-lfs.sh b/t/t9823-git-p4-mock-lfs.sh
new file mode 100755 (executable)
index 0000000..1f2dc36
--- /dev/null
@@ -0,0 +1,192 @@
+#!/bin/sh
+
+test_description='Clone repositories and store files in Mock LFS'
+
+. ./lib-git-p4.sh
+
+test_file_is_not_in_mock_lfs () {
+       FILE="$1" &&
+       CONTENT="$2" &&
+       echo "$CONTENT" >expect_content &&
+       test_path_is_file "$FILE" &&
+       test_cmp expect_content "$FILE"
+}
+
+test_file_is_in_mock_lfs () {
+       FILE="$1" &&
+       CONTENT="$2" &&
+       LOCAL_STORAGE=".git/mock-storage/local/$CONTENT" &&
+       SERVER_STORAGE=".git/mock-storage/remote/$CONTENT" &&
+       echo "pointer-$CONTENT" >expect_pointer &&
+       echo "$CONTENT" >expect_content &&
+       test_path_is_file "$FILE" &&
+       test_path_is_file "$LOCAL_STORAGE" &&
+       test_path_is_file "$SERVER_STORAGE" &&
+       test_cmp expect_pointer "$FILE" &&
+       test_cmp expect_content "$LOCAL_STORAGE" &&
+       test_cmp expect_content "$SERVER_STORAGE"
+}
+
+test_file_is_deleted_in_mock_lfs () {
+       FILE="$1" &&
+       CONTENT="$2" &&
+       LOCAL_STORAGE=".git/mock-storage/local/$CONTENT" &&
+       SERVER_STORAGE=".git/mock-storage/remote/$CONTENT" &&
+       echo "pointer-$CONTENT" >expect_pointer &&
+       echo "$CONTENT" >expect_content &&
+       test_path_is_missing "$FILE" &&
+       test_path_is_file "$LOCAL_STORAGE" &&
+       test_path_is_file "$SERVER_STORAGE" &&
+       test_cmp expect_content "$LOCAL_STORAGE" &&
+       test_cmp expect_content "$SERVER_STORAGE"
+}
+
+test_file_count_in_dir () {
+       DIR="$1" &&
+       EXPECTED_COUNT="$2" &&
+       find "$DIR" -type f >actual &&
+       test_line_count = $EXPECTED_COUNT actual
+}
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'Create repo with binary files' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+
+               echo "content 1 txt 23 bytes" >file1.txt &&
+               p4 add file1.txt &&
+               echo "content 2-3 bin 25 bytes" >file2.dat &&
+               p4 add file2.dat &&
+               p4 submit -d "Add text and binary file" &&
+
+               mkdir "path with spaces" &&
+               echo "content 2-3 bin 25 bytes" >"path with spaces/file3.bin" &&
+               p4 add "path with spaces/file3.bin" &&
+               p4 submit -d "Add another binary file with same content and spaces in path" &&
+
+               echo "content 4 bin 26 bytes XX" >file4.bin &&
+               p4 add file4.bin &&
+               p4 submit -d "Add another binary file with different content"
+       )
+'
+
+test_expect_success 'Store files in Mock LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 2 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 2
+       )
+'
+
+test_expect_success 'Store files in Mock LFS based on extension (dat)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 1 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 1
+       )
+'
+
+test_expect_success 'Store files in Mock LFS based on extension (dat) and use git p4 sync and no client spec' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git config git-p4.largeFilePush True &&
+               git p4 sync //depot &&
+               git checkout p4/master &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 1 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 1
+       )
+'
+
+test_expect_success 'Remove file from repo and store files in Mock LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 delete file4.bin &&
+               p4 submit -d "Remove file"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_deleted_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 2 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 2
+       )
+'
+
+test_expect_success 'Run git p4 submit in repo configured with large file system' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_must_fail git p4 submit
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh
new file mode 100755 (executable)
index 0000000..0b664a3
--- /dev/null
@@ -0,0 +1,288 @@
+#!/bin/sh
+
+test_description='Clone repositories and store files in Git LFS'
+
+. ./lib-git-p4.sh
+
+git lfs help >/dev/null 2>&1 || {
+       skip_all='skipping git p4 Git LFS tests; Git LFS not found'
+       test_done
+}
+
+test_file_in_lfs () {
+       FILE="$1" &&
+       SIZE="$2" &&
+       EXPECTED_CONTENT="$3" &&
+       cat "$FILE" | grep "size $SIZE" &&
+       HASH=$(cat "$FILE" | grep "oid sha256:" | sed -e "s/oid sha256://g") &&
+       LFS_FILE=".git/lfs/objects/$(echo "$HASH" | cut -c1-2)/$(echo "$HASH" | cut -c3-4)/$HASH" &&
+       echo $EXPECTED_CONTENT >expect &&
+       test_path_is_file "$FILE" &&
+       test_path_is_file "$LFS_FILE" &&
+       test_cmp expect "$LFS_FILE"
+}
+
+test_file_count_in_dir () {
+       DIR="$1" &&
+       EXPECTED_COUNT="$2" &&
+       find "$DIR" -type f >actual &&
+       test_line_count = $EXPECTED_COUNT actual
+}
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'Create repo with binary files' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+
+               echo "content 1 txt 23 bytes" >file1.txt &&
+               p4 add file1.txt &&
+               echo "content 2-3 bin 25 bytes" >file2.dat &&
+               p4 add file2.dat &&
+               p4 submit -d "Add text and binary file" &&
+
+               mkdir "path with spaces" &&
+               echo "content 2-3 bin 25 bytes" >"path with spaces/file3.bin" &&
+               p4 add "path with spaces/file3.bin" &&
+               p4 submit -d "Add another binary file with same content and spaces in path" &&
+
+               echo "content 4 bin 26 bytes XX" >file4.bin &&
+               p4 add file4.bin &&
+               p4 submit -d "Add another binary file with different content"
+       )
+'
+
+test_expect_success 'Store files in LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file2.dat filter=lfs -text
+               /file4.bin filter=lfs -text
+               /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Store files in LFS based on size (>25 bytes)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 25 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" &&
+               test_file_count_in_dir ".git/lfs/objects" 1 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file4.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Store files in LFS based on extension (dat)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_count_in_dir ".git/lfs/objects" 1 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               *.dat filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Store files in LFS based on size (>25 bytes) and extension (dat)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git config git-p4.largeFileThreshold 25 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" &&
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               *.dat filter=lfs -text
+               /file4.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Remove file from repo and store files in LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 delete file4.bin &&
+               p4 submit -d "Remove file"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" &&
+               test_path_is_missing file4.bin &&
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file2.dat filter=lfs -text
+               /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Add .gitattributes and store files in LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               echo "*.txt text" >.gitattributes &&
+               p4 add .gitattributes &&
+               p4 submit -d "Add .gitattributes"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" &&
+               test_path_is_missing file4.bin &&
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+               *.txt text
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file2.dat filter=lfs -text
+               /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Add big files to repo and store files in LFS based on compressed size (>28 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               echo "content 5 bin 40 bytes XXXXXXXXXXXXXXXX" >file5.bin &&
+               p4 add file5.bin &&
+               p4 submit -d "Add file with small footprint after compression" &&
+
+               echo "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" >file6.bin &&
+               p4 add file6.bin &&
+               p4 submit -d "Add file with large footprint after compression"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileCompressedThreshold 28 &&
+               # We only import HEAD here ("@all" is missing!)
+               git p4 clone --destination="$git" //depot &&
+
+               test_file_in_lfs file6.bin 13 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ"
+               test_file_count_in_dir ".git/lfs/objects" 1 &&
+
+               cat >expect <<-\EOF &&
+               *.txt text
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file6.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index e8d3c0fdbc76d93ea18f6da1c869fc963e7ca81d..73e37a1f6c23d00371dbd3f10aee1648e8b947bc 100644 (file)
@@ -145,6 +145,14 @@ test_pause () {
        fi
 }
 
+# Wrap git in gdb. Adding this to a command can make it easier to
+# understand what is going on in a failing test.
+#
+# Example: "debug git checkout master".
+debug () {
+        GIT_TEST_GDB=1 "$@"
+}
+
 # Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]"
 #
 # This will commit a file with the given contents and the given commit
@@ -201,7 +209,14 @@ test_chmod () {
 
 # Unset a configuration variable, but don't fail if it doesn't exist.
 test_unconfig () {
-       git config --unset-all "$@"
+       config_dir=
+       if test "$1" = -C
+       then
+               shift
+               config_dir=$1
+               shift
+       fi
+       git ${config_dir:+-C "$config_dir"} config --unset-all "$@"
        config_status=$?
        case "$config_status" in
        5) # ok, nothing to unset
@@ -213,8 +228,15 @@ test_unconfig () {
 
 # Set git config, automatically unsetting it after the test is over.
 test_config () {
-       test_when_finished "test_unconfig '$1'" &&
-       git config "$@"
+       config_dir=
+       if test "$1" = -C
+       then
+               shift
+               config_dir=$1
+               shift
+       fi
+       test_when_finished "test_unconfig ${config_dir:+-C '$config_dir'} '$1'" &&
+       git ${config_dir:+-C "$config_dir"} config "$@"
 }
 
 test_config_global () {
@@ -722,6 +744,11 @@ test_seq () {
 # what went wrong.
 
 test_when_finished () {
+       # We cannot detect when we are in a subshell in general, but by
+       # doing so on Bash is better than nothing (the test will
+       # silently pass on other shells).
+       test "${BASH_SUBSHELL-0}" = 0 ||
+       error "bug in test script: test_when_finished does nothing in a subshell"
        test_cleanup="{ $*
                } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
 }
diff --git a/tag.c b/tag.c
index 5b0ac62ed846188ad44ceac41d278ff37ec8bd9d..5b2a06d92b75c7aea3f8cfb1cadb33d9da27f3c6 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -82,7 +82,7 @@ int parse_tag_buffer(struct tag *item, const void *data, unsigned long size)
        nl = memchr(bufptr, '\n', tail - bufptr);
        if (!nl || sizeof(type) <= (nl - bufptr))
                return -1;
-       strncpy(type, bufptr, nl - bufptr);
+       memcpy(type, bufptr, nl - bufptr);
        type[nl - bufptr] = '\0';
        bufptr = nl + 1;
 
index 54c0872fcb18edb4ca28a63fdfd4be3a17df388e..bb53c0aa655c7a2df09638024b304a79e8b07fc6 100644 (file)
@@ -47,7 +47,7 @@ static int dump_cache_tree(struct cache_tree *it,
                struct cache_tree_sub *rdwn;
 
                rdwn = cache_tree_sub(ref, down->name);
-               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name);
                if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
                        errs = 1;
        }
diff --git a/trace.c b/trace.c
index 7393926ebcd95404fe16cdd84e40fd4f1bb02f40..4aeea60973100bfed212842cb46a5a81154b7781 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -277,25 +277,24 @@ void trace_performance_fl(const char *file, int line, uint64_t nanos,
 
 static const char *quote_crnl(const char *path)
 {
-       static char new_path[PATH_MAX];
-       const char *p2 = path;
-       char *p1 = new_path;
+       static struct strbuf new_path = STRBUF_INIT;
 
        if (!path)
                return NULL;
 
-       while (*p2) {
-               switch (*p2) {
-               case '\\': *p1++ = '\\'; *p1++ = '\\'; break;
-               case '\n': *p1++ = '\\'; *p1++ = 'n'; break;
-               case '\r': *p1++ = '\\'; *p1++ = 'r'; break;
+       strbuf_reset(&new_path);
+
+       while (*path) {
+               switch (*path) {
+               case '\\': strbuf_addstr(&new_path, "\\\\"); break;
+               case '\n': strbuf_addstr(&new_path, "\\n"); break;
+               case '\r': strbuf_addstr(&new_path, "\\r"); break;
                default:
-                       *p1++ = *p2;
+                       strbuf_addch(&new_path, *path);
                }
-               p2++;
+               path++;
        }
-       *p1 = '\0';
-       return new_path;
+       return new_path.buf;
 }
 
 /* FIXME: move prefix to startup_info struct and get rid of this arg */
index 863eb524f9087302be6b99c89f071195dd1258b8..23b2ed6f0cf6f521c4d5afa6c1cffe0786d29a5f 100644 (file)
@@ -654,23 +654,24 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
                        "[new branch]"),
                        ref, ref->peer_ref, NULL, porcelain);
        else {
-               char quickref[84];
+               struct strbuf quickref = STRBUF_INIT;
                char type;
                const char *msg;
 
-               strcpy(quickref, status_abbrev(ref->old_sha1));
+               strbuf_addstr(&quickref, status_abbrev(ref->old_sha1));
                if (ref->forced_update) {
-                       strcat(quickref, "...");
+                       strbuf_addstr(&quickref, "...");
                        type = '+';
                        msg = "forced update";
                } else {
-                       strcat(quickref, "..");
+                       strbuf_addstr(&quickref, "..");
                        type = ' ';
                        msg = NULL;
                }
-               strcat(quickref, status_abbrev(ref->new_sha1));
+               strbuf_addstr(&quickref, status_abbrev(ref->new_sha1));
 
-               print_ref_status(type, quickref, ref, ref->peer_ref, msg, porcelain);
+               print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, porcelain);
+               strbuf_release(&quickref);
        }
 }
 
index f932e80e862cfafb7b909059b460dcb688c6dc0b..8e2032f4e592910d6f3336f95019647992113877 100644 (file)
@@ -1350,9 +1350,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
         * Then we need to make sure that we do not lose a locally
         * present file that is not ignored.
         */
-       pathbuf = xmalloc(namelen + 2);
-       memcpy(pathbuf, ce->name, namelen);
-       strcpy(pathbuf+namelen, "/");
+       pathbuf = xstrfmt("%.*s/", namelen, ce->name);
 
        memset(&d, 0, sizeof(d));
        if (o->dir)
index 89e832b64a0548ec79802dfc6911eff9f5c353be..d0bc3ca07ab1b005884c80901ea8dee04452b71a 100644 (file)
@@ -316,10 +316,8 @@ static int reachable(struct commit *want)
 
        commit_list_insert_by_date(want, &work);
        while (work) {
-               struct commit_list *list = work->next;
-               struct commit *commit = work->item;
-               free(work);
-               work = list;
+               struct commit_list *list;
+               struct commit *commit = pop_commit(&work);
 
                if (commit->object.flags & THEY_HAVE) {
                        want->object.flags |= COMMON_KNOWN;
diff --git a/url.c b/url.c
index 7ca2a69e1091fc57cb292052015c920499fe3379..2d89ad190cfe1c57cd20194661329551ff229993 100644 (file)
--- a/url.c
+++ b/url.c
@@ -120,8 +120,7 @@ char *url_decode_parameter_value(const char **query)
 void end_url_with_slash(struct strbuf *buf, const char *url)
 {
        strbuf_addstr(buf, url);
-       if (buf->len && buf->buf[buf->len - 1] != '/')
-               strbuf_addch(buf, '/');
+       strbuf_complete(buf, '/');
 }
 
 void str_end_url_with_slash(const char *url, char **dest) {
diff --git a/utf8.c b/utf8.c
index 28e6d76a425db4c16a8cc32e6f17c7aa72c2b1f0..00e10c86ad7ea7bcc2d53a90a2f608dd66d47462 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -644,3 +644,24 @@ int skip_utf8_bom(char **text, size_t len)
        *text += strlen(utf8_bom);
        return 1;
 }
+
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+                      const char *s)
+{
+       int slen = strlen(s);
+       int display_len = utf8_strnwidth(s, slen, 0);
+       int utf8_compensation = slen - display_len;
+
+       if (display_len >= width) {
+               strbuf_addstr(buf, s);
+               return;
+       }
+
+       if (position == ALIGN_LEFT)
+               strbuf_addf(buf, "%-*s", width + utf8_compensation, s);
+       else if (position == ALIGN_MIDDLE) {
+               int left = (width - display_len) / 2;
+               strbuf_addf(buf, "%*s%-*s", left, "", width - left + utf8_compensation, s);
+       } else if (position == ALIGN_RIGHT)
+               strbuf_addf(buf, "%*s", width + utf8_compensation, s);
+}
diff --git a/utf8.h b/utf8.h
index 5a9e94bee62fd2ed62dcbc82aee12bfc6d75e254..7930b44f19c701cc671d9639289782abb1812034 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -55,4 +55,19 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
  */
 int is_hfs_dotgit(const char *path);
 
+typedef enum {
+       ALIGN_LEFT,
+       ALIGN_MIDDLE,
+       ALIGN_RIGHT
+} align_type;
+
+/*
+ * Align the string given and store it into a strbuf as per the
+ * 'position' and 'width'. If the given string length is larger than
+ * 'width' than then the input string is not truncated and no
+ * alignment is done.
+ */
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+                      const char *s);
+
 #endif
index 44a936c1cfddfac538afa66e76cea295577b136d..cdeb63f319abc088fe54e4291ed765900d16f1fd 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -17,10 +17,9 @@ void walker_say(struct walker *walker, const char *fmt, const char *hex)
 
 static void report_missing(const struct object *obj)
 {
-       char missing_hex[41];
-       strcpy(missing_hex, sha1_to_hex(obj->sha1));
        fprintf(stderr, "Cannot obtain needed %s %s\n",
-               obj->type ? typename(obj->type): "object", missing_hex);
+               obj->type ? typename(obj->type): "object",
+               sha1_to_hex(obj->sha1));
        if (!is_null_sha1(current_commit_sha1))
                fprintf(stderr, "while processing commit %s.\n",
                        sha1_to_hex(current_commit_sha1));
diff --git a/worktree.c b/worktree.c
new file mode 100644 (file)
index 0000000..981f810
--- /dev/null
@@ -0,0 +1,219 @@
+#include "cache.h"
+#include "refs.h"
+#include "strbuf.h"
+#include "worktree.h"
+
+void free_worktrees(struct worktree **worktrees)
+{
+       int i = 0;
+
+       for (i = 0; worktrees[i]; i++) {
+               free(worktrees[i]->path);
+               free(worktrees[i]->git_dir);
+               free(worktrees[i]->head_ref);
+               free(worktrees[i]);
+       }
+       free (worktrees);
+}
+
+/*
+ * read 'path_to_ref' into 'ref'.  Also if is_detached is not NULL,
+ * set is_detached to 1 (0) if the ref is detatched (is not detached).
+ *
+ * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
+ * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
+ * git_path). Parse the ref ourselves.
+ *
+ * return -1 if the ref is not a proper ref, 0 otherwise (success)
+ */
+static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
+{
+       if (is_detached)
+               *is_detached = 0;
+       if (!strbuf_readlink(ref, path_to_ref, 0)) {
+               /* HEAD is symbolic link */
+               if (!starts_with(ref->buf, "refs/") ||
+                               check_refname_format(ref->buf, 0))
+                       return -1;
+       } else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
+               /* textual symref or detached */
+               if (!starts_with(ref->buf, "ref:")) {
+                       if (is_detached)
+                               *is_detached = 1;
+               } else {
+                       strbuf_remove(ref, 0, strlen("ref:"));
+                       strbuf_trim(ref);
+                       if (check_refname_format(ref->buf, 0))
+                               return -1;
+               }
+       } else
+               return -1;
+       return 0;
+}
+
+/**
+ * Add the head_sha1 and head_ref (if not detached) to the given worktree
+ */
+static void add_head_info(struct strbuf *head_ref, struct worktree *worktree)
+{
+       if (head_ref->len) {
+               if (worktree->is_detached) {
+                       get_sha1_hex(head_ref->buf, worktree->head_sha1);
+               } else {
+                       resolve_ref_unsafe(head_ref->buf, 0, worktree->head_sha1, NULL);
+                       worktree->head_ref = strbuf_detach(head_ref, NULL);
+               }
+       }
+}
+
+/**
+ * get the main worktree
+ */
+static struct worktree *get_main_worktree(void)
+{
+       struct worktree *worktree = NULL;
+       struct strbuf path = STRBUF_INIT;
+       struct strbuf worktree_path = STRBUF_INIT;
+       struct strbuf gitdir = STRBUF_INIT;
+       struct strbuf head_ref = STRBUF_INIT;
+       int is_bare = 0;
+       int is_detached = 0;
+
+       strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir()));
+       strbuf_addbuf(&worktree_path, &gitdir);
+       is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
+       if (is_bare)
+               strbuf_strip_suffix(&worktree_path, "/.");
+
+       strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+
+       if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
+               goto done;
+
+       worktree = xmalloc(sizeof(struct worktree));
+       worktree->path = strbuf_detach(&worktree_path, NULL);
+       worktree->git_dir = strbuf_detach(&gitdir, NULL);
+       worktree->is_bare = is_bare;
+       worktree->head_ref = NULL;
+       worktree->is_detached = is_detached;
+       add_head_info(&head_ref, worktree);
+
+done:
+       strbuf_release(&path);
+       strbuf_release(&gitdir);
+       strbuf_release(&worktree_path);
+       strbuf_release(&head_ref);
+       return worktree;
+}
+
+static struct worktree *get_linked_worktree(const char *id)
+{
+       struct worktree *worktree = NULL;
+       struct strbuf path = STRBUF_INIT;
+       struct strbuf worktree_path = STRBUF_INIT;
+       struct strbuf gitdir = STRBUF_INIT;
+       struct strbuf head_ref = STRBUF_INIT;
+       int is_detached = 0;
+
+       if (!id)
+               die("Missing linked worktree name");
+
+       strbuf_addf(&gitdir, "%s/worktrees/%s",
+                       absolute_path(get_git_common_dir()), id);
+       strbuf_addf(&path, "%s/gitdir", gitdir.buf);
+       if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
+               /* invalid gitdir file */
+               goto done;
+
+       strbuf_rtrim(&worktree_path);
+       if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
+               strbuf_reset(&worktree_path);
+               strbuf_addstr(&worktree_path, absolute_path("."));
+               strbuf_strip_suffix(&worktree_path, "/.");
+       }
+
+       strbuf_reset(&path);
+       strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
+
+       if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
+               goto done;
+
+       worktree = xmalloc(sizeof(struct worktree));
+       worktree->path = strbuf_detach(&worktree_path, NULL);
+       worktree->git_dir = strbuf_detach(&gitdir, NULL);
+       worktree->is_bare = 0;
+       worktree->head_ref = NULL;
+       worktree->is_detached = is_detached;
+       add_head_info(&head_ref, worktree);
+
+done:
+       strbuf_release(&path);
+       strbuf_release(&gitdir);
+       strbuf_release(&worktree_path);
+       strbuf_release(&head_ref);
+       return worktree;
+}
+
+struct worktree **get_worktrees(void)
+{
+       struct worktree **list = NULL;
+       struct strbuf path = STRBUF_INIT;
+       DIR *dir;
+       struct dirent *d;
+       int counter = 0, alloc = 2;
+
+       list = xmalloc(alloc * sizeof(struct worktree *));
+
+       if ((list[counter] = get_main_worktree()))
+               counter++;
+
+       strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
+       dir = opendir(path.buf);
+       strbuf_release(&path);
+       if (dir) {
+               while ((d = readdir(dir)) != NULL) {
+                       struct worktree *linked = NULL;
+                       if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+                               continue;
+
+                               if ((linked = get_linked_worktree(d->d_name))) {
+                                       ALLOC_GROW(list, counter + 1, alloc);
+                                       list[counter++] = linked;
+                               }
+               }
+               closedir(dir);
+       }
+       ALLOC_GROW(list, counter + 1, alloc);
+       list[counter] = NULL;
+       return list;
+}
+
+char *find_shared_symref(const char *symref, const char *target)
+{
+       char *existing = NULL;
+       struct strbuf path = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT;
+       struct worktree **worktrees = get_worktrees();
+       int i = 0;
+
+       for (i = 0; worktrees[i]; i++) {
+               strbuf_reset(&path);
+               strbuf_reset(&sb);
+               strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref);
+
+               if (parse_ref(path.buf, &sb, NULL)) {
+                       continue;
+               }
+
+               if (!strcmp(sb.buf, target)) {
+                       existing = xstrdup(worktrees[i]->path);
+                       break;
+               }
+       }
+
+       strbuf_release(&path);
+       strbuf_release(&sb);
+       free_worktrees(worktrees);
+
+       return existing;
+}
diff --git a/worktree.h b/worktree.h
new file mode 100644 (file)
index 0000000..b4b3dda
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef WORKTREE_H
+#define WORKTREE_H
+
+struct worktree {
+       char *path;
+       char *git_dir;
+       char *head_ref;
+       unsigned char head_sha1[20];
+       int is_detached;
+       int is_bare;
+};
+
+/* Functions for acting on the information about worktrees. */
+
+/*
+ * Get the worktrees.  The primary worktree will always be the first returned,
+ * and linked worktrees will be pointed to by 'next' in each subsequent
+ * worktree.  No specific ordering is done on the linked worktrees.
+ *
+ * The caller is responsible for freeing the memory from the returned
+ * worktree(s).
+ */
+extern struct worktree **get_worktrees(void);
+
+/*
+ * Free up the memory for worktree(s)
+ */
+extern void free_worktrees(struct worktree **);
+
+/*
+ * Check if a per-worktree symref points to a ref in the main worktree
+ * or any linked worktree, and return the path to the exising worktree
+ * if it is.  Returns NULL if there is no existing ref.  The caller is
+ * responsible for freeing the returned path.
+ */
+extern char *find_shared_symref(const char *symref, const char *target);
+
+#endif
index 701d2339b9f5014d78ff1d5c88cfd21a1accd0c2..db0ec6a7370fd56e4e11c49a4b9e5e15f461e1f9 100644 (file)
@@ -19,4 +19,10 @@ GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale'
 PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
 export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
 
-exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
+if test -n "$GIT_TEST_GDB"
+then
+       unset GIT_TEST_GDB
+       exec gdb --args "${GIT_EXEC_PATH}/@@PROG@@" "$@"
+else
+       exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"
+fi
index 0e22d4381438b2c262e1cd5ee0cc4effc2fb3c3d..6fcaa4dc62b504078ea5202cfd7a096e79a83c5e 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -621,6 +621,22 @@ char *xgetcwd(void)
        return strbuf_detach(&sb, NULL);
 }
 
+int xsnprintf(char *dst, size_t max, const char *fmt, ...)
+{
+       va_list ap;
+       int len;
+
+       va_start(ap, fmt);
+       len = vsnprintf(dst, max, fmt, ap);
+       va_end(ap);
+
+       if (len < 0)
+               die("BUG: your snprintf is broken");
+       if (len >= max)
+               die("BUG: attempt to snprintf into too-small buffer");
+       return len;
+}
+
 static int write_file_v(const char *path, int fatal,
                        const char *fmt, va_list params)
 {
index 3e3b8c098924d655bccb395e95cee832abb1d39f..435fc2806ec0a59acf390ee89ed2efc79f229a0e 100644 (file)
@@ -897,15 +897,15 @@ static void wt_status_print_verbose(struct wt_status *s)
 static void wt_status_print_tracking(struct wt_status *s)
 {
        struct strbuf sb = STRBUF_INIT;
-       const char *cp, *ep;
+       const char *cp, *ep, *branch_name;
        struct branch *branch;
        char comment_line_string[3];
        int i;
 
        assert(s->branch && !s->is_initial);
-       if (!starts_with(s->branch, "refs/heads/"))
+       if (!skip_prefix(s->branch, "refs/heads/", &branch_name))
                return;
-       branch = branch_get(s->branch + 11);
+       branch = branch_get(branch_name);
        if (!format_tracking_info(branch, &sb))
                return;
 
@@ -1268,6 +1268,7 @@ static char *read_and_strip_branch(const char *path)
 {
        struct strbuf sb = STRBUF_INIT;
        unsigned char sha1[20];
+       const char *branch_name;
 
        if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
                goto got_nothing;
@@ -1276,8 +1277,8 @@ static char *read_and_strip_branch(const char *path)
                strbuf_setlen(&sb, sb.len - 1);
        if (!sb.len)
                goto got_nothing;
-       if (starts_with(sb.buf, "refs/heads/"))
-               strbuf_remove(&sb,0, strlen("refs/heads/"));
+       if (skip_prefix(sb.buf, "refs/heads/", &branch_name))
+               strbuf_remove(&sb, 0, branch_name - sb.buf);
        else if (starts_with(sb.buf, "refs/"))
                ;
        else if (!get_sha1_hex(sb.buf, sha1)) {
@@ -1308,9 +1309,8 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
        struct grab_1st_switch_cbdata *cb = cb_data;
        const char *target = NULL, *end;
 
-       if (!starts_with(message, "checkout: moving from "))
+       if (!skip_prefix(message, "checkout: moving from ", &message))
                return 0;
-       message += strlen("checkout: moving from ");
        target = strstr(message, " to ");
        if (!target)
                return 0;
@@ -1348,14 +1348,10 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
             /* perhaps sha1 is a tag, try to dereference to a commit */
             ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
              !hashcmp(cb.nsha1, commit->object.sha1)))) {
-               int ofs;
-               if (starts_with(ref, "refs/tags/"))
-                       ofs = strlen("refs/tags/");
-               else if (starts_with(ref, "refs/remotes/"))
-                       ofs = strlen("refs/remotes/");
-               else
-                       ofs = 0;
-               state->detached_from = xstrdup(ref + ofs);
+               const char *from = ref;
+               if (!skip_prefix(from, "refs/tags/", &from))
+                       skip_prefix(from, "refs/remotes/", &from);
+               state->detached_from = xstrdup(from);
        } else
                state->detached_from =
                        xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
@@ -1442,9 +1438,7 @@ void wt_status_print(struct wt_status *s)
        if (s->branch) {
                const char *on_what = _("On branch ");
                const char *branch_name = s->branch;
-               if (starts_with(branch_name, "refs/heads/"))
-                       branch_name += 11;
-               else if (!strcmp(branch_name, "HEAD")) {
+               if (!strcmp(branch_name, "HEAD")) {
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
                        if (state.rebase_in_progress || state.rebase_interactive_in_progress) {
                                if (state.rebase_interactive_in_progress)
@@ -1462,7 +1456,8 @@ void wt_status_print(struct wt_status *s)
                                branch_name = "";
                                on_what = _("Not currently on any branch.");
                        }
-               }
+               } else
+                       skip_prefix(branch_name, "refs/heads/", &branch_name);
                status_printf(s, color(WT_STATUS_HEADER, s), "%s", "");
                status_printf_more(s, branch_status_color, "%s", on_what);
                status_printf_more(s, branch_color, "%s\n", branch_name);
@@ -1644,24 +1639,24 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
                return;
        branch_name = s->branch;
 
-       if (starts_with(branch_name, "refs/heads/"))
-               branch_name += 11;
-       else if (!strcmp(branch_name, "HEAD")) {
-               branch_name = _("HEAD (no branch)");
-               branch_color_local = color(WT_STATUS_NOBRANCH, s);
-       }
-
-       branch = branch_get(s->branch + 11);
        if (s->is_initial)
                color_fprintf(s->fp, header_color, _("Initial commit on "));
 
+       if (!strcmp(s->branch, "HEAD")) {
+               color_fprintf(s->fp, color(WT_STATUS_NOBRANCH, s), "%s",
+                             _("HEAD (no branch)"));
+               goto conclude;
+       }
+
+       skip_prefix(branch_name, "refs/heads/", &branch_name);
+
+       branch = branch_get(branch_name);
+
        color_fprintf(s->fp, branch_color_local, "%s", branch_name);
 
        if (stat_tracking_info(branch, &num_ours, &num_theirs, &base) < 0) {
-               if (!base) {
-                       fputc(s->null_termination ? '\0' : '\n', s->fp);
-                       return;
-               }
+               if (!base)
+                       goto conclude;
 
                upstream_is_gone = 1;
        }
@@ -1671,10 +1666,8 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
        color_fprintf(s->fp, branch_color_remote, "%s", base);
        free((char *)base);
 
-       if (!upstream_is_gone && !num_ours && !num_theirs) {
-               fputc(s->null_termination ? '\0' : '\n', s->fp);
-               return;
-       }
+       if (!upstream_is_gone && !num_ours && !num_theirs)
+               goto conclude;
 
 #define LABEL(string) (s->no_gettext ? (string) : _(string))
 
@@ -1695,6 +1688,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
        }
 
        color_fprintf(s->fp, header_color, "]");
+ conclude:
        fputc(s->null_termination ? '\0' : '\n', s->fp);
 }