Merge branch 'ta/doc-no-small-caps'
authorJunio C Hamano <gitster@pobox.com>
Wed, 6 Feb 2013 00:13:32 +0000 (16:13 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 6 Feb 2013 00:13:32 +0000 (16:13 -0800)
Update documentation to change "GIT" which was a poor-man's small
caps to "Git". The latter was the intended spelling.

Also change "git" spelled in all-lowercase to "Git" when it refers
to the system as the whole or the concept it embodies, as opposed to
the command the end users would type.

* ta/doc-no-small-caps:
Documentation: StGit is the right spelling, not StGIT
Documentation: describe the "repository" in repository-layout
Documentation: add a description for 'gitfile' to glossary
Documentation: do not use undefined terms git-dir and git-file
Documentation: the name of the system is 'Git', not 'git'
Documentation: avoid poor-man's small caps GIT

143 files changed:
.gitignore
Documentation/RelNotes/1.8.1.2.txt
Documentation/RelNotes/1.8.1.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.8.2.txt
Documentation/config.txt
Documentation/fetch-options.txt
Documentation/git-add.txt
Documentation/git-check-ignore.txt [new file with mode: 0644]
Documentation/git-cvsimport.txt
Documentation/git-cvsserver.txt
Documentation/git-fetch-pack.txt
Documentation/git-for-each-ref.txt
Documentation/git-p4.txt
Documentation/git-reset.txt
Documentation/git-stripspace.txt
Documentation/githooks.txt
Documentation/gitignore.txt
Documentation/howto/maintain-git.txt
Documentation/technical/api-directory-listing.txt
Documentation/technical/api-strbuf.txt
Documentation/technical/shallow.txt
INSTALL
Makefile
README
advice.c
advice.h
archive-tar.c
attr.c
builtin.h
builtin/add.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/check-ignore.c [new file with mode: 0644]
builtin/clean.c
builtin/clone.c
builtin/commit.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/grep.c
builtin/help.c
builtin/ls-files.c
builtin/merge.c
builtin/notes.c
builtin/push.c
builtin/receive-pack.c
builtin/reflog.c
builtin/reset.c
builtin/send-pack.c
builtin/stripspace.c
builtin/tag.c
cache.h
command-list.txt
commit.h
config.c
contrib/completion/git-completion.bash
contrib/hooks/post-receive-email
contrib/remote-helpers/git-remote-hg
contrib/remote-helpers/test-hg-hg-git.sh
contrib/svn-fe/svnrdump_sim.py
convert.c
dir.c
dir.h
environment.c
fetch-pack.c
git-am.sh
git-compat-util.h
git-cvsserver.perl
git-p4.py
git-rebase--interactive.sh
git-remote-testpy.py
git-submodule.sh
git-svn.perl
git.c
git_remote_helpers/.gitignore
git_remote_helpers/Makefile
git_remote_helpers/git/importer.py
git_remote_helpers/setup.py
gitk-git/.gitignore [new file with mode: 0644]
gitk-git/Makefile
gitk-git/gitk
gitk-git/po/sv.po
gpg-interface.c
help.c
ident.c
imap-send.c
ll-merge.c
parse-options.h
pathspec.c [new file with mode: 0644]
pathspec.h [new file with mode: 0644]
perl/Git/SVN.pm
perl/Git/SVN/Editor.pm
pretty.c
read-cache.c
refs.c
remote.c
run-command.c
run-command.h
send-pack.c
setup.c
shallow.c
strbuf.c
strbuf.h
submodule.c
t/lib-git-p4.sh
t/t0008-ignores.sh [new file with mode: 0755]
t/t0030-stripspace.sh
t/t0050-filesystem.sh
t/t2013-checkout-submodule.sh
t/t3070-wildmatch.sh
t/t4042-diff-textconv-caching.sh
t/t4208-log-magic-pathspec.sh
t/t5000-tar-tree.sh
t/t5500-fetch-pack.sh
t/t5516-fetch-push.sh
t/t5571-pre-push-hook.sh [new file with mode: 0755]
t/t7102-reset.sh
t/t7106-reset-unborn-branch.sh [new file with mode: 0755]
t/t7402-submodule-rebase.sh
t/t7502-commit.sh
t/t7508-status.sh
t/t9100-git-svn-basic.sh
t/t9402-git-cvsserver-refs.sh [new file with mode: 0755]
t/t9800-git-p4-basic.sh
t/t9802-git-p4-filetype.sh
t/t9806-git-p4-options.sh
t/t9807-git-p4-submit.sh
t/t9809-git-p4-client-view.sh
t/t9812-git-p4-wildcards.sh
t/t9815-git-p4-submit-fail.sh
t/t9902-completion.sh
t/test-lib.sh
templates/hooks--pre-push.sample [new file with mode: 0644]
test-wildmatch.c
transport-helper.c
transport.c
transport.h
unpack-trees.c
upload-pack.c
userdiff.c
wildmatch.c
wildmatch.h
wt-status.c
index aa258a6bcfe105cdb872c6d77cd9bd19c84e8dc5..6669bf0c6c9a0b42f74e2ed189350a6a9f11f17d 100644 (file)
@@ -22,6 +22,7 @@
 /git-bundle
 /git-cat-file
 /git-check-attr
+/git-check-ignore
 /git-check-ref-format
 /git-checkout
 /git-checkout-index
index 76ad0b3d01d355b05fb8b887300accf60c089461..5ab7b18906f08e640d9fd3ced98c707bbb8bfe21 100644 (file)
@@ -4,6 +4,16 @@ Git 1.8.1.2 Release Notes
 Fixes since v1.8.1.1
 --------------------
 
+ * An element on GIT_CEILING_DIRECTORIES list that does not name the
+   real path to a directory (i.e. a symbolic link) could have caused
+   the GIT_DIR discovery logic to escape the ceiling.
+
+ * Command line completion for "tcsh" emitted an unwanted space
+   after completing a single directory name.
+
+ * Command line completion leaked an unnecessary error message while
+   looking for possible matches with paths in <tree-ish>.
+
  * "git archive" did not record uncompressed size in the header when
    streaming a zip archive, which confused some implementations of unzip.
 
@@ -11,3 +21,5 @@ Fixes since v1.8.1.1
    trailer part, "git send-email" failed to pick up the addresses from
    there. As e-mail headers field names are case insensitive, this
    script should follow suit and treat "cc:" and "Cc:" the same way.
+
+Also contains various documentation fixes.
diff --git a/Documentation/RelNotes/1.8.1.3.txt b/Documentation/RelNotes/1.8.1.3.txt
new file mode 100644 (file)
index 0000000..03bd330
--- /dev/null
@@ -0,0 +1,28 @@
+Git 1.8.1.3 Release Notes
+=========================
+
+Fixes since v1.8.1.2
+--------------------
+
+ * The attribute mechanism didn't allow limiting attributes to be
+   applied to only a single directory itself with "path/" like the
+   exclude mechanism does.  The fix for this in 1.8.1.2 had
+   performance degradations.
+
+ * Command line completion code was inadvertently made incompatible with
+   older versions of bash by using a newer array notation.
+
+ * We used to stuff "user@" and then append what we read from
+   /etc/mailname to come up with a default e-mail ident, but a bug
+   lost the "user@" part.
+
+ * "git am" did not parse datestamp correctly from Hg generated patch,
+   when it is run in a locale outside C (or en).
+
+ * Attempt to "branch --edit-description" an existing branch, while
+   being on a detached HEAD, errored out.
+
+ * "git rebase --preserve-merges" lost empty merges in recent versions
+   of Git.
+
+Also contains various documentation fixes.
index f6d15059dc71c22ace37732b5b215c39c4cec944..a1ebb96ea8b8dff07b388c817b754bc367093d11 100644 (file)
@@ -15,6 +15,13 @@ current branch to the branch with the same name, only when the current
 branch is set to integrate with that remote branch.  There is a user
 preference configuration variable "push.default" to change this.
 
+"git push $there tag v1.2.3" used to allow replacing a tag v1.2.3
+that already exists in the repository $there, if the rewritten tag
+you are pushing points at a commit that is a decendant of a commit
+that the old tag v1.2.3 points at.  This was found to be error prone
+and starting with this release, any attempt to update an existing
+ref under refs/tags/ hierarchy will fail, without "--force".
+
 
 Updates since v1.8.1
 --------------------
@@ -38,11 +45,34 @@ UI, Workflows & Features
  * Scripts can ask Git that wildcard patterns in pathspecs they give do
    not have any significance, i.e. take them as literal strings.
 
- * The pathspec code learned to grok "foo/**/bar" as a pattern that
-   matches "bar" in 0-or-more levels of subdirectory in "foo".
+ * The patterns in .gitignore and .gitattributes files can have **/,
+   as a pattern that matches 0 or more levels of subdirectory.
+   E.g. "foo/**/bar" matches "bar" in "foo" itself or in a
+   subdirectory of "foo".
+
+ * When giving arguments without "--" disambiguation, object names
+   that come earlier on the command line must not be interpretable as
+   pathspecs and pathspecs that come later on the command line must
+   not be interpretable as object names.  This disambiguation rule has
+   been tweaked so that ":/" (no other string before or after) is
+   always interpreted as a pathspec; "git cmd -- :/" is no longer
+   needed, you can just say "git cmd :/".
+
+ * Various "hint" lines Git gives when it asks the user to edit
+   messages in the editor are commented out with '#' by default. The
+   core.commentchar configuration variable can be used to customize
+   this '#' to a different character.
+
+ * "git add -u" and "git add -A" without pathspec issues warning to
+   make users aware that they are only operating on paths inside the
+   subdirectory they are in.  Use ":/" (everything from the top) or
+   "." (everything from the $cwd) to disambiguate.
 
  * "git blame" (and "git diff") learned the "--no-follow" option.
 
+ * "git check-ignore" command to help debugging .gitignore files has
+   been added.
+
  * "git cherry-pick" can be used to replay a root commit to an unborn
    branch.
 
@@ -72,12 +102,26 @@ UI, Workflows & Features
  * "git push" now requires "-f" to update a tag, even if it is a
    fast-forward, as tags are meant to be fixed points.
 
+ * Error messages from "git push" when it stops to prevent remote refs
+   from getting overwritten by mistake have been improved to explain
+   various situations separately.
+
+ * "git push" will stop without doing anything if the new "pre-push"
+   hook exists and exits with a failure.
+
  * When "git rebase" fails to generate patches to be applied (e.g. due
    to oom), it failed to detect the failure and instead behaved as if
    there were nothing to do.  A workaround to use a temporary file has
    been applied, but we probably would want to revisit this later, as
    it hurts the common case of not failing at all.
 
+ * Input and preconditions to "git reset" has been loosened where
+   appropriate.  "git reset $fromtree Makefile" requires $fromtree to
+   be any tree (it used to require it to be a commit), for example.
+   "git reset" (without options or parameters) used to error out when
+   you do not have any commits in your history, but it now gives you
+   an empty index (to match non-existent commit you are not even on).
+
  * "git submodule" started learning a new mode to integrate with the
    tip of the remote branch (as opposed to integrating with the commit
    recorded in the superproject's gitlink).
@@ -90,6 +134,13 @@ Foreign Interface
 
  * A new remote helper to interact with bzr has been added to contrib/.
 
+ * "git p4" got various bugfixes around its branch handling.  It is
+   also made usable with Python 2.4/2.5.  In addition, its various
+   portability issues for Cygwin have been addressed.
+
+ * The remote helper to interact with Hg in contrib/ has seen a few
+   fixes.
+
 
 Performance, Internal Implementation, etc.
 
@@ -99,13 +150,45 @@ Performance, Internal Implementation, etc.
  * Matching paths with common forms of pathspecs that contain wildcard
    characters has been optimized further.
 
+ * We stopped paying attention to $GIT_CONFIG environment that points
+   at a single configuration file from any command other than "git config"
+   quite a while ago, but "git clone" internally set, exported, and
+   then unexported the variable during its operation unnecessarily.
+
+ * "git reset" internals has been reworked and should be faster in
+   general. We tried to be careful not to break any behaviour but
+   there could be corner cases, especially when running the command
+   from a conflicted state, that we may have missed.
+
  * The implementation of "imap-send" has been updated to reuse xml
-   quoting code from http-push codepath.
+   quoting code from http-push codepath, and lost a lot of unused
+   code.
 
  * There is a simple-minded checker for the test scripts in t/
    directory to catch most common mistakes (it is not enabled by
    default).
 
+ * You can build with USE_WILDMATCH=YesPlease to use a replacement
+   implementation of pattern matching logic used for pathname-like
+   things, e.g. refnames and paths in the repository.  This new
+   implementation is not expected change the existing behaviour of Git
+   in this release, except for "git for-each-ref" where you can now
+   say "refs/**/master" and match with both refs/heads/master and
+   refs/remotes/origin/master.  We plan to use this new implementation
+   in wider places (e.g. "git ls-files '**/Makefile' may find Makefile
+   at the top-level, and "git log '**/t*.sh'" may find commits that
+   touch a shell script whose name begins with "t" at any level) in
+   future versions of Git, but we are not there yet.  By building with
+   USE_WILDMATCH, using the resulting Git daily and reporting when you
+   find breakages, you can help us get closer to that goal.
+
+ * Some reimplementations of Git do not write all the stat info back
+   to the index due to their implementation limitations (e.g. jgit).
+   A configuration option can tell Git to ignore changes to most of
+   the stat fields and only pay attention to mtime and size, which
+   these implementations can reliably update.  This can be used to
+   avoid excessive revalidation of contents.
+
 
 Also contains minor documentation updates and code clean-ups.
 
@@ -120,117 +203,137 @@ details).
  * An element on GIT_CEILING_DIRECTORIES list that does not name the
    real path to a directory (i.e. a symbolic link) could have caused
    the GIT_DIR discovery logic to escape the ceiling.
-   (merge 059b379 mh/ceiling later to maint).
 
  * When attempting to read the XDG-style $HOME/.config/git/config and
    finding that $HOME/.config/git is a file, we gave a wrong error
    message, instead of treating the case as "a custom config file does
    not exist there" and moving on.
-   (merge 8f2bbe4 jn/warn-on-inaccessible-loosen later to maint).
 
  * The behaviour visible to the end users was confusing, when they
    attempt to kill a process spawned in the editor that was in turn
    launched by Git with SIGINT (or SIGQUIT), as Git would catch that
    signal and die.  We ignore these signals now.
-   (merge 1250857 pf/editor-ignore-sigint later to maint).
+   (merge 0398fc34 pf/editor-ignore-sigint later to maint).
 
  * A child process that was killed by a signal (e.g. SIGINT) was
    reported in an inconsistent way depending on how the process was
    spawned by us, with or without a shell in between.
-   (merge 709ca73 jk/unify-exit-code-by-receiving-signal later to maint).
 
  * After failing to create a temporary file using mkstemp(), failing
    pathname was not reported correctly on some platforms.
-   (merge f7be59b jc/mkstemp-more-careful-error-reporting later to maint).
+
+ * We used to stuff "user@" and then append what we read from
+   /etc/mailname to come up with a default e-mail ident, but a bug
+   lost the "user@" part.
 
  * The attribute mechanism didn't allow limiting attributes to be
    applied to only a single directory itself with "path/" like the
-   exclude mechanism does.
-   (merge 94bc671 ja/directory-attrs later to maint).
+   exclude mechanism does.  The initial implementation of this that
+   was merged to 'maint' and 1.8.1.2 was with a severe performance
+   degradations and needs to merge a fix-up topic.
+
+ * "git am" did not parse datestamp correctly from Hg generated patch,
+   when it is run in a locale outside C (or en).
 
  * "git apply" misbehaved when fixing whitespace breakages by removing
    excess trailing blank lines.
-   (merge 5de7166 jc/apply-trailing-blank-removal later to maint).
 
  * A tar archive created by "git archive" recorded a directory in a
    way that made NetBSD's implementation of "tar" sometimes unhappy.
-   (merge 22f0dcd rs/leave-base-name-in-name-field-of-tar later to maint).
 
  * "git archive" did not record uncompressed size in the header when
    streaming a zip archive, which confused some implementations of unzip.
-   (merge 5ea2c84 rs/zip-with-uncompressed-size-in-the-header later to maint).
+
+ * "git archive" did not parse configuration values in tar.* namespace
+   correctly.
+   (merge b3873c3 jk/config-parsing-cleanup later to maint).
+
+ * Attempt to "branch --edit-description" an existing branch, while
+   being on a detached HEAD, errored out.
+
+ * "git clean" showed what it was going to do, but sometimes end up
+   finding that it was not allowed to do so, which resulted in a
+   confusing output (e.g. after saying that it will remove an
+   untracked directory, it found an embedded git repository there
+   which it is not allowed to remove).  It now performs the actions
+   and then reports the outcome more faithfully.
+   (merge f538a91 zk/clean-report-failure later to maint).
 
  * When "git clone --separate-git-dir=$over_there" is interrupted, it
    failed to remove the real location of the $GIT_DIR it created.
    This was most visible when interrupting a submodule update.
-   (merge 9be1980 jl/interrupt-clone-remove-separate-git-dir later to maint).
+
+ * "git fetch --depth" was broken in at least three ways.  The
+   resulting history was deeper than specified by one commit, it was
+   unclear how to wipe the shallowness of the repository with the
+   command, and documentation was misleading.
+   (merge cfb70e1 nd/fetch-depth-is-broken later to maint).
+
+ * "git log --all -p" that walked refs/notes/textconv/ ref can later
+   try to use the textconv data incorrectly after it gets freed.
+   (merge be5c9fb jk/read-commit-buffer-data-after-free later to maint).
 
  * The way "git svn" asked for password using SSH_ASKPASS and
    GIT_ASKPASS was not in line with the rest of the system.
-   (merge e9263e4 ss/svn-prompt later to maint).
 
  * The --graph code fell into infinite loop when asked to do what the
    code did not expect.
-   (merge 656197a mk/maint-graph-infinity-loop later to maint).
 
  * http transport was wrong to ask for the username when the
    authentication is done by certificate identity.
-   (merge 75e9a40 rb/http-cert-cred-no-username-prompt later to maint).
 
  * "git pack-refs" that ran in parallel to another process that
    created new refs had a nasty race.
-   (merge b3f1280 jk/repack-ref-racefix later to maint).
 
  * After "git add -N" and then writing a tree object out of the
    index, the cache-tree data structure got corrupted.
-   (merge eec3e7e nd/invalidate-i-t-a-cache-tree later to maint).
+
+ * "git clone" used to allow --bare and --separate-git-dir=$there
+   options at the same time, which was nonsensical.
+   (merge 95b63f1 nd/clone-no-separate-git-dir-with-bare later to maint).
+
+ * "git rebase --preserve-merges" lost empty merges in recent versions
+   of Git.
 
  * "git merge --no-edit" computed who were involved in the work done
    on the side branch, even though that information is to be discarded
    without getting seen in the editor.
-   (merge 9bcbb1c jc/maint-fmt-merge-msg-no-edit-lose-credit later to maint).
 
  * "git merge" started calling prepare-commit-msg hook like "git
    commit" does some time ago, but forgot to pay attention to the exit
    status of the hook.
-   (merge 3e4141d ap/merge-stop-at-prepare-commit-msg-failure later to maint).
 
  * When users spell "cc:" in lowercase in the fake "header" in the
    trailer part, "git send-email" failed to pick up the addresses from
    there. As e-mail headers field names are case insensitive, this
    script should follow suit and treat "cc:" and "Cc:" the same way.
-   (merge 6310071 nz/send-email-headers-are-case-insensitive later to maint).
 
  * Output from "git status --ignored" showed an unexpected interaction
    with "--untracked".
-   (merge a45fb69 ap/status-ignored-in-ignored-directory later to maint).
 
  * "gitweb", when sorting by age to show repositories with new
    activities first, used to sort repositories with absolutely
    nothing in it early, which was not very useful.
-   (merge 28dae18 md/gitweb-sort-by-age later to maint).
 
  * "gitweb"'s code to sanitize control characters before passing it to
    "highlight" filter lost known-to-be-safe control characters by
    mistake.
-   (merge 0e901d2 os/gitweb-highlight-uncaptured later to maint).
 
  * When a line to be wrapped has a solid run of non space characters
    whose length exactly is the wrap width, "git shortlog -w" failed
    to add a newline after such a line.
-   (merge e0db176 sp/shortlog-missing-lf later to maint).
 
  * Command line completion leaked an unnecessary error message while
    looking for possible matches with paths in <tree-ish>.
-   (merge ca87dd6 ds/completion-silence-in-tree-path-probe later to maint).
 
  * Command line completion for "tcsh" emitted an unwanted space
    after completing a single directory name.
-   (merge 92f1c04 mk/complete-tcsh later to maint).
+
+ * Command line completion code was inadvertently made incompatible with
+   older versions of bash by using a newer array notation.
 
  * Some shells do not behave correctly when IFS is unset; work it
    around by explicitly setting it to the default value.
-   (merge 393050c jc/maint-fbsd-sh-ifs-workaround later to maint).
 
  * Some scripted programs written in Python did not get updated when
    PYTHON_PATH changed.
@@ -238,24 +341,24 @@ details).
 
  * When autoconf is used, any build on a different commit always ran
    "config.status --recheck" even when unnecessary.
-   (merge 1226504 jn/less-reconfigure later to maint).
 
  * We have been carrying a translated and long-unmaintained copy of an
    old version of the tutorial; removed.
-   (merge 0a85441 ta/remove-stale-translated-tut later to maint).
+
+ * t0050 had tests expecting failures from a bug that was fixed some
+   time ago.
 
  * t4014, t9502 and t0200 tests had various portability issues that
    broke on OpenBSD.
-   (merge 27f6342 jc/maint-test-portability later to maint).
 
  * t9020 and t3600 tests had various portability issues.
-   (merge 5a02966 jc/test-portability later to maint).
 
  * t9200 runs "cvs init" on a directory that already exists, but a
    platform can configure this fail for the current user (e.g. you
    need to be in the cvsadmin group on NetBSD 6.0).
-   (merge 8666df0 jc/test-cvs-no-init-in-existing-dir later to maint).
 
  * t9020 and t9810 had a few non-portable shell script construct.
-   (merge 2797914 tb/test-t9020-no-which later to maint).
-   (merge 6f4e505 tb/test-t9810-no-sed-i later to maint).
+
+ * Scripts to test bash completion was inherently flaky as it was
+   affected by whatever random things the user may have on $PATH.
+   (merge 5047822 jc/do-not-let-random-file-interfere-with-completion-tests later to maint).
index 1bfbc7a1d32d83069b30b3e7a2faf5baf5cb908f..9b115973870d164b13334726ef9dad37802f0616 100644 (file)
@@ -143,7 +143,8 @@ advice.*::
        pushUpdateRejected::
                Set this variable to 'false' if you want to disable
                'pushNonFFCurrent', 'pushNonFFDefault',
-               'pushNonFFMatching', and 'pushAlreadyExists'
+               'pushNonFFMatching', 'pushAlreadyExists',
+               'pushFetchFirst', and 'pushNeedsForce'
                simultaneously.
        pushNonFFCurrent::
                Advice shown when linkgit:git-push[1] fails due to a
@@ -162,6 +163,15 @@ advice.*::
        pushAlreadyExists::
                Shown when linkgit:git-push[1] rejects an update that
                does not qualify for fast-forwarding (e.g., a tag.)
+       pushFetchFirst::
+               Shown when linkgit:git-push[1] rejects an update that
+               tries to overwrite a remote ref that points at an
+               object we do not have.
+       pushNeedsForce::
+               Shown when linkgit:git-push[1] rejects an update that
+               tries to overwrite a remote ref that points at an
+               object that is not a committish, or make the remote
+               ref point at an object that is not a committish.
        statusHints::
                Show directions on how to proceed from the current
                state in the output of linkgit:git-status[1], in
@@ -235,6 +245,12 @@ core.trustctime::
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
 
+core.checkstat::
+       Determines which stat fields to match between the index
+       and work tree. The user can set this to 'default' or
+       'minimal'. Default (or explicitly 'default'), is to check
+       all fields, including the sub-second part of mtime and ctime.
+
 core.quotepath::
        The commands that output paths (e.g. 'ls-files',
        'diff'), when not given the `-z` option, will quote
@@ -528,6 +544,12 @@ core.editor::
        variable when it is set, and the environment variable
        `GIT_EDITOR` is not set.  See linkgit:git-var[1].
 
+core.commentchar::
+       Commands such as `commit` and `tag` that lets you edit
+       messages consider a line that begins with this character
+       commented, and removes them after the editor returns
+       (default '#').
+
 sequence.editor::
        Text editor used by `git rebase -i` for editing the rebase insn file.
        The value is meant to be interpreted by the shell when it is used.
index 6e98bdf1490a0a5718dd14a4c2d8b2baaee7383e..9cb649673d609c3b27b46ae5dd217edfcc28a5a9 100644 (file)
@@ -8,11 +8,15 @@
        option old data in `.git/FETCH_HEAD` will be overwritten.
 
 --depth=<depth>::
-       Deepen the history of a 'shallow' repository created by
+       Deepen or shorten the history of a 'shallow' repository created by
        `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1])
        to the specified number of commits from the tip of each remote
        branch history. Tags for the deepened commits are not fetched.
 
+--unshallow::
+       Convert a shallow repository to a complete one, removing all
+       the limitations imposed by shallow repositories.
+
 ifndef::git-pull[]
 --dry-run::
        Show what would be done, without making any changes.
index fd9e36b99f5506964ac8352b82bb7af05551a523..533355984cf00bc81f21dfeee4a4e8977631f423 100644 (file)
@@ -107,9 +107,10 @@ apply to the index. See EDITING PATCHES below.
        from the index if the corresponding files in the working tree
        have been removed.
 +
-If no <filepattern> is given, default to "."; in other words,
-update all tracked files in the current directory and its
-subdirectories.
+If no <filepattern> is given, the current version of Git defaults to
+"."; in other words, update all tracked files in the current directory
+and its subdirectories. This default will change in a future version
+of Git, hence the form without <filepattern> should not be used.
 
 -A::
 --all::
diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt
new file mode 100644 (file)
index 0000000..854e4d0
--- /dev/null
@@ -0,0 +1,89 @@
+git-check-ignore(1)
+===================
+
+NAME
+----
+git-check-ignore - Debug gitignore / exclude files
+
+
+SYNOPSIS
+--------
+[verse]
+'git check-ignore' [options] pathname...
+'git check-ignore' [options] --stdin < <list-of-paths>
+
+DESCRIPTION
+-----------
+
+For each pathname given via the command-line or from a file via
+`--stdin`, show the pattern from .gitignore (or other input files to
+the exclude mechanism) that decides if the pathname is excluded or
+included.  Later patterns within a file take precedence over earlier
+ones.
+
+OPTIONS
+-------
+-q, --quiet::
+       Don't output anything, just set exit status.  This is only
+       valid with a single pathname.
+
+-v, --verbose::
+       Also output details about the matching pattern (if any)
+       for each given pathname.
+
+--stdin::
+       Read file names from stdin instead of from the command-line.
+
+-z::
+       The output format is modified to be machine-parseable (see
+       below).  If `--stdin` is also given, input paths are separated
+       with a NUL character instead of a linefeed character.
+
+OUTPUT
+------
+
+By default, any of the given pathnames which match an ignore pattern
+will be output, one per line.  If no pattern matches a given path,
+nothing will be output for that path; this means that path will not be
+ignored.
+
+If `--verbose` is specified, the output is a series of lines of the form:
+
+<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>
+
+<pathname> is the path of a file being queried, <pattern> is the
+matching pattern, <source> is the pattern's source file, and <linenum>
+is the line number of the pattern within that source.  If the pattern
+contained a `!` prefix or `/` suffix, it will be preserved in the
+output.  <source> will be an absolute path when referring to the file
+configured by `core.excludesfile`, or relative to the repository root
+when referring to `.git/info/exclude` or a per-directory exclude file.
+
+If `-z` is specified, the pathnames in the output are delimited by the
+null character; if `--verbose` is also specified then null characters
+are also used instead of colons and hard tabs:
+
+<source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL>
+
+
+EXIT STATUS
+-----------
+
+0::
+       One or more of the provided paths is ignored.
+
+1::
+       None of the provided paths are ignored.
+
+128::
+       A fatal error was encountered.
+
+SEE ALSO
+--------
+linkgit:gitignore[5]
+linkgit:gitconfig[5]
+linkgit:git-ls-files[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 1786d9dc9636a981ad088151c7f4a5d081931315..d1bcda28f423566dff17a7bff85f40ae07d4ca9a 100644 (file)
@@ -18,6 +18,12 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+*WARNING:* `git cvsimport` uses cvsps version 2, which is considered
+deprecated; it does not work with cvsps version 3 and later.  If you are
+performing a one-shot import of a CVS repository consider using
+link:http://cvs2svn.tigris.org/cvs2git.html[cvs2git] or
+link:https://github.com/BartMassey/parsecvs[parsecvs].
+
 Imports a CVS repository into Git. It will either create a new
 repository, or incrementally import into an existing one.
 
index 7ca64b384b09e8a295278549c56e8d8ede22e954..472f5cbd0712bc3202f44743e80397bfa6fb16ba 100644 (file)
@@ -359,6 +359,43 @@ Operations supported
 
 All the operations required for normal use are supported, including
 checkout, diff, status, update, log, add, remove, commit.
+
+Most CVS command arguments that read CVS tags or revision numbers
+(typically -r) work, and also support any git refspec
+(tag, branch, commit ID, etc).
+However, CVS revision numbers for non-default branches are not well
+emulated, and cvs log does not show tags or branches at
+all.  (Non-main-branch CVS revision numbers superficially resemble CVS
+revision numbers, but they actually encode a git commit ID directly,
+rather than represent the number of revisions since the branch point.)
+
+Note that there are two ways to checkout a particular branch.
+As described elsewhere on this page, the "module" parameter
+of cvs checkout is interpreted as a branch name, and it becomes
+the main branch.  It remains the main branch for a given sandbox
+even if you temporarily make another branch sticky with
+cvs update -r.  Alternatively, the -r argument can indicate
+some other branch to actually checkout, even though the module
+is still the "main" branch.  Tradeoffs (as currently
+implemented): Each new "module" creates a new database on disk with
+a history for the given module, and after the database is created,
+operations against that main branch are fast.  Or alternatively,
+-r doesn't take any extra disk space, but may be significantly slower for
+many operations, like cvs update.
+
+If you want to refer to a git refspec that has characters that are
+not allowed by CVS, you have two options.  First, it may just work
+to supply the git refspec directly to the appropriate CVS -r argument;
+some CVS clients don't seem to do much sanity checking of the argument.
+Second, if that fails, you can use a special character escape mechanism
+that only uses characters that are valid in CVS tags.  A sequence
+of 4 or 5 characters of the form (underscore (`"_"`), dash (`"-"`),
+one or two characters, and dash (`"-"`)) can encode various characters based
+on the one or two letters: `"s"` for slash (`"/"`), `"p"` for
+period (`"."`), `"u"` for underscore (`"_"`), or two hexadecimal digits
+for any byte value at all (typically an ASCII number, or perhaps a part
+of a UTF-8 encoded character).
+
 Legacy monitoring operations are not supported (edit, watch and related).
 Exports and tagging (tags and branches) are not supported at this stage.
 
index 8c751202d75ec5f3e62b7c386bb168f13e63e295..b81e90d8e70722e0da690ab5afa1bca4ad356b68 100644 (file)
@@ -84,6 +84,8 @@ be in a separate packet, and the list must end with a flush packet.
 
 --depth=<n>::
        Limit fetching to ancestor-chains not longer than n.
+       'git-upload-pack' treats the special depth 2147483647 as
+       infinite even if there is an ancestor-chain that long.
 
 --no-progress::
        Do not show the progress.
index db55a4e0bbc524c20f08e6862daaa97dd1785b71..f2e08d11c19cf127c5f7d484b0c3d66fc475d547 100644 (file)
@@ -117,7 +117,7 @@ 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` or `:rfc2822` to the end of the fieldname; e.g.
+`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g.
 `%(taggerdate:relative)`.
 
 
index 3417cb3f596c6f99177da58e2e0365ee1236c560..c579fbc2b858d0a6fb5cd8dcbcaa25d3b3fca2a5 100644 (file)
@@ -112,6 +112,11 @@ will be fetched and consulted first during a 'git p4 sync'.  Since
 importing directly from p4 is considerably slower than pulling changes
 from a Git remote, this can be useful in a multi-developer environment.
 
+If there are multiple branches, doing 'git p4 sync' will automatically
+use the "BRANCH DETECTION" algorithm to try to partition new changes
+into the right branch.  This can be overridden with the '--branch'
+option to specify just a single branch to update.
+
 
 Rebase
 ~~~~~~
@@ -173,9 +178,11 @@ subsequent 'sync' operations.
 
 --branch <branch>::
        Import changes into given branch.  If the branch starts with
-       'refs/', it will be used as is, otherwise the path 'refs/heads/'
-       will be prepended.  The default branch is 'master'.  If used
-       with an initial clone, no HEAD will be checked out.
+       'refs/', it will be used as is.  Otherwise if it does not start
+       with 'p4/', that prefix is added.  The branch is assumed to
+       name a remote tracking, but this can be modified using
+       '--import-local', or by giving a full ref name.  The default
+       branch is 'master'.
 +
 This example imports a new remote "p4/proj2" into an existing
 Git repository:
@@ -287,6 +294,11 @@ These options can be used to modify 'git p4 submit' behavior.
        to bypass the prompt, causing conflicting commits to be automatically
        skipped, or to quit trying to apply commits, without prompting.
 
+--branch <branch>::
+       After submitting, sync this named branch instead of the default
+       p4/master.  See the "Sync options" section above for more
+       information.
+
 Rebase options
 ~~~~~~~~~~~~~~
 These options can be used to modify 'git p4 rebase' behavior.
@@ -394,8 +406,10 @@ the path elements in the p4 repository.  The example above relied on the
 presence of the p4 branch.  Without p4 branches, the same result will
 occur with:
 ----
+git init depot
+cd depot
 git config git-p4.branchList main:branch1
-git p4 clone --detect-branches //depot@all
+git p4 clone --detect-branches //depot@all .
 ----
 
 
index 978d8da50c150a695565958d89daade5434ce8e0..a404b47b7bf80ef85e5f34595044090b7025f32c 100644 (file)
@@ -8,20 +8,20 @@ git-reset - Reset current HEAD to the specified state
 SYNOPSIS
 --------
 [verse]
-'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' (--patch | -p) [<commit>] [--] [<paths>...]
+'git reset' [-q] [<tree-ish>] [--] <paths>...
+'git reset' (--patch | -p) [<tree-sh>] [--] [<paths>...]
 'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
 
 DESCRIPTION
 -----------
-In the first and second form, copy entries from <commit> to the index.
+In the first and second form, copy entries from <tree-ish> to the index.
 In the third form, set the current branch head (HEAD) to <commit>, optionally
-modifying index and working tree to match.  The <commit> defaults to HEAD
-in all forms.
+modifying index and working tree to match.  The <tree-ish>/<commit> defaults
+to HEAD in all forms.
 
-'git reset' [-q] [<commit>] [--] <paths>...::
+'git reset' [-q] [<tree-ish>] [--] <paths>...::
        This form resets the index entries for all <paths> to their
-       state at <commit>.  (It does not affect the working tree, nor
+       state at <tree-ish>.  (It does not affect the working tree, nor
        the current branch.)
 +
 This means that `git reset <paths>` is the opposite of `git add
@@ -34,9 +34,9 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' (--patch | -p) [<commit>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]::
        Interactively select hunks in the difference between the index
-       and <commit> (defaults to HEAD).  The chosen hunks are applied
+       and <tree-ish> (defaults to HEAD).  The chosen hunks are applied
        in reverse to the index.
 +
 This means that `git reset -p` is the opposite of `git add -p`, i.e.
index eed921e6d6ab2b4bf8749379dcc8164ab3e2bfbc..c87bfcb674b9a1026ab0d7b8e4907d538c99c7e5 100644 (file)
@@ -35,7 +35,13 @@ OPTIONS
 -------
 -s::
 --strip-comments::
-       Skip and remove all lines starting with '#'.
+       Skip and remove all lines starting with comment character (default '#').
+
+-c::
+--comment-lines::
+       Prepend comment character and blank to each line. Lines will automatically
+       be terminated with a newline. On empty lines, only the comment character
+       will be prepended.
 
 EXAMPLES
 --------
index 26babb3aad39c91493efcc03a8aca281282781a5..8181e4ed2ad815e5cdc3fe840c9bcdad33ffdd2d 100644 (file)
@@ -176,6 +176,35 @@ save and restore any form of metadata associated with the working tree
 (eg: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl
 for an example of how to do this.
 
+pre-push
+~~~~~~~~
+
+This hook is called by 'git push' and can be used to prevent a push from taking
+place.  The hook is called with two parameters which provide the name and
+location of the destination remote, if a named remote is not being used both
+values will be the same.
+
+Information about what is to be pushed is provided on the hook's standard
+input with lines of the form:
+
+  <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF
+
+For instance, if the command +git push origin master:foreign+ were run the
+hook would receive a line like the following:
+
+  refs/heads/master 67890 refs/heads/foreign 12345
+
+although the full, 40-character SHA1s would be supplied.  If the foreign ref
+does not yet exist the `<remote SHA1>` will be 40 `0`.  If a ref is to be
+deleted, the `<local ref>` will be supplied as `(delete)` and the `<local
+SHA1>` will be 40 `0`.  If the local commit was specified by something other
+than a name which could be expanded (such as `HEAD~`, or a SHA1) it will be
+supplied as it was originally given.
+
+If this hook exits with a non-zero status, 'git push' will abort without
+pushing anything.  Information about why the push is rejected may be sent
+to the user by writing to standard error.
+
 [[pre-receive]]
 pre-receive
 ~~~~~~~~~~~
index bc7a0462fa883334df42f3ee8aa5556e55b1015a..54e334e3af1e790b132da5b6af761326d586ce8a 100644 (file)
@@ -184,8 +184,10 @@ The second .gitignore prevents Git from ignoring
 
 SEE ALSO
 --------
-linkgit:git-rm[1], linkgit:git-update-index[1],
-linkgit:gitrepository-layout[5]
+linkgit:git-rm[1],
+linkgit:git-update-index[1],
+linkgit:gitrepository-layout[5],
+linkgit:git-check-ignore[1]
 
 GIT
 ---
index 86715735b34c5f92f786fd2f62dd44c6e8d7205b..33ae69c11f0f70b6dd1881f0ae013eca653acf5c 100644 (file)
@@ -10,35 +10,42 @@ Content-type: text/asciidoc
 How to maintain Git
 ===================
 
+Activities
+----------
+
 The maintainer's Git time is spent on three activities.
 
- - Communication (60%)
+ - Communication (45%)
 
    Mailing list discussions on general design, fielding user
    questions, diagnosing bug reports; reviewing, commenting on,
    suggesting alternatives to, and rejecting patches.
 
- - Integration (30%)
+ - Integration (50%)
 
    Applying new patches from the contributors while spotting and
    correcting minor mistakes, shuffling the integration and
    testing branches, pushing the results out, cutting the
    releases, and making announcements.
 
- - Own development (10%)
+ - Own development (5%)
 
    Scratching my own itch and sending proposed patch series out.
 
+The Policy
+----------
+
 The policy on Integration is informally mentioned in "A Note
 from the maintainer" message, which is periodically posted to
 this mailing list after each feature release is made.
 
-The policy.
-
  - Feature releases are numbered as vX.Y.Z and are meant to
    contain bugfixes and enhancements in any area, including
    functionality, performance and usability, without regression.
 
+ - One release cycle for a feature release is expected to last for
+   eight to ten weeks.
+
  - Maintenance releases are numbered as vX.Y.Z.W and are meant
    to contain only bugfixes for the corresponding vX.Y.Z feature
    release and earlier maintenance releases vX.Y.Z.V (V < W).
@@ -62,12 +69,15 @@ The policy.
  - 'pu' branch is used to publish other proposed changes that do
    not yet pass the criteria set for 'next'.
 
- - The tips of 'master', 'maint' and 'next' branches will always
-   fast-forward, to allow people to build their own
-   customization on top of them.
+ - The tips of 'master' and 'maint' branches will not be rewound to
+   allow people to build their own customization on top of them.
+   Early in a new development cycle, 'next' is rewound to the tip of
+   'master' once, but otherwise it will not be rewound until the end
+   of the cycle.
 
- - Usually 'master' contains all of 'maint', 'next' contains all
-   of 'master' and 'pu' contains all of 'next'.
+ - Usually 'master' contains all of 'maint' and 'next' contains all
+   of 'master'.  'pu' contains all the topics merged to 'next', but
+   is rebuilt directly on 'master'.
 
  - The tip of 'master' is meant to be more stable than any
    tagged releases, and the users are encouraged to follow it.
@@ -77,14 +87,22 @@ The policy.
    are found before new topics are merged to 'master'.
 
 
+A Typical Git Day
+-----------------
+
 A typical Git day for the maintainer implements the above policy
 by doing the following:
 
- - Scan mailing list and #git channel log.  Respond with review
-   comments, suggestions etc.  Kibitz.  Collect potentially
-   usable patches from the mailing list.  Patches about a single
-   topic go to one mailbox (I read my mail in Gnus, and type
-   \C-o to save/append messages in files in mbox format).
+ - Scan mailing list.  Respond with review comments, suggestions
+   etc.  Kibitz.  Collect potentially usable patches from the
+   mailing list.  Patches about a single topic go to one mailbox (I
+   read my mail in Gnus, and type \C-o to save/append messages in
+   files in mbox format).
+
+ - Write his own patches to address issues raised on the list but
+   nobody has stepped up solving.  Send it out just like other
+   contributors do, and pick them up just like patches from other
+   contributors (see above).
 
  - Review the patches in the saved mailboxes.  Edit proposed log
    message for typofixes and clarifications, and add Acks
@@ -100,40 +118,32 @@ by doing the following:
    - Obviously correct fixes that pertain to the tip of 'master'
      are directly applied to 'master'.
 
+   - Other topics are not handled in this step.
+
    This step is done with "git am".
 
      $ git checkout master    ;# or "git checkout maint"
-     $ git am -3 -s mailbox
+     $ git am -sc3 mailbox
      $ make test
 
- - Merge downwards (maint->master):
-
-     $ git checkout master
-     $ git merge maint
-     $ make test
+   In practice, almost no patch directly goes to 'master' or
+   'maint'.
 
  - Review the last issue of "What's cooking" message, review the
-   topics scheduled for merging upwards (topic->master and
-   topic->maint), and merge.
+   topics ready for merging (topic->master and topic->maint).  Use
+   "Meta/cook -w" script (where Meta/ contains a checkout of the
+   'todo' branch) to aid this step.
+
+   And perform the merge.  Use "Meta/Reintegrate -e" script (see
+   later) to aid this step.
+
+     $ Meta/cook -w last-issue-of-whats-cooking.mbox
 
      $ git checkout master    ;# or "git checkout maint"
-     $ git merge ai/topic     ;# or "git merge ai/maint-topic"
+     $ echo ai/topic | Meta/Reintegrate -e ;# "git merge ai/topic"
      $ git log -p ORIG_HEAD.. ;# final review
      $ git diff ORIG_HEAD..   ;# final review
      $ make test              ;# final review
-     $ git branch -d ai/topic ;# or "git branch -d ai/maint-topic"
-
- - Merge downwards (maint->master) if needed:
-
-     $ git checkout master
-     $ git merge maint
-     $ make test
-
- - Merge downwards (master->next) if needed:
-
-     $ git checkout next
-     $ git merge master
-     $ make test
 
  - Handle the remaining patches:
 
@@ -142,9 +152,9 @@ by doing the following:
      and not in 'master') is applied to a new topic branch that
      is forked from the tip of 'master'.  This includes both
      enhancements and unobvious fixes to 'master'.  A topic
-     branch is named as ai/topic where "ai" is typically
-     author's initial and "topic" is a descriptive name of the
-     topic (in other words, "what's the series is about").
+     branch is named as ai/topic where "ai" is two-letter string
+     named after author's initial and "topic" is a descriptive name
+     of the topic (in other words, "what's the series is about").
 
    - An unobvious fix meant for 'maint' is applied to a new
      topic branch that is forked from the tip of 'maint'.  The
@@ -162,7 +172,8 @@ by doing the following:
 
    The above except the "replacement" are all done with:
 
-     $ git am -3 -s mailbox
+     $ git checkout ai/topic ;# or "git checkout -b ai/topic master"
+     $ git am -sc3 mailbox
 
    while patch replacement is often done by:
 
@@ -170,93 +181,170 @@ by doing the following:
 
    then replace some parts with the new patch, and reapplying:
 
+     $ git checkout ai/topic
      $ git reset --hard ai/topic~$n
-     $ git am -3 -s 000*.txt
+     $ git am -sc3 -s 000*.txt
 
    The full test suite is always run for 'maint' and 'master'
    after patch application; for topic branches the tests are run
    as time permits.
 
- - Update "What's cooking" message to review the updates to
-   existing topics, newly added topics and graduated topics.
+ - Merge maint to master as needed:
 
-   This step is helped with Meta/cook script (where Meta/ contains
-   a checkout of the 'todo' branch).
-
- - Merge topics to 'next'.  For each branch whose tip is not
-   merged to 'next', one of three things can happen:
+     $ git checkout master
+     $ git merge maint
+     $ make test
 
  - The commits are all next-worthy; merge the topic to next:
- Merge master to next as needed:
 
      $ git checkout next
-     $ git merge ai/topic     ;# or "git merge ai/maint-topic"
+     $ git merge master
      $ make test
 
+ - Review the last issue of "What's cooking" again and see if topics
+   that are ready to be merged to 'next' are still in good shape
+   (e.g. has there any new issue identified on the list with the
+   series?)
+
+ - Prepare 'jch' branch, which is used to represent somewhere
+   between 'master' and 'pu' and often is slightly ahead of 'next'.
+
+     $ Meta/Reintegrate master..pu >Meta/redo-jch.sh
+
+   The result is a script that lists topics to be merged in order to
+   rebuild 'pu' as the input to Meta/Reintegrate script.  Remove
+   later topics that should not be in 'jch' yet.  Add a line that
+   consists of '### match next' before the name of the first topic
+   in the output that should be in 'jch' but not in 'next' yet.
+
+ - Now we are ready to start merging topics to 'next'.  For each
+   branch whose tip is not merged to 'next', one of three things can
+   happen:
+
+   - The commits are all next-worthy; merge the topic to next;
    - The new parts are of mixed quality, but earlier ones are
-     next-worthy; merge the early parts to next:
+     next-worthy; merge the early parts to next;
+   - Nothing is next-worthy; do not do anything.
+
+   This step is aided with Meta/redo-jch.sh script created earlier.
+   If a topic that was already in 'next' gained a patch, the script
+   would list it as "ai/topic~1".  To include the new patch to the
+   updated 'next', drop the "~1" part; to keep it excluded, do not
+   touch the line.  If a topic that was not in 'next' should be
+   merged to 'next', add it at the end of the list.  Then:
+
+     $ git checkout -B jch master
+     $ Meta/redo-jch.sh -c1
+
+   to rebuild the 'jch' branch from scratch.  "-c1" tells the script
+   to stop merging at the first line that begins with '###'
+   (i.e. the "### match next" line you added earlier).
+
+   At this point, build-test the result.  It may reveal semantic
+   conflicts (e.g. a topic renamed a variable, another added a new
+   reference to the variable under its old name), in which case
+   prepare an appropriate merge-fix first (see appendix), and
+   rebuild the 'jch' branch from scratch, starting at the tip of
+   'master'.
+
+   Then do the same to 'next'
 
      $ git checkout next
-     $ git merge ai/topic~2   ;# the tip two are dubious
-     $ make test
+     $ sh Meta/redo-jch.sh -c1 -e
 
-   - Nothing is next-worthy; do not do anything.
+   The "-e" option allows the merge message that comes from the
+   history of the topic and the comments in the "What's cooking" to
+   be edited.  The resulting tree should match 'jch' as the same set
+   of topics are merged on 'master'; otherwise there is a mismerge.
+   Investigate why and do not proceed until the mismerge is found
+   and rectified.
 
- - [** OBSOLETE **] Optionally rebase topics that do not have any commit
-   in next yet, when they can take advantage of low-level framework
-   change that is merged to 'master' already.
+     $ git diff jch next
 
-     $ git rebase master ai/topic
+   When all is well, clean up the redo-jch.sh script with
 
-   This step is helped with Meta/git-topic.perl script to
-   identify which topic is rebaseable.  There also is a
-   pre-rebase hook to make sure that topics that are already in
-   'next' are not rebased beyond the merged commit.
+     $ sh Meta/redo-jch.sh -u
 
- - [** OBSOLETE **] Rebuild "pu" to merge the tips of topics not in 'next'.
+   This removes topics listed in the script that have already been
+   merged to 'master'.  This may lose '### match next' marker;
+   add it again to the appropriate place when it happens.
 
-     $ git checkout pu
-     $ git reset --hard next
-     $ git merge ai/topic     ;# repeat for all remaining topics
-     $ make test
+ - Rebuild 'pu'.
 
-   This step is helped with Meta/PU script
+     $ Meta/Reintegrate master..pu >Meta/redo-pu.sh
 
- - Push four integration branches to a private repository at
-   k.org and run "make test" on all of them.
+   Edit the result by adding new topics that are not still in 'pu'
+   in the script.  Then
 
- - Push four integration branches to /pub/scm/git/git.git at
-   k.org.  This triggers its post-update hook which:
+     $ git checkout -B pu jch
+     $ sh Meta/redo-pu.sh
 
-    (1) runs "git pull" in $HOME/git-doc/ repository to pull
-        'master' just pushed out;
+   When all is well, clean up the redo-pu.sh script with
 
-    (2) runs "make doc" in $HOME/git-doc/, install the generated
-        documentation in staging areas, which are separate
-        repositories that have html and man branches checked
-        out.
+     $ sh Meta/redo-pu.sh -u
 
-    (3) runs "git commit" in the staging areas, and run "git
-        push" back to /pub/scm/git/git.git/ to update the html
-        and man branches.
+   Double check by running
 
-    (4) installs generated documentation to /pub/software/scm/git/docs/
-        to be viewed from http://www.kernel.org/
+     $ git branch --no-merged pu
 
- - Fetch html and man branches back from k.org, and push four
-   integration branches and the two documentation branches to
-   repo.or.cz and other mirrors.
+   to see there is no unexpected leftover topics.
 
+   At this point, build-test the result for semantic conflicts, and
+   if there are, prepare an appropriate merge-fix first (see
+   appendix), and rebuild the 'pu' branch from scratch, starting at
+   the tip of 'jch'.
+
+ - Update "What's cooking" message to review the updates to
+   existing topics, newly added topics and graduated topics.
+
+   This step is helped with Meta/cook script.
+
+     $ Meta/cook
+
+   This script inspects the history between master..pu, finds tips
+   of topic branches, compares what it found with the current
+   contents in Meta/whats-cooking.txt, and updates that file.
+   Topics not listed in the file but are found in master..pu are
+   added to the "New topics" section, topics listed in the file that
+   are no longer found in master..pu are moved to the "Graduated to
+   master" section, and topics whose commits changed their states
+   (e.g. used to be only in 'pu', now merged to 'next') are updated
+   with change markers "<<" and ">>".
+
+   Look for lines enclosed in "<<" and ">>"; they hold contents from
+   old file that are replaced by this integration round.  After
+   verifying them, remove the old part.  Review the description for
+   each topic and update its doneness and plan as needed.  To review
+   the updated plan, run
+
+     $ Meta/cook -w
+
+   which will pick up comments given to the topics, such as "Will
+   merge to 'next'", etc. (see Meta/cook script to learn what kind
+   of phrases are supported).
+
+ - Compile, test and install all four (five) integration branches;
+   Meta/Dothem script may aid this step.
+
+ - Format documentation if the 'master' branch was updated;
+   Meta/dodoc.sh script may aid this step.
+
+ - Push the integration branches out to public places; Meta/pushall
+   script may aid this step.
+
+Observations
+------------
 
 Some observations to be made.
 
- * Each topic is tested individually, and also together with
-   other topics cooking in 'next'.  Until it matures, none part
-   of it is merged to 'master'.
+ * Each topic is tested individually, and also together with other
+   topics cooking first in 'pu', then in 'jch' and then in 'next'.
+   Until it matures, no part of it is merged to 'master'.
 
  * A topic already in 'next' can get fixes while still in
    'next'.  Such a topic will have many merges to 'next' (in
    other words, "git log --first-parent next" will show many
-   "Merge ai/topic to next" for the same topic.
+   "Merge branch 'ai/topic' to next" for the same topic.
 
  * An unobvious fix for 'maint' is cooked in 'next' and then
    merged to 'master' to make extra sure it is Ok and then
@@ -278,3 +366,80 @@ Some observations to be made.
  * Being in the 'next' branch is not a guarantee for a topic to
    be included in the next feature release.  Being in the
    'master' branch typically is.
+
+
+Appendix
+--------
+
+Preparing a "merge-fix"
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A merge of two topics may not textually conflict but still have
+conflict at the semantic level. A classic example is for one topic
+to rename an variable and all its uses, while another topic adds a
+new use of the variable under its old name. When these two topics
+are merged together, the reference to the variable newly added by
+the latter topic will still use the old name in the result.
+
+The Meta/Reintegrate script that is used by redo-jch and redo-pu
+scripts implements a crude but usable way to work this issue around.
+When the script merges branch $X, it checks if "refs/merge-fix/$X"
+exists, and if so, the effect of it is squashed into the result of
+the mechanical merge.  In other words,
+
+     $ echo $X | Meta/Reintegrate
+
+is roughly equivalent to this sequence:
+
+     $ git merge --rerere-autoupdate $X
+     $ git commit
+     $ git cherry-pick -n refs/merge-fix/$X
+     $ git commit --amend
+
+The goal of this "prepare a merge-fix" step is to come up with a
+commit that can be squashed into a result of mechanical merge to
+correct semantic conflicts.
+
+After finding that the result of merging branch "ai/topic" to an
+integration branch had such a semantic conflict, say pu~4, check the
+problematic merge out on a detached HEAD, edit the working tree to
+fix the semantic conflict, and make a separate commit to record the
+fix-up:
+
+     $ git checkout pu~4
+     $ git show -s --pretty=%s ;# double check
+     Merge branch 'ai/topic' to pu
+     $ edit
+     $ git commit -m 'merge-fix/ai/topic' -a
+
+Then make a reference "refs/merge-fix/ai/topic" to point at this
+result:
+
+     $ git update-ref refs/merge-fix/ai/topic HEAD
+
+Then double check the result by asking Meta/Reintegrate to redo the
+merge:
+
+     $ git checkout pu~5 ;# the parent of the problem merge
+     $ echo ai/topic | Meta/Reintegrate
+     $ git diff pu~4
+
+This time, because you prepared refs/merge-fix/ai/topic, the
+resulting merge should have been tweaked to include the fix for the
+semantic conflict.
+
+Note that this assumes that the order in which conflicting branches
+are merged does not change.  If the reason why merging ai/topic
+branch needs this merge-fix is because another branch merged earlier
+to the integration branch changed the underlying assumption ai/topic
+branch made (e.g. ai/topic branch added a site to refer to a
+variable, while the other branch renamed that variable and adjusted
+existing use sites), and if you changed redo-jch (or redo-pu) script
+to merge ai/topic branch before the other branch, then the above
+merge-fix should not be applied while merging ai/topic, but should
+instead be applied while merging the other branch.  You would need
+to move the fix to apply to the other branch, perhaps like this:
+
+      $ mf=refs/merge-fix
+      $ git update-ref $mf/$the_other_branch $mf/ai/topic
+      $ git update-ref -d $mf/ai/topic
index fa2c5d58e9ca6664837d2121ce80d78bf531e6a6..1f349b28ae175327706fa67a3e0b09f1a26a9c02 100644 (file)
@@ -67,11 +67,13 @@ marked. If you to exclude files, make sure you have loaded index first.
 * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0,
   sizeof(dir))`.
 
-* Call `add_exclude()` to add single exclude pattern,
-  `add_excludes_from_file()` to add patterns from a file
-  (e.g. `.git/info/exclude`), and/or set `dir.exclude_per_dir`.  A
-  short-hand function `setup_standard_excludes()` can be used to set up
-  the standard set of exclude settings.
+* To add single exclude pattern, call `add_exclude_list()` and then
+  `add_exclude()`.
+
+* To add patterns from a file (e.g. `.git/info/exclude`), call
+  `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`.  A
+  short-hand function `setup_standard_excludes()` can be used to set
+  up the standard set of exclude settings.
 
 * Set options described in the Data Structure section above.
 
@@ -79,4 +81,6 @@ marked. If you to exclude files, make sure you have loaded index first.
 
 * Use `dir.entries[]`.
 
+* Call `clear_directory()` when none of the contained elements are no longer in use.
+
 (JC)
index 84686b5c69348ef67819818b7223c77f501965d0..2c59cb2259d941232652241dc1cadcfc0c3ef812 100644 (file)
@@ -156,6 +156,11 @@ then they will free() it.
        Remove the bytes between `pos..pos+len` and replace it with the given
        data.
 
+`strbuf_add_commented_lines`::
+
+       Add a NUL-terminated string to the buffer. Each line will be prepended
+       by a comment character and a blank.
+
 `strbuf_add`::
 
        Add data of given length to the buffer.
@@ -229,6 +234,11 @@ which can be used by the programmer of the callback as she sees fit.
 
        Add a formatted string to the buffer.
 
+`strbuf_commented_addf`::
+
+       Add a formatted string prepended by a comment character and a
+       blank to the buffer.
+
 `strbuf_fread`::
 
        Read a given size of data from a FILE* pointer to the buffer.
index 0502a5471e699aac572abd5ab82c71a03f07e9f1..ea2f69faf5cd1e87226eb2a8c281aa4d5f6ed204 100644 (file)
@@ -53,3 +53,6 @@ It also writes an appropriate $GIT_DIR/shallow.
 You can deepen a shallow repository with "git-fetch --depth 20
 repo branch", which will fetch branch from repo, but stop at depth
 20, updating $GIT_DIR/shallow.
+
+The special depth 2147483647 (or 0x7fffffff, the largest positive
+number a signed 32-bit integer can contain) means infinite depth.
diff --git a/INSTALL b/INSTALL
index 28f34bd254d8b0be484b5aedce1c35079fd32cc4..2dc3b61d1fe1b675377a6ece96feaddf8a0721ba 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -131,8 +131,9 @@ Issues of note:
          use English. Under autoconf the configure script will do this
          automatically if it can't find libintl on the system.
 
-       - Python version 2.6 or later is needed to use the git-p4
-         interface to Perforce.
+       - Python version 2.4 or later (but not 3.x, which is not
+         supported by Perforce) is needed to use the git-p4 interface
+         to Perforce.
 
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
index 1b30d7bde301305cde0d3af74c7e478e36bd673b..731b6a8834834fe6e6d1852e57752ee38d4321b0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -105,6 +105,9 @@ all::
 # Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the
 # FNM_CASEFOLD GNU extension.
 #
+# Define USE_WILDMATCH if you want to use Git's wildmatch
+# implementation as fnmatch
+#
 # Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd
 # in the C library.
 #
@@ -241,11 +244,16 @@ all::
 # apostrophes to be ASCII so that cut&pasting examples to the shell
 # will work.
 #
+# Define PERL_PATH to the path of your Perl binary (usually /usr/bin/perl).
+#
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
 # Define NO_PERL if you do not want Perl scripts or libraries at all.
 #
+# Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python
+# but /usr/bin/python2.7 on some platforms).
+#
 # Define NO_PYTHON if you do not want Python scripts or libraries at all.
 #
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
@@ -649,7 +657,7 @@ LIB_H += list-objects.h
 LIB_H += ll-merge.h
 LIB_H += log-tree.h
 LIB_H += mailmap.h
-LIB_H += merge-file.h
+LIB_H += merge-blobs.h
 LIB_H += merge-recursive.h
 LIB_H += mergesort.h
 LIB_H += notes-cache.h
@@ -661,6 +669,7 @@ LIB_H += pack-revindex.h
 LIB_H += pack.h
 LIB_H += parse-options.h
 LIB_H += patch-ids.h
+LIB_H += pathspec.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
 LIB_H += prompt.h
@@ -784,6 +793,7 @@ LIB_OBJS += parse-options-cb.o
 LIB_OBJS += patch-delta.o
 LIB_OBJS += patch-ids.o
 LIB_OBJS += path.o
+LIB_OBJS += pathspec.o
 LIB_OBJS += pkt-line.o
 LIB_OBJS += preload-index.o
 LIB_OBJS += pretty.o
@@ -849,6 +859,7 @@ BUILTIN_OBJS += builtin/branch.o
 BUILTIN_OBJS += builtin/bundle.o
 BUILTIN_OBJS += builtin/cat-file.o
 BUILTIN_OBJS += builtin/check-attr.o
+BUILTIN_OBJS += builtin/check-ignore.o
 BUILTIN_OBJS += builtin/check-ref-format.o
 BUILTIN_OBJS += builtin/checkout-index.o
 BUILTIN_OBJS += builtin/checkout.o
@@ -1217,6 +1228,9 @@ ifdef NO_FNMATCH_CASEFOLD
        COMPAT_OBJS += compat/fnmatch/fnmatch.o
 endif
 endif
+ifdef USE_WILDMATCH
+       COMPAT_CFLAGS += -DUSE_WILDMATCH
+endif
 ifdef NO_SETENV
        COMPAT_CFLAGS += -DNO_SETENV
        COMPAT_OBJS += compat/setenv.o
diff --git a/README b/README
index f7fc0dec28757da649925a02a1d0530cfb1cf5bc..15a8e235012a247e6f22abd2c298a2e43c940c61 100644 (file)
--- a/README
+++ b/README
@@ -47,11 +47,10 @@ requests, comments and patches to git@vger.kernel.org (read
 Documentation/SubmittingPatches for instructions on patch submission).
 To subscribe to the list, send an email with just "subscribe git" in
 the body to majordomo@vger.kernel.org. The mailing list archives are
-available at http://marc.theaimsgroup.com/?l=git and other archival
-sites.
-
-The messages titled "A note from the maintainer", "What's in
-git.git (stable)" and "What's cooking in git.git (topics)" and
-the discussion following them on the mailing list give a good
-reference for project status, development direction and
-remaining tasks.
+available at http://news.gmane.org/gmane.comp.version-control.git/,
+http://marc.info/?l=git and other archival sites.
+
+The maintainer frequently sends the "What's cooking" reports that
+list the current status of various development topics to the mailing
+list.  The discussion following them give a good reference for
+project status, development direction and remaining tasks.
index d2879272802cb3477dd6b47a78098ffdc26dcb37..780f58da0f5b508eaa278c98dd15bdf6f919c468 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -5,6 +5,8 @@ int advice_push_non_ff_current = 1;
 int advice_push_non_ff_default = 1;
 int advice_push_non_ff_matching = 1;
 int advice_push_already_exists = 1;
+int advice_push_fetch_first = 1;
+int advice_push_needs_force = 1;
 int advice_status_hints = 1;
 int advice_commit_before_merge = 1;
 int advice_resolve_conflict = 1;
@@ -20,6 +22,8 @@ static struct {
        { "pushnonffdefault", &advice_push_non_ff_default },
        { "pushnonffmatching", &advice_push_non_ff_matching },
        { "pushalreadyexists", &advice_push_already_exists },
+       { "pushfetchfirst", &advice_push_fetch_first },
+       { "pushneedsforce", &advice_push_needs_force },
        { "statushints", &advice_status_hints },
        { "commitbeforemerge", &advice_commit_before_merge },
        { "resolveconflict", &advice_resolve_conflict },
index 8bf63563a5cf1573e0e6bf7ffcd5c55643d3f439..fad36df467f065bf7ff7a82d9d447ee4ac5cecb9 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -8,6 +8,8 @@ extern int advice_push_non_ff_current;
 extern int advice_push_non_ff_default;
 extern int advice_push_non_ff_matching;
 extern int advice_push_already_exists;
+extern int advice_push_fetch_first;
+extern int advice_push_needs_force;
 extern int advice_status_hints;
 extern int advice_commit_before_merge;
 extern int advice_resolve_conflict;
index d1cce46e3310d64af32b231de697449ae90ddccf..719b6298e6abf9c9e9e8009ec49dfc76d0d9e49b 100644 (file)
@@ -327,20 +327,12 @@ static struct archiver *find_tar_filter(const char *name, int len)
 static int tar_filter_config(const char *var, const char *value, void *data)
 {
        struct archiver *ar;
-       const char *dot;
        const char *name;
        const char *type;
        int namelen;
 
-       if (prefixcmp(var, "tar."))
+       if (parse_config_key(var, "tar", &name, &namelen, &type) < 0 || !name)
                return 0;
-       dot = strrchr(var, '.');
-       if (dot == var + 9)
-               return 0;
-
-       name = var + 4;
-       namelen = dot - name;
-       type = dot + 1;
 
        ar = find_tar_filter(name, namelen);
        if (!ar) {
diff --git a/attr.c b/attr.c
index 233539969af6dc17e8953e4a553cae76fd9601da..4657cc233c4aa7cff90e745a58ac7ad0a5fa8cd2 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -564,25 +564,12 @@ static void bootstrap_attr_stack(void)
        attr_stack = elem;
 }
 
-static const char *find_basename(const char *path)
-{
-       const char *cp, *last_slash = NULL;
-
-       for (cp = path; *cp; cp++) {
-               if (*cp == '/' && cp[1])
-                       last_slash = cp;
-       }
-       return last_slash ? last_slash + 1 : path;
-}
-
-static void prepare_attr_stack(const char *path)
+static void prepare_attr_stack(const char *path, int dirlen)
 {
        struct attr_stack *elem, *info;
-       int dirlen, len;
+       int len;
        const char *cp;
 
-       dirlen = find_basename(path) - path;
-
        /*
         * At the bottom of the attribute stack is the built-in
         * set of attribute definitions, followed by the contents
@@ -762,15 +749,26 @@ static int macroexpand_one(int attr_nr, int rem)
 static void collect_all_attrs(const char *path)
 {
        struct attr_stack *stk;
-       int i, pathlen, rem;
-       const char *basename;
+       int i, pathlen, rem, dirlen;
+       const char *basename, *cp, *last_slash = NULL;
+
+       for (cp = path; *cp; cp++) {
+               if (*cp == '/' && cp[1])
+                       last_slash = cp;
+       }
+       pathlen = cp - path;
+       if (last_slash) {
+               basename = last_slash + 1;
+               dirlen = last_slash - path;
+       } else {
+               basename = path;
+               dirlen = 0;
+       }
 
-       prepare_attr_stack(path);
+       prepare_attr_stack(path, dirlen);
        for (i = 0; i < attr_nr; i++)
                check_all_attr[i].value = ATTR__UNKNOWN;
 
-       basename = find_basename(path);
-       pathlen = strlen(path);
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
                rem = fill(path, pathlen, basename, stk, rem);
index 7e7bbd665a905b0fdf9ce5351f6f09dfd3dd7e8a..faef55913634a21a757b4814f855ed834c32915e 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -52,6 +52,7 @@ extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
+extern int cmd_check_ignore(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
index 075312afcd8813c3a7dbf08f6be519a37498fe4f..7738025a2e56ea3fbd256666998040981360e5ab 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
+#include "pathspec.h"
 #include "exec_cmd.h"
 #include "cache-tree.h"
 #include "run-command.h"
@@ -97,39 +98,6 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
        return !!data.add_errors;
 }
 
-static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
-{
-       int num_unmatched = 0, i;
-
-       /*
-        * Since we are walking the index as if we were walking the directory,
-        * we have to mark the matched pathspec as seen; otherwise we will
-        * mistakenly think that the user gave a pathspec that did not match
-        * anything.
-        */
-       for (i = 0; i < specs; i++)
-               if (!seen[i])
-                       num_unmatched++;
-       if (!num_unmatched)
-               return;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
-       }
-}
-
-static char *find_used_pathspec(const char **pathspec)
-{
-       char *seen;
-       int i;
-
-       for (i = 0; pathspec[i];  i++)
-               ; /* just counting */
-       seen = xcalloc(i, 1);
-       fill_pathspec_matches(pathspec, seen, i);
-       return seen;
-}
-
 static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
 {
        char *seen;
@@ -149,10 +117,14 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int
                        *dst++ = entry;
        }
        dir->nr = dst - dir->entries;
-       fill_pathspec_matches(pathspec, seen, specs);
+       add_pathspec_matches_against_index(pathspec, seen, specs);
        return seen;
 }
 
+/*
+ * Checks the index to see whether any path in pathspec refers to
+ * something inside a submodule.  If so, dies with an error message.
+ */
 static void treat_gitlinks(const char **pathspec)
 {
        int i;
@@ -160,24 +132,8 @@ static void treat_gitlinks(const char **pathspec)
        if (!pathspec || !*pathspec)
                return;
 
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (S_ISGITLINK(ce->ce_mode)) {
-                       int len = ce_namelen(ce), j;
-                       for (j = 0; pathspec[j]; j++) {
-                               int len2 = strlen(pathspec[j]);
-                               if (len2 <= len || pathspec[j][len] != '/' ||
-                                   memcmp(ce->name, pathspec[j], len))
-                                       continue;
-                               if (len2 == len + 1)
-                                       /* strip trailing slash */
-                                       pathspec[j] = xstrndup(ce->name, len);
-                               else
-                                       die (_("Path '%s' is in submodule '%.*s'"),
-                                               pathspec[j], len, ce->name);
-                       }
-               }
-       }
+       for (i = 0; pathspec[i]; i++)
+               pathspec[i] = check_path_for_gitlink(pathspec[i]);
 }
 
 static void refresh(int verbose, const char **pathspec)
@@ -197,17 +153,19 @@ static void refresh(int verbose, const char **pathspec)
         free(seen);
 }
 
-static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
+/*
+ * Normalizes argv relative to prefix, via get_pathspec(), and then
+ * runs die_if_path_beyond_symlink() on each path in the normalized
+ * list.
+ */
+static const char **validate_pathspec(const char **argv, const char *prefix)
 {
        const char **pathspec = get_pathspec(prefix, argv);
 
        if (pathspec) {
                const char **p;
                for (p = pathspec; *p; p++) {
-                       if (has_symlink_leading_path(*p, strlen(*p))) {
-                               int len = prefix ? strlen(prefix) : 0;
-                               die(_("'%s' is beyond a symbolic link"), *p + len);
-                       }
+                       die_if_path_beyond_symlink(*p, prefix);
                }
        }
 
@@ -248,7 +206,7 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch)
        const char **pathspec = NULL;
 
        if (argc) {
-               pathspec = validate_pathspec(argc, argv, prefix);
+               pathspec = validate_pathspec(argv, prefix);
                if (!pathspec)
                        return -1;
        }
@@ -363,6 +321,35 @@ static int add_files(struct dir_struct *dir, int flags)
        return exit_status;
 }
 
+static void warn_pathless_add(const char *option_name, const char *short_name) {
+       /*
+        * To be consistent with "git add -p" and most Git
+        * commands, we should default to being tree-wide, but
+        * this is not the original behavior and can't be
+        * changed until users trained themselves not to type
+        * "git add -u" or "git add -A". For now, we warn and
+        * keep the old behavior. Later, this warning can be
+        * turned into a die(...), and eventually we may
+        * reallow the command with a new behavior.
+        */
+       warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n"
+                 "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n"
+                 "To add content for the whole tree, run:\n"
+                 "\n"
+                 "  git add %s :/\n"
+                 "  (or git add %s :/)\n"
+                 "\n"
+                 "To restrict the command to the current directory, run:\n"
+                 "\n"
+                 "  git add %s .\n"
+                 "  (or git add %s .)\n"
+                 "\n"
+                 "With the current Git version, the command is restricted to the current directory."),
+               option_name, short_name,
+               option_name, short_name,
+               option_name, short_name);
+}
+
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
        int exit_status = 0;
@@ -373,6 +360,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        int add_new_files;
        int require_pathspec;
        char *seen = NULL;
+       const char *option_with_implicit_dot = NULL;
+       const char *short_option_with_implicit_dot = NULL;
 
        git_config(add_config, NULL);
 
@@ -392,8 +381,19 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                die(_("-A and -u are mutually incompatible"));
        if (!show_only && ignore_missing)
                die(_("Option --ignore-missing can only be used together with --dry-run"));
-       if ((addremove || take_worktree_changes) && !argc) {
+       if (addremove) {
+               option_with_implicit_dot = "--all";
+               short_option_with_implicit_dot = "-A";
+       }
+       if (take_worktree_changes) {
+               option_with_implicit_dot = "--update";
+               short_option_with_implicit_dot = "-u";
+       }
+       if (option_with_implicit_dot && !argc) {
                static const char *here[2] = { ".", NULL };
+               if (prefix)
+                       warn_pathless_add(option_with_implicit_dot,
+                                         short_option_with_implicit_dot);
                argc = 1;
                argv = here;
        }
@@ -415,7 +415,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
                return 0;
        }
-       pathspec = validate_pathspec(argc, argv, prefix);
+       pathspec = validate_pathspec(argv, prefix);
 
        if (read_cache() < 0)
                die(_("index file corrupt"));
@@ -448,7 +448,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
                path_exclude_check_init(&check, &dir);
                if (!seen)
-                       seen = find_used_pathspec(pathspec);
+                       seen = find_pathspecs_matching_against_index(pathspec);
                for (i = 0; pathspec[i]; i++) {
                        if (!seen[i] && pathspec[i][0]
                            && !file_exists(pathspec[i])) {
index 6c11e8bc73892b15f91fcb65d65e18ee05956ec8..9706ca73ab0bc2ee4c3cf7ce7ab39591830a1fab 100644 (file)
@@ -3609,7 +3609,6 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
         * worth showing the new sha1 prefix, but until then...
         */
        for (patch = list; patch; patch = patch->next) {
-               const unsigned char *sha1_ptr;
                unsigned char sha1[20];
                struct cache_entry *ce;
                const char *name;
@@ -3617,20 +3616,23 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
                name = patch->old_name ? patch->old_name : patch->new_name;
                if (0 < patch->is_new)
                        continue;
-               else if (get_sha1_blob(patch->old_sha1_prefix, sha1))
-                       /* git diff has no index line for mode/type changes */
-                       if (!patch->lines_added && !patch->lines_deleted) {
-                               if (get_current_sha1(patch->old_name, sha1))
-                                       die("mode change for %s, which is not "
-                                               "in current HEAD", name);
-                               sha1_ptr = sha1;
-                       } else
-                               die("sha1 information is lacking or useless "
-                                       "(%s).", name);
-               else
-                       sha1_ptr = sha1;
 
-               ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
+               if (S_ISGITLINK(patch->old_mode)) {
+                       if (get_sha1_hex(patch->old_sha1_prefix, sha1))
+                               die("submoule change for %s without full index name",
+                                   name);
+               } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) {
+                       ; /* ok */
+               } else if (!patch->lines_added && !patch->lines_deleted) {
+                       /* mode-only change: update the current */
+                       if (get_current_sha1(patch->old_name, sha1))
+                               die("mode change for %s, which is not "
+                                   "in current HEAD", name);
+               } else
+                       die("sha1 information is lacking or useless "
+                           "(%s).", name);
+
+               ce = make_cache_entry(patch->old_mode, sha1, name, 0, 0);
                if (!ce)
                        die(_("make_cache_entry failed for path '%s'"), name);
                if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
index b431ba320997918324a76c29b81dd01613cfa9db..86100e96627e4f839fb5b627bc09ed4264c36176 100644 (file)
@@ -1420,32 +1420,18 @@ static void get_commit_info(struct commit *commit,
 {
        int len;
        const char *subject, *encoding;
-       char *reencoded, *message;
+       char *message;
 
        commit_info_init(ret);
 
-       /*
-        * We've operated without save_commit_buffer, so
-        * we now need to populate them for output.
-        */
-       if (!commit->buffer) {
-               enum object_type type;
-               unsigned long size;
-               commit->buffer =
-                       read_sha1_file(commit->object.sha1, &type, &size);
-               if (!commit->buffer)
-                       die("Cannot read commit %s",
-                           sha1_to_hex(commit->object.sha1));
-       }
        encoding = get_log_output_encoding();
-       reencoded = logmsg_reencode(commit, encoding);
-       message   = reencoded ? reencoded : commit->buffer;
+       message = logmsg_reencode(commit, encoding);
        get_ac_line(message, "\nauthor ",
                    &ret->author, &ret->author_mail,
                    &ret->author_time, &ret->author_tz);
 
        if (!detailed) {
-               free(reencoded);
+               logmsg_free(message, commit);
                return;
        }
 
@@ -1459,7 +1445,7 @@ static void get_commit_info(struct commit *commit,
        else
                strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
 
-       free(reencoded);
+       logmsg_free(message, commit);
 }
 
 /*
index 873f624d1cc70e03da4c67b30f8c839b4afe099d..77b435825c178ead3ff59d7783137c55057a6649 100644 (file)
@@ -706,11 +706,11 @@ static int edit_branch_description(const char *branch_name)
        read_branch_desc(&buf, branch_name);
        if (!buf.len || buf.buf[buf.len-1] != '\n')
                strbuf_addch(&buf, '\n');
-       strbuf_addf(&buf,
-                   "Please edit the description for the branch\n"
-                   "  %s\n"
-                   "# Lines starting with '#' will be stripped.\n",
-                   branch_name);
+       strbuf_commented_addf(&buf,
+                   "Please edit the description for the branch\n"
+                   "  %s\n"
+                   "Lines starting with '%c' will be stripped.\n",
+                   branch_name, comment_line_char);
        fp = fopen(git_path(edit_description), "w");
        if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
                strbuf_release(&buf);
@@ -850,11 +850,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
 
-               if (detached)
-                       die("Cannot give description to detached HEAD");
-               if (!argc)
+               if (!argc) {
+                       if (detached)
+                               die("Cannot give description to detached HEAD");
                        branch_name = head;
-               else if (argc == 1)
+               else if (argc == 1)
                        branch_name = argv[0];
                else
                        usage_with_options(builtin_branch_usage, options);
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
new file mode 100644 (file)
index 0000000..709535c
--- /dev/null
@@ -0,0 +1,173 @@
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "parse-options.h"
+
+static int quiet, verbose, stdin_paths;
+static const char * const check_ignore_usage[] = {
+"git check-ignore [options] pathname...",
+"git check-ignore [options] --stdin < <list-of-paths>",
+NULL
+};
+
+static int null_term_line;
+
+static const struct option check_ignore_options[] = {
+       OPT__QUIET(&quiet, N_("suppress progress reporting")),
+       OPT__VERBOSE(&verbose, N_("be verbose")),
+       OPT_GROUP(""),
+       OPT_BOOLEAN(0, "stdin", &stdin_paths,
+                   N_("read file names from stdin")),
+       OPT_BOOLEAN('z', NULL, &null_term_line,
+                   N_("input paths are terminated by a null character")),
+       OPT_END()
+};
+
+static void output_exclude(const char *path, struct exclude *exclude)
+{
+       char *bang  = exclude->flags & EXC_FLAG_NEGATIVE  ? "!" : "";
+       char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
+       if (!null_term_line) {
+               if (!verbose) {
+                       write_name_quoted(path, stdout, '\n');
+               } else {
+                       quote_c_style(exclude->el->src, NULL, stdout, 0);
+                       printf(":%d:%s%s%s\t",
+                              exclude->srcpos,
+                              bang, exclude->pattern, slash);
+                       quote_c_style(path, NULL, stdout, 0);
+                       fputc('\n', stdout);
+               }
+       } else {
+               if (!verbose) {
+                       printf("%s%c", path, '\0');
+               } else {
+                       printf("%s%c%d%c%s%s%s%c%s%c",
+                              exclude->el->src, '\0',
+                              exclude->srcpos, '\0',
+                              bang, exclude->pattern, slash, '\0',
+                              path, '\0');
+               }
+       }
+}
+
+static int check_ignore(const char *prefix, const char **pathspec)
+{
+       struct dir_struct dir;
+       const char *path, *full_path;
+       char *seen;
+       int num_ignored = 0, dtype = DT_UNKNOWN, i;
+       struct path_exclude_check check;
+       struct exclude *exclude;
+
+       /* read_cache() is only necessary so we can watch out for submodules. */
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
+
+       memset(&dir, 0, sizeof(dir));
+       dir.flags |= DIR_COLLECT_IGNORED;
+       setup_standard_excludes(&dir);
+
+       if (!pathspec || !*pathspec) {
+               if (!quiet)
+                       fprintf(stderr, "no pathspec given.\n");
+               return 0;
+       }
+
+       path_exclude_check_init(&check, &dir);
+       /*
+        * look for pathspecs matching entries in the index, since these
+        * should not be ignored, in order to be consistent with
+        * 'git status', 'git add' etc.
+        */
+       seen = find_pathspecs_matching_against_index(pathspec);
+       for (i = 0; pathspec[i]; i++) {
+               path = pathspec[i];
+               full_path = prefix_path(prefix, prefix
+                                       ? strlen(prefix) : 0, path);
+               full_path = check_path_for_gitlink(full_path);
+               die_if_path_beyond_symlink(full_path, prefix);
+               if (!seen[i] && path[0]) {
+                       exclude = last_exclude_matching_path(&check, full_path,
+                                                            -1, &dtype);
+                       if (exclude) {
+                               if (!quiet)
+                                       output_exclude(path, exclude);
+                               num_ignored++;
+                       }
+               }
+       }
+       free(seen);
+       clear_directory(&dir);
+       path_exclude_check_clear(&check);
+
+       return num_ignored;
+}
+
+static int check_ignore_stdin_paths(const char *prefix)
+{
+       struct strbuf buf, nbuf;
+       char **pathspec = NULL;
+       size_t nr = 0, alloc = 0;
+       int line_termination = null_term_line ? 0 : '\n';
+       int num_ignored;
+
+       strbuf_init(&buf, 0);
+       strbuf_init(&nbuf, 0);
+       while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+               if (line_termination && buf.buf[0] == '"') {
+                       strbuf_reset(&nbuf);
+                       if (unquote_c_style(&nbuf, buf.buf, NULL))
+                               die("line is badly quoted");
+                       strbuf_swap(&buf, &nbuf);
+               }
+               ALLOC_GROW(pathspec, nr + 1, alloc);
+               pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf));
+               strcpy(pathspec[nr++], buf.buf);
+       }
+       ALLOC_GROW(pathspec, nr + 1, alloc);
+       pathspec[nr] = NULL;
+       num_ignored = check_ignore(prefix, (const char **)pathspec);
+       maybe_flush_or_die(stdout, "attribute to stdout");
+       strbuf_release(&buf);
+       strbuf_release(&nbuf);
+       free(pathspec);
+       return num_ignored;
+}
+
+int cmd_check_ignore(int argc, const char **argv, const char *prefix)
+{
+       int num_ignored;
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, check_ignore_options,
+                            check_ignore_usage, 0);
+
+       if (stdin_paths) {
+               if (argc > 0)
+                       die(_("cannot specify pathnames with --stdin"));
+       } else {
+               if (null_term_line)
+                       die(_("-z only makes sense with --stdin"));
+               if (argc == 0)
+                       die(_("no path specified"));
+       }
+       if (quiet) {
+               if (argc > 1)
+                       die(_("--quiet is only valid with a single pathname"));
+               if (verbose)
+                       die(_("cannot have both --quiet and --verbose"));
+       }
+
+       if (stdin_paths) {
+               num_ignored = check_ignore_stdin_paths(prefix);
+       } else {
+               num_ignored = check_ignore(prefix, argv);
+               maybe_flush_or_die(stdout, "ignore to stdout");
+       }
+
+       return !num_ignored;
+}
index 69c1cda9061f248479377d2841a1ddf703ea59ba..04e396b17acc2443a663dd085ef812c3c2746d27 100644 (file)
@@ -10,6 +10,7 @@
 #include "cache.h"
 #include "dir.h"
 #include "parse-options.h"
+#include "refs.h"
 #include "string-list.h"
 #include "quote.h"
 
@@ -20,6 +21,12 @@ static const char *const builtin_clean_usage[] = {
        NULL
 };
 
+static const char *msg_remove = N_("Removing %s\n");
+static const char *msg_would_remove = N_("Would remove %s\n");
+static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
+static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
+static const char *msg_warn_remove_failed = N_("failed to remove %s");
+
 static int git_clean_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "clean.requireforce"))
@@ -34,22 +41,124 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
+               int dry_run, int quiet, int *dir_gone)
+{
+       DIR *dir;
+       struct strbuf quoted = STRBUF_INIT;
+       struct dirent *e;
+       int res = 0, ret = 0, gone = 1, original_len = path->len, len, i;
+       unsigned char submodule_head[20];
+       struct string_list dels = STRING_LIST_INIT_DUP;
+
+       *dir_gone = 1;
+
+       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
+                       !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
+               if (!quiet) {
+                       quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                       printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
+                                       quoted.buf);
+               }
+
+               *dir_gone = 0;
+               return 0;
+       }
+
+       dir = opendir(path->buf);
+       if (!dir) {
+               /* an empty dir could be removed even if it is unreadble */
+               res = dry_run ? 0 : rmdir(path->buf);
+               if (res) {
+                       quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                       warning(_(msg_warn_remove_failed), quoted.buf);
+                       *dir_gone = 0;
+               }
+               return res;
+       }
+
+       if (path->buf[original_len - 1] != '/')
+               strbuf_addch(path, '/');
+
+       len = path->len;
+       while ((e = readdir(dir)) != NULL) {
+               struct stat st;
+               if (is_dot_or_dotdot(e->d_name))
+                       continue;
+
+               strbuf_setlen(path, len);
+               strbuf_addstr(path, e->d_name);
+               if (lstat(path->buf, &st))
+                       ; /* fall thru */
+               else if (S_ISDIR(st.st_mode)) {
+                       if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
+                               ret = 1;
+                       if (gone) {
+                               quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                               string_list_append(&dels, quoted.buf);
+                       } else
+                               *dir_gone = 0;
+                       continue;
+               } else {
+                       res = dry_run ? 0 : unlink(path->buf);
+                       if (!res) {
+                               quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                               string_list_append(&dels, quoted.buf);
+                       } else {
+                               quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                               warning(_(msg_warn_remove_failed), quoted.buf);
+                               *dir_gone = 0;
+                               ret = 1;
+                       }
+                       continue;
+               }
+
+               /* path too long, stat fails, or non-directory still exists */
+               *dir_gone = 0;
+               ret = 1;
+               break;
+       }
+       closedir(dir);
+
+       strbuf_setlen(path, original_len);
+
+       if (*dir_gone) {
+               res = dry_run ? 0 : rmdir(path->buf);
+               if (!res)
+                       *dir_gone = 1;
+               else {
+                       quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                       warning(_(msg_warn_remove_failed), quoted.buf);
+                       *dir_gone = 0;
+                       ret = 1;
+               }
+       }
+
+       if (!*dir_gone && !quiet) {
+               for (i = 0; i < dels.nr; i++)
+                       printf(dry_run ?  _(msg_would_remove) : _(msg_remove), dels.items[i].string);
+       }
+       string_list_clear(&dels, 0);
+       return ret;
+}
+
 int cmd_clean(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
-       int ignored_only = 0, config_set = 0, errors = 0;
+       int i, res;
+       int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
+       int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
        struct strbuf directory = STRBUF_INIT;
        struct dir_struct dir;
        static const char **pathspec;
        struct strbuf buf = STRBUF_INIT;
        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
+       struct exclude_list *el;
        const char *qname;
        char *seen = NULL;
        struct option options[] = {
                OPT__QUIET(&quiet, N_("do not print names of files removed")),
-               OPT__DRY_RUN(&show_only, N_("dry run")),
+               OPT__DRY_RUN(&dry_run, N_("dry run")),
                OPT__FORCE(&force, N_("force")),
                OPT_BOOLEAN('d', NULL, &remove_directories,
                                N_("remove whole directories")),
@@ -77,7 +186,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        if (ignored && ignored_only)
                die(_("-x and -X cannot be used together"));
 
-       if (!show_only && !force) {
+       if (!dry_run && !force) {
                if (config_set)
                        die(_("clean.requireForce set to true and neither -n nor -f given; "
                                  "refusing to clean"));
@@ -97,9 +206,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        if (!ignored)
                setup_standard_excludes(&dir);
 
+       el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
        for (i = 0; i < exclude_list.nr; i++)
-               add_exclude(exclude_list.items[i].string, "", 0,
-                           &dir.exclude_list[EXC_CMDL]);
+               add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
 
        pathspec = get_pathspec(prefix, argv);
 
@@ -149,38 +258,26 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 
                if (S_ISDIR(st.st_mode)) {
                        strbuf_addstr(&directory, ent->name);
-                       qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
-                       if (show_only && (remove_directories ||
-                           (matches == MATCHED_EXACTLY))) {
-                               printf(_("Would remove %s\n"), qname);
-                       } else if (remove_directories ||
-                                  (matches == MATCHED_EXACTLY)) {
-                               if (!quiet)
-                                       printf(_("Removing %s\n"), qname);
-                               if (remove_dir_recursively(&directory,
-                                                          rm_flags) != 0) {
-                                       warning(_("failed to remove %s"), qname);
+                       if (remove_directories || (matches == MATCHED_EXACTLY)) {
+                               if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))
                                        errors++;
+                               if (gone && !quiet) {
+                                       qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
+                                       printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
                                }
-                       } else if (show_only) {
-                               printf(_("Would not remove %s\n"), qname);
-                       } else {
-                               printf(_("Not removing %s\n"), qname);
                        }
                        strbuf_reset(&directory);
                } else {
                        if (pathspec && !matches)
                                continue;
-                       qname = quote_path_relative(ent->name, -1, &buf, prefix);
-                       if (show_only) {
-                               printf(_("Would remove %s\n"), qname);
-                               continue;
-                       } else if (!quiet) {
-                               printf(_("Removing %s\n"), qname);
-                       }
-                       if (unlink(ent->name) != 0) {
-                               warning(_("failed to remove %s"), qname);
+                       res = dry_run ? 0 : unlink(ent->name);
+                       if (res) {
+                               qname = quote_path_relative(ent->name, -1, &buf, prefix);
+                               warning(_(msg_warn_remove_failed), qname);
                                errors++;
+                       } else if (!quiet) {
+                               qname = quote_path_relative(ent->name, -1, &buf, prefix);
+                               printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
                        }
                }
        }
index 8d23a62e8a1b875f1415f78a4c283f4cdefac011..e0aaf13583376c7adf49f504cc9e7e1303fb4a4d 100644 (file)
@@ -704,6 +704,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (option_origin)
                        die(_("--bare and --origin %s options are incompatible."),
                            option_origin);
+               if (real_git_dir)
+                       die(_("--bare and --separate-git-dir are incompatible."));
                option_no_checkout = 1;
        }
 
@@ -765,8 +767,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        atexit(remove_junk);
        sigchain_push_common(remove_junk_on_signal);
 
-       setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
-
        if (safe_create_leading_directories_const(git_dir) < 0)
                die(_("could not create leading directories of '%s'"), git_dir);
 
@@ -785,13 +785,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        init_db(option_template, INIT_DB_QUIET);
        write_config(&option_config);
 
-       /*
-        * At this point, the config exists, so we do not need the
-        * environment variable.  We actually need to unset it, too, to
-        * re-enable parsing of the global configs.
-        */
-       unsetenv(CONFIG_ENVIRONMENT);
-
        git_config(git_default_config, NULL);
 
        if (option_bare) {
index 7c2a3d48b4ac6bf6f55539946adacb433b51dabb..1a0e5f14a36975a012199f9e7a96d9a58ee986d0 100644 (file)
@@ -733,15 +733,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                if (cleanup_mode == CLEANUP_ALL)
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
-                               " Lines starting\nwith '#' will be ignored, and an empty"
-                               " message aborts the commit.\n"));
+                                 " Lines starting\nwith '%c' will be ignored, and an empty"
+                                 " message aborts the commit.\n"), comment_line_char);
                else /* CLEANUP_SPACE, that is. */
                        status_printf(s, GIT_COLOR_NORMAL,
                                _("Please enter the commit message for your changes."
-                               " Lines starting\n"
-                               "with '#' will be kept; you may remove them"
-                               " yourself if you want to.\n"
-                               "An empty message aborts the commit.\n"));
+                                 " Lines starting\n"
+                                 "with '%c' will be kept; you may remove them"
+                                 " yourself if you want to.\n"
+                                 "An empty message aborts the commit.\n"), comment_line_char);
                if (only_include_assumed)
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                        "%s", only_include_assumed);
@@ -946,24 +946,14 @@ static void handle_untracked_files_arg(struct wt_status *s)
 
 static const char *read_commit_message(const char *name)
 {
-       const char *out_enc, *out;
+       const char *out_enc;
        struct commit *commit;
 
        commit = lookup_commit_reference_by_name(name);
        if (!commit)
                die(_("could not lookup commit %s"), name);
        out_enc = get_commit_output_encoding();
-       out = logmsg_reencode(commit, out_enc);
-
-       /*
-        * If we failed to reencode the buffer, just copy it
-        * byte for byte so the user can try to fix it up.
-        * This also handles the case where input and output
-        * encodings are identical.
-        */
-       if (out == NULL)
-               out = xstrdup(commit->buffer);
-       return out;
+       return logmsg_reencode(commit, out_enc);
 }
 
 static int parse_and_validate_options(int argc, const char *argv[],
@@ -1329,8 +1319,6 @@ static int git_commit_config(const char *k, const char *v, void *cb)
        return git_status_config(k, v, s);
 }
 
-static const char post_rewrite_hook[] = "hooks/post-rewrite";
-
 static int run_rewrite_hook(const unsigned char *oldsha1,
                            const unsigned char *newsha1)
 {
@@ -1341,10 +1329,10 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
        int code;
        size_t n;
 
-       if (access(git_path(post_rewrite_hook), X_OK) < 0)
+       argv[0] = find_hook("post-rewrite");
+       if (!argv[0])
                return 0;
 
-       argv[0] = git_path(post_rewrite_hook);
        argv[1] = "amend";
        argv[2] = NULL;
 
index 4b5a89839b66f201bda42f06cabdf781d44f0192..4b6b1dfe66952a7e3fb1516e7e14473febde3d2d 100644 (file)
@@ -32,7 +32,7 @@ enum {
 
 static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
 static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
-static int tags = TAGS_DEFAULT;
+static int tags = TAGS_DEFAULT, unshallow;
 static const char *depth;
 static const char *upload_pack;
 static struct strbuf default_rla = STRBUF_INIT;
@@ -82,6 +82,9 @@ static struct option builtin_fetch_options[] = {
        OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
        OPT_STRING(0, "depth", &depth, N_("depth"),
                   N_("deepen history of shallow clone")),
+       { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL,
+                  N_("convert to a complete repository"),
+                  PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 },
        { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"),
                   N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN },
        { OPTION_STRING, 0, "recurse-submodules-default",
@@ -959,6 +962,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        struct string_list list = STRING_LIST_INIT_NODUP;
        struct remote *remote;
        int result = 0;
+       static const char *argv_gc_auto[] = {
+               "gc", "--auto", NULL,
+       };
 
        packet_trace_identity("fetch");
 
@@ -970,6 +976,18 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
 
+       if (unshallow) {
+               if (depth)
+                       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;
+               }
+       }
+
        if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
                if (recurse_submodules_default) {
                        int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
@@ -1026,5 +1044,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        list.strdup_strings = 1;
        string_list_clear(&list, 0);
 
+       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+
        return result;
 }
index d9af43c257150c957bf3bc3c3b7a7e43e370174c..b49612f0ce02e0c8503bee77477800297e84065d 100644 (file)
@@ -470,7 +470,7 @@ static void fmt_tag_signature(struct strbuf *tagbuf,
        strbuf_complete_line(tagbuf);
        if (sig->len) {
                strbuf_addch(tagbuf, '\n');
-               strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+               strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
        }
 }
 
index 0e1b6c860e18c6c47f343fad885cbd24f641862e..8025964987553b8f1ef21b8319b2085d6d31bb0c 100644 (file)
@@ -823,6 +823,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        struct object *object = parse_object(sha1);
                        if (!object)
                                die(_("bad object %s"), arg);
+                       if (!seen_dashdash)
+                               verify_non_filename(prefix, arg);
                        add_object_array(object, arg, &list);
                        continue;
                }
index bd86253d835fce671ed19676e5efeb59a6fc261d..d1d71816a9df67721578bc29665c15887575caec 100644 (file)
@@ -6,7 +6,6 @@
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
-#include "common-cmds.h"
 #include "parse-options.h"
 #include "run-command.h"
 #include "column.h"
@@ -237,21 +236,21 @@ static int add_man_viewer_cmd(const char *name,
 
 static int add_man_viewer_info(const char *var, const char *value)
 {
-       const char *name = var + 4;
-       const char *subkey = strrchr(name, '.');
+       const char *name, *subkey;
+       int namelen;
 
-       if (!subkey)
+       if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
                return 0;
 
-       if (!strcmp(subkey, ".path")) {
+       if (!strcmp(subkey, "path")) {
                if (!value)
                        return config_error_nonbool(var);
-               return add_man_viewer_path(name, subkey - name, value);
+               return add_man_viewer_path(name, namelen, value);
        }
-       if (!strcmp(subkey, ".cmd")) {
+       if (!strcmp(subkey, "cmd")) {
                if (!value)
                        return config_error_nonbool(var);
-               return add_man_viewer_cmd(name, subkey - name, value);
+               return add_man_viewer_cmd(name, namelen, value);
        }
 
        return 0;
@@ -287,23 +286,6 @@ static int git_help_config(const char *var, const char *value, void *cb)
 
 static struct cmdnames main_cmds, other_cmds;
 
-void list_common_cmds_help(void)
-{
-       int i, longest = 0;
-
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               if (longest < strlen(common_cmds[i].name))
-                       longest = strlen(common_cmds[i].name);
-       }
-
-       puts(_("The most commonly used git commands are:"));
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               printf("   %s   ", common_cmds[i].name);
-               mput_char(' ', longest - strlen(common_cmds[i].name));
-               puts(_(common_cmds[i].help));
-       }
-}
-
 static int is_git_command(const char *s)
 {
        return is_in_cmdlist(&main_cmds, s) ||
index 373c573449b5771f9b56d7d4ce04e4c0b3957cd9..175e6e3e72003387bfa4f78dbd0ed10c02c4e318 100644 (file)
@@ -35,6 +35,7 @@ static int error_unmatch;
 static char *ps_matched;
 static const char *with_tree;
 static int exc_given;
+static int exclude_args;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -420,10 +421,10 @@ static int option_parse_z(const struct option *opt,
 static int option_parse_exclude(const struct option *opt,
                                const char *arg, int unset)
 {
-       struct exclude_list *list = opt->value;
+       struct string_list *exclude_list = opt->value;
 
        exc_given = 1;
-       add_exclude(arg, "", 0, list);
+       string_list_append(exclude_list, arg);
 
        return 0;
 }
@@ -452,9 +453,11 @@ static int option_parse_exclude_standard(const struct option *opt,
 
 int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 {
-       int require_work_tree = 0, show_tag = 0;
+       int require_work_tree = 0, show_tag = 0, i;
        const char *max_prefix;
        struct dir_struct dir;
+       struct exclude_list *el;
+       struct string_list exclude_list = STRING_LIST_INIT_NODUP;
        struct option builtin_ls_files_options[] = {
                { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
                        N_("paths are separated with NUL character"),
@@ -488,7 +491,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                        N_("show unmerged files in the output")),
                OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
                            N_("show resolve-undo information")),
-               { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], N_("pattern"),
+               { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
                        N_("skip files matching pattern"),
                        0, option_parse_exclude },
                { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"),
@@ -525,6 +528,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 
        argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
                        ls_files_usage, 0);
+       el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
+       for (i = 0; i < exclude_list.nr; i++) {
+               add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
+       }
        if (show_tag || show_valid_bit) {
                tag_cached = "H ";
                tag_unmerged = "M ";
index 9307e9c726587461d002530938dbf2c0d74d55a1..7c8922c8b0b44307a0dbb43329301ad7d1654a46 100644 (file)
@@ -788,17 +788,16 @@ static const char merge_editor_comment[] =
 N_("Please enter a commit message to explain why this merge is necessary,\n"
    "especially if it merges an updated upstream into a topic branch.\n"
    "\n"
-   "Lines starting with '#' will be ignored, and an empty message aborts\n"
+   "Lines starting with '%c' will be ignored, and an empty message aborts\n"
    "the commit.\n");
 
 static void prepare_to_commit(struct commit_list *remoteheads)
 {
        struct strbuf msg = STRBUF_INIT;
-       const char *comment = _(merge_editor_comment);
        strbuf_addbuf(&msg, &merge_msg);
        strbuf_addch(&msg, '\n');
        if (0 < option_edit)
-               strbuf_add_lines(&msg, "# ", comment, strlen(comment));
+               strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
        write_merge_msg(&msg);
        if (run_hook(get_index_file(), "prepare-commit-msg",
                     git_path("MERGE_MSG"), "merge", NULL, NULL))
index 453457adb9dedac510c5a95e0e07cb0b4b46c80e..57748a6fb67820c15ee6a8a92b32d4a0392dd933 100644 (file)
@@ -92,10 +92,7 @@ static const char * const git_notes_get_ref_usage[] = {
 };
 
 static const char note_template[] =
-       "\n"
-       "#\n"
-       "# Write/edit the notes for the following object:\n"
-       "#\n";
+       "\nWrite/edit the notes for the following object:\n";
 
 struct msg_arg {
        int given;
@@ -129,7 +126,7 @@ static void write_commented_object(int fd, const unsigned char *object)
                {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL};
        struct child_process show;
        struct strbuf buf = STRBUF_INIT;
-       FILE *show_out;
+       struct strbuf cbuf = STRBUF_INIT;
 
        /* Invoke "git show --stat --no-notes $object" */
        memset(&show, 0, sizeof(show));
@@ -142,21 +139,14 @@ static void write_commented_object(int fd, const unsigned char *object)
                die(_("unable to start 'show' for object '%s'"),
                    sha1_to_hex(object));
 
-       /* Open the output as FILE* so strbuf_getline() can be used. */
-       show_out = xfdopen(show.out, "r");
-       if (show_out == NULL)
-               die_errno(_("can't fdopen 'show' output fd"));
+       if (strbuf_read(&buf, show.out, 0) < 0)
+               die_errno(_("could not read 'show' output"));
+       strbuf_add_commented_lines(&cbuf, buf.buf, buf.len);
+       write_or_die(fd, cbuf.buf, cbuf.len);
 
-       /* Prepend "# " to each output line and write result to 'fd' */
-       while (strbuf_getline(&buf, show_out, '\n') != EOF) {
-               write_or_die(fd, "# ", 2);
-               write_or_die(fd, buf.buf, buf.len);
-               write_or_die(fd, "\n", 1);
-       }
+       strbuf_release(&cbuf);
        strbuf_release(&buf);
-       if (fclose(show_out))
-               die_errno(_("failed to close pipe to 'show' for object '%s'"),
-                         sha1_to_hex(object));
+
        if (finish_command(&show))
                die(_("failed to finish 'show' for object '%s'"),
                    sha1_to_hex(object));
@@ -170,6 +160,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
 
        if (msg->use_editor || !msg->given) {
                int fd;
+               struct strbuf buf = STRBUF_INIT;
 
                /* write the template message before editing: */
                path = git_pathdup("NOTES_EDITMSG");
@@ -181,11 +172,16 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
                        write_or_die(fd, msg->buf.buf, msg->buf.len);
                else if (prev && !append_only)
                        write_note_data(fd, prev);
-               write_or_die(fd, note_template, strlen(note_template));
+
+               strbuf_addch(&buf, '\n');
+               strbuf_add_commented_lines(&buf, note_template, strlen(note_template));
+               strbuf_addch(&buf, '\n');
+               write_or_die(fd, buf.buf, buf.len);
 
                write_commented_object(fd, object);
 
                close(fd);
+               strbuf_release(&buf);
                strbuf_reset(&(msg->buf));
 
                if (launch_editor(path, &(msg->buf), NULL)) {
index 8491e431e41aaff32c76ab0c61f528682b3babdb..42b129d36cf615ed264be0f1bff523a7fc327b12 100644 (file)
@@ -220,9 +220,20 @@ static const char message_advice_checkout_pull_push[] =
           "(e.g. 'git pull') before pushing again.\n"
           "See the 'Note about fast-forwards' in 'git push --help' for details.");
 
+static const char message_advice_ref_fetch_first[] =
+       N_("Updates were rejected because the remote contains work that you do\n"
+          "not have locally. This is usually caused by another repository pushing\n"
+          "to the same ref. You may want to first merge the remote changes (e.g.,\n"
+          "'git pull') before pushing again.\n"
+          "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
 static const char message_advice_ref_already_exists[] =
-       N_("Updates were rejected because the destination reference already exists\n"
-          "in the remote.");
+       N_("Updates were rejected because the tag already exists in the remote.");
+
+static const char message_advice_ref_needs_force[] =
+       N_("You cannot update a remote ref that points at a non-commit object,\n"
+          "or update a remote ref to make it point at a non-commit object,\n"
+          "without using the '--force' option.\n");
 
 static void advise_pull_before_push(void)
 {
@@ -252,6 +263,20 @@ static void advise_ref_already_exists(void)
        advise(_(message_advice_ref_already_exists));
 }
 
+static void advise_ref_fetch_first(void)
+{
+       if (!advice_push_fetch_first || !advice_push_update_rejected)
+               return;
+       advise(_(message_advice_ref_fetch_first));
+}
+
+static void advise_ref_needs_force(void)
+{
+       if (!advice_push_needs_force || !advice_push_update_rejected)
+               return;
+       advise(_(message_advice_ref_needs_force));
+}
+
 static int push_with_options(struct transport *transport, int flags)
 {
        int err;
@@ -285,6 +310,10 @@ static int push_with_options(struct transport *transport, int flags)
                        advise_checkout_pull_push();
        } else if (reject_reasons & REJECT_ALREADY_EXISTS) {
                advise_ref_already_exists();
+       } else if (reject_reasons & REJECT_FETCH_FIRST) {
+               advise_ref_fetch_first();
+       } else if (reject_reasons & REJECT_NEEDS_FORCE) {
+               advise_ref_needs_force();
        }
 
        return 1;
@@ -407,6 +436,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
                OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
                        TRANSPORT_PUSH_PRUNE),
+               OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
                OPT_END()
        };
 
index ff781febcad92dfccd8a3d97d0414526ded36943..e8878de45c9474cddd5c713cd7a712bed15a515a 100644 (file)
@@ -182,9 +182,6 @@ struct command {
        char ref_name[FLEX_ARRAY]; /* more */
 };
 
-static const char pre_receive_hook[] = "hooks/pre-receive";
-static const char post_receive_hook[] = "hooks/post-receive";
-
 static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
@@ -242,10 +239,10 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
        const char *argv[2];
        int code;
 
-       if (access(hook_name, X_OK) < 0)
+       argv[0] = find_hook(hook_name);
+       if (!argv[0])
                return 0;
 
-       argv[0] = hook_name;
        argv[1] = NULL;
 
        memset(&proc, 0, sizeof(proc));
@@ -331,15 +328,14 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
 
 static int run_update_hook(struct command *cmd)
 {
-       static const char update_hook[] = "hooks/update";
        const char *argv[5];
        struct child_process proc;
        int code;
 
-       if (access(update_hook, X_OK) < 0)
+       argv[0] = find_hook("update");
+       if (!argv[0])
                return 0;
 
-       argv[0] = update_hook;
        argv[1] = cmd->ref_name;
        argv[2] = sha1_to_hex(cmd->old_sha1);
        argv[3] = sha1_to_hex(cmd->new_sha1);
@@ -532,24 +528,25 @@ static const char *update(struct command *cmd)
        }
 }
 
-static char update_post_hook[] = "hooks/post-update";
-
 static void run_update_post_hook(struct command *commands)
 {
        struct command *cmd;
        int argc;
        const char **argv;
        struct child_process proc;
+       char *hook;
 
+       hook = find_hook("post-update");
        for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
                if (cmd->error_string || cmd->did_not_exist)
                        continue;
                argc++;
        }
-       if (!argc || access(update_post_hook, X_OK) < 0)
+       if (!argc || !hook)
                return;
+
        argv = xmalloc(sizeof(*argv) * (2 + argc));
-       argv[0] = update_post_hook;
+       argv[0] = hook;
 
        for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
                char *p;
@@ -704,7 +701,7 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
                                       0, &cmd))
                set_connectivity_errors(commands);
 
-       if (run_receive_hook(commands, pre_receive_hook, 0)) {
+       if (run_receive_hook(commands, "pre-receive", 0)) {
                for (cmd = commands; cmd; cmd = cmd->next) {
                        if (!cmd->error_string)
                                cmd->error_string = "pre-receive hook declined";
@@ -994,7 +991,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        unlink_or_warn(pack_lockfile);
                if (report_status)
                        report(commands, unpack_status);
-               run_receive_hook(commands, post_receive_hook, 1);
+               run_receive_hook(commands, "post-receive", 1);
                run_update_post_hook(commands);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {
index b3c9e27bde653bf01acc6126deeb5f508fa0b26e..1fedf66329d73f192d80d3d42789c290a839d7b0 100644 (file)
@@ -510,26 +510,27 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l
 
 static int reflog_expire_config(const char *var, const char *value, void *cb)
 {
-       const char *lastdot = strrchr(var, '.');
+       const char *pattern, *key;
+       int pattern_len;
        unsigned long expire;
        int slot;
        struct reflog_expire_cfg *ent;
 
-       if (!lastdot || prefixcmp(var, "gc."))
+       if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
                return git_default_config(var, value, cb);
 
-       if (!strcmp(lastdot, ".reflogexpire")) {
+       if (!strcmp(key, "reflogexpire")) {
                slot = EXPIRE_TOTAL;
                if (parse_expire_cfg_value(var, value, &expire))
                        return -1;
-       } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
+       } else if (!strcmp(key, "reflogexpireunreachable")) {
                slot = EXPIRE_UNREACH;
                if (parse_expire_cfg_value(var, value, &expire))
                        return -1;
        } else
                return git_default_config(var, value, cb);
 
-       if (lastdot == var + 2) {
+       if (!pattern) {
                switch (slot) {
                case EXPIRE_TOTAL:
                        default_reflog_expire = expire;
@@ -541,7 +542,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       ent = find_cfg_ent(var + 3, lastdot - (var+3));
+       ent = find_cfg_ent(pattern, pattern_len);
        if (!ent)
                return -1;
        switch (slot) {
index 915cc9f86f855d517abaa55d31c502899fb77220..6032131a9087281e97d0abc05365b7485004121b 100644 (file)
@@ -23,8 +23,8 @@
 
 static const char * const git_reset_usage[] = {
        N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
-       N_("git reset [-q] <commit> [--] <paths>..."),
-       N_("git reset --patch [<commit>] [--] [<paths>...]"),
+       N_("git reset [-q] <tree-ish> [--] <paths>..."),
+       N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
        NULL
 };
 
@@ -38,14 +38,12 @@ static inline int is_merge(void)
        return !access(git_path("MERGE_HEAD"), F_OK);
 }
 
-static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
+static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
 {
        int nr = 1;
-       int newfd;
        struct tree_desc desc[2];
        struct tree *tree;
        struct unpack_trees_options opts;
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = 1;
@@ -67,8 +65,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
                opts.reset = 1;
        }
 
-       newfd = hold_locked_index(lock, 1);
-
        read_cache_unmerged();
 
        if (reset_type == KEEP) {
@@ -91,10 +87,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
                prime_cache_tree(&active_cache_tree, tree);
        }
 
-       if (write_cache(newfd, active_cache, active_nr) ||
-           commit_locked_index(lock))
-               return error(_("Could not write new index file."));
-
        return 0;
 }
 
@@ -117,36 +109,10 @@ static void print_new_head_line(struct commit *commit)
                printf("\n");
 }
 
-static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
-{
-       int result;
-
-       if (!index_lock) {
-               index_lock = xcalloc(1, sizeof(struct lock_file));
-               fd = hold_locked_index(index_lock, 1);
-       }
-
-       if (read_cache() < 0)
-               return error(_("Could not read index"));
-
-       result = refresh_index(&the_index, (flags), NULL, NULL,
-                              _("Unstaged changes after reset:")) ? 1 : 0;
-       if (write_cache(fd, active_cache, active_nr) ||
-                       commit_locked_index(index_lock))
-               return error ("Could not refresh index");
-       return result;
-}
-
 static void update_index_from_diff(struct diff_queue_struct *q,
                struct diff_options *opt, void *data)
 {
        int i;
-       int *discard_flag = data;
-
-       /* do_diff_cache() mangled the index */
-       discard_cache();
-       *discard_flag = 1;
-       read_cache();
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filespec *one = q->queue[i]->one;
@@ -164,32 +130,15 @@ static void update_index_from_diff(struct diff_queue_struct *q,
        }
 }
 
-static int interactive_reset(const char *revision, const char **argv,
-                            const char *prefix)
+static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
 {
-       const char **pathspec = NULL;
-
-       if (*argv)
-               pathspec = get_pathspec(prefix, argv);
-
-       return run_add_interactive(revision, "--patch=reset", pathspec);
-}
-
-static int read_from_tree(const char *prefix, const char **argv,
-               unsigned char *tree_sha1, int refresh_flags)
-{
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int index_fd, index_was_discarded = 0;
        struct diff_options opt;
 
        memset(&opt, 0, sizeof(opt));
-       diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+       diff_tree_setup_paths(pathspec, &opt);
        opt.output_format = DIFF_FORMAT_CALLBACK;
        opt.format_callback = update_index_from_diff;
-       opt.format_callback_data = &index_was_discarded;
 
-       index_fd = hold_locked_index(lock, 1);
-       index_was_discarded = 0;
        read_cache();
        if (do_diff_cache(tree_sha1, &opt))
                return 1;
@@ -197,10 +146,7 @@ static int read_from_tree(const char *prefix, const char **argv,
        diff_flush(&opt);
        diff_tree_release_paths(&opt);
 
-       if (!index_was_discarded)
-               /* The index is still clobbered from do_diff_cache() */
-               discard_cache();
-       return update_index_refresh(index_fd, lock, refresh_flags);
+       return 0;
 }
 
 static void set_reflog_message(struct strbuf *sb, const char *action,
@@ -225,15 +171,79 @@ static void die_if_unmerged_cache(int reset_type)
 
 }
 
-int cmd_reset(int argc, const char **argv, const char *prefix)
+static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
 {
-       int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
-       int patch_mode = 0;
        const char *rev = "HEAD";
-       unsigned char sha1[20], *orig = NULL, sha1_orig[20],
-                               *old_orig = NULL, sha1_old_orig[20];
-       struct commit *commit;
+       unsigned char unused[20];
+       /*
+        * Possible arguments are:
+        *
+        * git reset [-opts] [<rev>]
+        * git reset [-opts] <tree> [<paths>...]
+        * git reset [-opts] <tree> -- [<paths>...]
+        * git reset [-opts] -- [<paths>...]
+        * git reset [-opts] <paths>...
+        *
+        * At this point, argv points immediately after [-opts].
+        */
+
+       if (argv[0]) {
+               if (!strcmp(argv[0], "--")) {
+                       argv++; /* reset to HEAD, possibly with paths */
+               } else if (argv[1] && !strcmp(argv[1], "--")) {
+                       rev = argv[0];
+                       argv += 2;
+               }
+               /*
+                * Otherwise, argv[0] could be either <rev> or <paths> and
+                * has to be unambiguous. If there is a single argument, it
+                * can not be a tree
+                */
+               else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) ||
+                        (argv[1] && !get_sha1_treeish(argv[0], unused))) {
+                       /*
+                        * Ok, argv[0] looks like a commit/tree; it should not
+                        * be a filename.
+                        */
+                       verify_non_filename(prefix, argv[0]);
+                       rev = *argv++;
+               } else {
+                       /* Otherwise we treat this as a filename */
+                       verify_filename(prefix, argv[0], 1);
+               }
+       }
+       *rev_ret = rev;
+       return argv[0] ? get_pathspec(prefix, argv) : NULL;
+}
+
+static int update_refs(const char *rev, const unsigned char *sha1)
+{
+       int update_ref_status;
        struct strbuf msg = STRBUF_INIT;
+       unsigned char *orig = NULL, sha1_orig[20],
+               *old_orig = NULL, sha1_old_orig[20];
+
+       if (!get_sha1("ORIG_HEAD", sha1_old_orig))
+               old_orig = sha1_old_orig;
+       if (!get_sha1("HEAD", sha1_orig)) {
+               orig = sha1_orig;
+               set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
+               update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+       } else if (old_orig)
+               delete_ref("ORIG_HEAD", old_orig, 0);
+       set_reflog_message(&msg, "updating HEAD", rev);
+       update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+       strbuf_release(&msg);
+       return update_ref_status;
+}
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+       int reset_type = NONE, update_ref_status = 0, quiet = 0;
+       int patch_mode = 0, unborn;
+       const char *rev;
+       unsigned char sha1[20];
+       const char **pathspec = NULL;
        const struct option options[] = {
                OPT__QUIET(&quiet, N_("be quiet, only report errors")),
                OPT_SET_INT(0, "mixed", &reset_type,
@@ -253,73 +263,45 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
-
-       /*
-        * Possible arguments are:
-        *
-        * git reset [-opts] <rev> <paths>...
-        * git reset [-opts] <rev> -- <paths>...
-        * git reset [-opts] -- <paths>...
-        * git reset [-opts] <paths>...
-        *
-        * At this point, argv[i] points immediately after [-opts].
-        */
-
-       if (i < argc) {
-               if (!strcmp(argv[i], "--")) {
-                       i++; /* reset to HEAD, possibly with paths */
-               } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
-                       rev = argv[i];
-                       i += 2;
-               }
-               /*
-                * Otherwise, argv[i] could be either <rev> or <paths> and
-                * has to be unambiguous.
-                */
-               else if (!get_sha1_committish(argv[i], sha1)) {
-                       /*
-                        * Ok, argv[i] looks like a rev; it should not
-                        * be a filename.
-                        */
-                       verify_non_filename(prefix, argv[i]);
-                       rev = argv[i++];
-               } else {
-                       /* Otherwise we treat this as a filename */
-                       verify_filename(prefix, argv[i], 1);
-               }
+       pathspec = parse_args(argv, prefix, &rev);
+
+       unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
+       if (unborn) {
+               /* reset on unborn branch: treat as reset to empty tree */
+               hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
+       } else if (!pathspec) {
+               struct commit *commit;
+               if (get_sha1_committish(rev, sha1))
+                       die(_("Failed to resolve '%s' as a valid revision."), rev);
+               commit = lookup_commit_reference(sha1);
+               if (!commit)
+                       die(_("Could not parse object '%s'."), rev);
+               hashcpy(sha1, commit->object.sha1);
+       } else {
+               struct tree *tree;
+               if (get_sha1_treeish(rev, sha1))
+                       die(_("Failed to resolve '%s' as a valid tree."), rev);
+               tree = parse_tree_indirect(sha1);
+               if (!tree)
+                       die(_("Could not parse object '%s'."), rev);
+               hashcpy(sha1, tree->object.sha1);
        }
 
-       if (get_sha1_committish(rev, sha1))
-               die(_("Failed to resolve '%s' as a valid ref."), rev);
-
-       /*
-        * NOTE: As "git reset $treeish -- $path" should be usable on
-        * any tree-ish, this is not strictly correct. We are not
-        * moving the HEAD to any commit; we are merely resetting the
-        * entries in the index to that of a treeish.
-        */
-       commit = lookup_commit_reference(sha1);
-       if (!commit)
-               die(_("Could not parse object '%s'."), rev);
-       hashcpy(sha1, commit->object.sha1);
-
        if (patch_mode) {
                if (reset_type != NONE)
                        die(_("--patch is incompatible with --{hard,mixed,soft}"));
-               return interactive_reset(rev, argv + i, prefix);
+               return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
        }
 
        /* git reset tree [--] paths... can be used to
         * load chosen paths from the tree into the index without
         * affecting the working tree nor HEAD. */
-       if (i < argc) {
+       if (pathspec) {
                if (reset_type == MIXED)
                        warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
                else if (reset_type != NONE)
                        die(_("Cannot do %s reset with paths."),
                                        _(reset_type_names[reset_type]));
-               return read_from_tree(prefix, argv + i, sha1,
-                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
        }
        if (reset_type == NONE)
                reset_type = MIXED; /* by default */
@@ -334,49 +316,44 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        /* Soft reset does not touch the index file nor the working tree
         * at all, but requires them in a good order.  Other resets reset
         * the index file to the tree object we are switching to. */
-       if (reset_type == SOFT)
+       if (reset_type == SOFT || reset_type == KEEP)
                die_if_unmerged_cache(reset_type);
-       else {
-               int err;
-               if (reset_type == KEEP)
-                       die_if_unmerged_cache(reset_type);
-               err = reset_index_file(sha1, reset_type, quiet);
-               if (reset_type == KEEP)
-                       err = err || reset_index_file(sha1, MIXED, quiet);
-               if (err)
-                       die(_("Could not reset index file to revision '%s'."), rev);
-       }
 
-       /* Any resets update HEAD to the head being switched to,
-        * saving the previous head in ORIG_HEAD before. */
-       if (!get_sha1("ORIG_HEAD", sha1_old_orig))
-               old_orig = sha1_old_orig;
-       if (!get_sha1("HEAD", sha1_orig)) {
-               orig = sha1_orig;
-               set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
-               update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
-       }
-       else if (old_orig)
-               delete_ref("ORIG_HEAD", old_orig, 0);
-       set_reflog_message(&msg, "updating HEAD", rev);
-       update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+       if (reset_type != SOFT) {
+               struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+               int newfd = hold_locked_index(lock, 1);
+               if (reset_type == MIXED) {
+                       if (read_from_tree(pathspec, sha1))
+                               return 1;
+               } else {
+                       int err = reset_index(sha1, reset_type, quiet);
+                       if (reset_type == KEEP && !err)
+                               err = reset_index(sha1, MIXED, quiet);
+                       if (err)
+                               die(_("Could not reset index file to revision '%s'."), rev);
+               }
 
-       switch (reset_type) {
-       case HARD:
-               if (!update_ref_status && !quiet)
-                       print_new_head_line(commit);
-               break;
-       case SOFT: /* Nothing else to do. */
-               break;
-       case MIXED: /* Report what has not been updated. */
-               update_index_refresh(0, NULL,
-                               quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
-               break;
+               if (reset_type == MIXED) { /* Report what has not been updated. */
+                       int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
+                       refresh_index(&the_index, flags, NULL, NULL,
+                                     _("Unstaged changes after reset:"));
+               }
+
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_locked_index(lock))
+                       die(_("Could not write new index file."));
        }
 
-       remove_branch_state();
+       if (!pathspec && !unborn) {
+               /* Any resets without paths update HEAD to the head being
+                * switched to, saving the previous head in ORIG_HEAD before. */
+               update_ref_status = update_refs(rev, sha1);
 
-       strbuf_release(&msg);
+               if (reset_type == HARD && !update_ref_status && !quiet)
+                       print_new_head_line(lookup_commit_reference(sha1));
+       }
+       if (!pathspec)
+               remove_branch_state();
 
        return update_ref_status;
 }
index f849e0a4a041610dc258a9dc56cc8a357cff31b6..57a46b2654aa82b154d32eb77addd6a4f89c8e8d 100644 (file)
@@ -44,6 +44,16 @@ static void print_helper_status(struct ref *ref)
                        msg = "non-fast forward";
                        break;
 
+               case REF_STATUS_REJECT_FETCH_FIRST:
+                       res = "error";
+                       msg = "fetch first";
+                       break;
+
+               case REF_STATUS_REJECT_NEEDS_FORCE:
+                       res = "error";
+                       msg = "needs force";
+                       break;
+
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                        res = "error";
                        msg = "already exists";
index f16986c0ae811f6b998d20be39da1af6095fcfe8..e981dfb9f088cbbfdfb199700a6de17758bf1eca 100644 (file)
@@ -30,7 +30,8 @@ static size_t cleanup(char *line, size_t len)
  *
  * If last line does not have a newline at the end, one is added.
  *
- * Enable skip_comments to skip every line starting with "#".
+ * Enable skip_comments to skip every line starting with comment
+ * character.
  */
 void stripspace(struct strbuf *sb, int skip_comments)
 {
@@ -45,7 +46,7 @@ void stripspace(struct strbuf *sb, int skip_comments)
                eol = memchr(sb->buf + i, '\n', sb->len - i);
                len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
 
-               if (skip_comments && len && sb->buf[i] == '#') {
+               if (skip_comments && len && sb->buf[i] == comment_line_char) {
                        newlen = 0;
                        continue;
                }
@@ -66,21 +67,53 @@ void stripspace(struct strbuf *sb, int skip_comments)
        strbuf_setlen(sb, j);
 }
 
+static void comment_lines(struct strbuf *buf)
+{
+       char *msg;
+       size_t len;
+
+       msg = strbuf_detach(buf, &len);
+       strbuf_add_commented_lines(buf, msg, len);
+       free(msg);
+}
+
+static const char *usage_msg = "\n"
+"  git stripspace [-s | --strip-comments] < input\n"
+"  git stripspace [-c | --comment-lines] < input";
+
 int cmd_stripspace(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
        int strip_comments = 0;
+       enum { INVAL = 0, STRIP_SPACE = 1, COMMENT_LINES = 2 } mode = STRIP_SPACE;
+
+       if (argc == 2) {
+               if (!strcmp(argv[1], "-s") ||
+                       !strcmp(argv[1], "--strip-comments")) {
+                        strip_comments = 1;
+               } else if (!strcmp(argv[1], "-c") ||
+                                        !strcmp(argv[1], "--comment-lines")) {
+                        mode = COMMENT_LINES;
+               } else {
+                       mode = INVAL;
+               }
+       } else if (argc > 1) {
+               mode = INVAL;
+       }
+
+       if (mode == INVAL)
+               usage(usage_msg);
 
-       if (argc == 2 && (!strcmp(argv[1], "-s") ||
-                               !strcmp(argv[1], "--strip-comments")))
-               strip_comments = 1;
-       else if (argc > 1)
-               usage("git stripspace [-s | --strip-comments] < input");
+       if (strip_comments || mode == COMMENT_LINES)
+               git_config(git_default_config, NULL);
 
        if (strbuf_read(&buf, 0, 1024) < 0)
                die_errno("could not read the input");
 
-       stripspace(&buf, strip_comments);
+       if (mode == STRIP_SPACE)
+               stripspace(&buf, strip_comments);
+       else
+               comment_lines(&buf);
 
        write_or_die(1, buf.buf, buf.len);
        strbuf_release(&buf);
index 9c3e0673d5bd4e011c877c4d3a17975498265e22..f8266888cca97f492dc5b52a992b1dc7cab2909e 100644 (file)
@@ -246,19 +246,13 @@ static int do_sign(struct strbuf *buffer)
 }
 
 static const char tag_template[] =
-       N_("\n"
-       "#\n"
-       "# Write a tag message\n"
-       "# Lines starting with '#' will be ignored.\n"
-       "#\n");
+       N_("\nWrite a tag message\n"
+       "Lines starting with '%c' will be ignored.\n");
 
 static const char tag_template_nocleanup[] =
-       N_("\n"
-       "#\n"
-       "# Write a tag message\n"
-       "# Lines starting with '#' will be kept; you may remove them"
-       " yourself if you want to.\n"
-       "#\n");
+       N_("\nWrite a tag message\n"
+       "Lines starting with '%c' will be kept; you may remove them"
+       " yourself if you want to.\n");
 
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
@@ -346,14 +340,18 @@ static void create_tag(const unsigned char *object, const char *tag,
                if (fd < 0)
                        die_errno(_("could not create file '%s'"), path);
 
-               if (!is_null_sha1(prev))
+               if (!is_null_sha1(prev)) {
                        write_tag_body(fd, prev);
-               else if (opt->cleanup_mode == CLEANUP_ALL)
-                       write_or_die(fd, _(tag_template),
-                                       strlen(_(tag_template)));
-               else
-                       write_or_die(fd, _(tag_template_nocleanup),
-                                       strlen(_(tag_template_nocleanup)));
+               } else {
+                       struct strbuf buf = STRBUF_INIT;
+                       strbuf_addch(&buf, '\n');
+                       if (opt->cleanup_mode == CLEANUP_ALL)
+                               strbuf_commented_addf(&buf, _(tag_template), comment_line_char);
+                       else
+                               strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char);
+                       write_or_die(fd, buf.buf, buf.len);
+                       strbuf_release(&buf);
+               }
                close(fd);
 
                if (launch_editor(path, buf, NULL)) {
diff --git a/cache.h b/cache.h
index c257953fa798110e0f4be9258f4f88f2d7952bdb..e493563f4c07e6adcd00a1b2476926d69a4a67f8 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -536,6 +536,7 @@ extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
 extern int trust_ctime;
+extern int check_stat;
 extern int quote_path_fully;
 extern int has_symlinks;
 extern int minimum_abbrev, default_abbrev;
@@ -562,6 +563,12 @@ extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
 
+/*
+ * The character that begins a commented line in user-editable file
+ * that is subject to stripspace.
+ */
+extern char comment_line_char;
+
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@ -1008,11 +1015,8 @@ struct ref {
        char *symref;
        unsigned int
                force:1,
-               requires_force:1,
+               forced_update:1,
                merge:1,
-               nonfastforward:1,
-               not_forwardable:1,
-               update:1,
                deletion:1;
        enum {
                REF_STATUS_NONE = 0,
@@ -1020,6 +1024,8 @@ struct ref {
                REF_STATUS_REJECT_NONFASTFORWARD,
                REF_STATUS_REJECT_ALREADY_EXISTS,
                REF_STATUS_REJECT_NODELETE,
+               REF_STATUS_REJECT_FETCH_FIRST,
+               REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
@@ -1148,7 +1154,7 @@ extern int check_repository_format_version(const char *var, const char *value, v
 extern int git_env_bool(const char *, int);
 extern int git_config_system(void);
 extern int config_error_nonbool(const char *);
-#ifdef __GNUC__
+#if defined(__GNUC__) && ! defined(__clang__)
 #define config_error_nonbool(s) (config_error_nonbool(s), -1)
 #endif
 extern const char *get_log_output_encoding(void);
@@ -1164,6 +1170,21 @@ struct config_include_data {
 #define CONFIG_INCLUDE_INIT { 0 }
 extern int git_config_include(const char *name, const char *value, void *data);
 
+/*
+ * Match and parse a config key of the form:
+ *
+ *   section.(subsection.)?key
+ *
+ * (i.e., what gets handed to a config_fn_t). The caller provides the section;
+ * we return -1 if it does not match, 0 otherwise. The subsection and key
+ * out-parameters are filled by the function (and subsection is NULL if it is
+ * missing).
+ */
+extern int parse_config_key(const char *var,
+                           const char *section,
+                           const char **subsection, int *subsection_len,
+                           const char **key);
+
 extern int committer_ident_sufficiently_given(void);
 extern int author_ident_sufficiently_given(void);
 
index 7e8cfec29d9e00cd6dfb26bca1f1295f64ae53fd..bf83303c48dc5cfbccfc4a31b347dcdec197d7d4 100644 (file)
@@ -12,6 +12,7 @@ git-branch                              mainporcelain common
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
+git-check-ignore                        purehelpers
 git-checkout                            mainporcelain common
 git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
index c16c8a75349f2ef4dba1f3c05528d04750a925da..4138bb4c0850f95ba7215cb62f457cd682495968 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -101,6 +101,7 @@ extern int has_non_ascii(const char *text);
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 extern char *logmsg_reencode(const struct commit *commit,
                             const char *output_encoding);
+extern void logmsg_free(char *msg, const struct commit *commit);
 extern void get_commit_format(const char *arg, struct rev_info *);
 extern const char *format_subject(struct strbuf *sb, const char *msg,
                                  const char *line_separator);
@@ -163,6 +164,9 @@ extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *r
 extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
 extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
 
+/* largest postive number a signed 32-bit integer can contain */
+#define INFINITE_DEPTH 0x7fffffff
+
 extern int register_shallow(const unsigned char *sha1);
 extern int unregister_shallow(const unsigned char *sha1);
 extern int for_each_commit_graft(each_commit_graft_fn, void *);
index 7b444b68abcebb5829a5628ee5b74cf3f0020c0b..aefd80b12a079d4a3c91d43c8a2c33ed6fbd0a38 100644 (file)
--- a/config.c
+++ b/config.c
@@ -566,6 +566,12 @@ static int git_default_core_config(const char *var, const char *value)
                trust_ctime = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "core.statinfo")) {
+               if (!strcasecmp(value, "default"))
+                       check_stat = 1;
+               else if (!strcasecmp(value, "minimal"))
+                       check_stat = 0;
+       }
 
        if (!strcmp(var, "core.quotepath")) {
                quote_path_fully = git_config_bool(var, value);
@@ -717,6 +723,14 @@ static int git_default_core_config(const char *var, const char *value)
        if (!strcmp(var, "core.editor"))
                return git_config_string(&editor_program, var, value);
 
+       if (!strcmp(var, "core.commentchar")) {
+               const char *comment;
+               int ret = git_config_string(&comment, var, value);
+               if (!ret)
+                       comment_line_char = comment[0];
+               return ret;
+       }
+
        if (!strcmp(var, "core.askpass"))
                return git_config_string(&askpass_program, var, value);
 
@@ -1667,3 +1681,36 @@ int config_error_nonbool(const char *var)
 {
        return error("Missing value for '%s'", var);
 }
+
+int parse_config_key(const char *var,
+                    const char *section,
+                    const char **subsection, int *subsection_len,
+                    const char **key)
+{
+       int section_len = strlen(section);
+       const char *dot;
+
+       /* Does it start with "section." ? */
+       if (prefixcmp(var, section) || var[section_len] != '.')
+               return -1;
+
+       /*
+        * Find the key; we don't know yet if we have a subsection, but we must
+        * parse backwards from the end, since the subsection may have dots in
+        * it, too.
+        */
+       dot = strrchr(var, '.');
+       *key = dot + 1;
+
+       /* Did we have a subsection at all? */
+       if (dot == var + section_len) {
+               *subsection = NULL;
+               *subsection_len = 0;
+       }
+       else {
+               *subsection = var + section_len + 1;
+               *subsection_len = dot - *subsection;
+       }
+
+       return 0;
+}
index 14dd5e7ca272350b3fa13d6fc8da3ccb46e12227..5770b6f2d01c6d08c337a2901bba276f3d99f2d0 100644 (file)
@@ -531,10 +531,19 @@ __git_complete_strategy ()
        return 1
 }
 
+__git_commands () {
+       if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
+       then
+               printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
+       else
+               git help -a|egrep '^  [a-zA-Z0-9]'
+       fi
+}
+
 __git_list_all_commands ()
 {
        local i IFS=" "$'\n'
-       for i in $(git help -a|egrep '^  [a-zA-Z0-9]')
+       for i in $(__git_commands)
        do
                case $i in
                *--*)             : helper pattern;;
@@ -563,6 +572,7 @@ __git_list_porcelain_commands ()
                archimport)       : import;;
                cat-file)         : plumbing;;
                check-attr)       : plumbing;;
+               check-ignore)     : plumbing;;
                check-ref-format) : plumbing;;
                checkout-index)   : plumbing;;
                commit-tree)      : plumbing;;
@@ -2431,7 +2441,7 @@ if [[ -n ${ZSH_VERSION-} ]]; then
                                --*=*|*.) ;;
                                *) c="$c " ;;
                                esac
-                               array+=("$c")
+                               array[$#array+1]="$c"
                        done
                        compset -P '*[=:]'
                        compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
index b2171a092eafef4cf1e5e1abfa37695a0c111927..0e5b72d7f19185c3b6b36150085d475c7ef15db4 100755 (executable)
@@ -237,6 +237,7 @@ generate_email_header()
        X-Git-Reftype: $refname_type
        X-Git-Oldrev: $oldrev
        X-Git-Newrev: $newrev
+       Auto-Submitted: auto-generated
 
        This is an automated email from the git hooks/post-receive script. It was
        generated because a ref change was pushed to the repository containing
index c7006000a6f4a19fa4e5f4bf3fc543d4756a1f4d..328c2dc76dd33e47cfe69064757631e9717771e6 100755 (executable)
@@ -53,7 +53,7 @@ def gittz(tz):
     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
 
 def hgmode(mode):
-    m = { '0100755': 'x', '0120000': 'l' }
+    m = { '100755': 'x', '120000': 'l' }
     return m.get(mode, '')
 
 def get_config(config):
@@ -720,6 +720,14 @@ def do_export(parser):
     if peer:
         parser.repo.push(peer, force=False)
 
+def fix_path(alias, repo, orig_url):
+    repo_url = util.url(repo.url())
+    url = util.url(orig_url)
+    if str(url) == str(repo_url):
+        return
+    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % repo_url]
+    subprocess.call(cmd)
+
 def main(args):
     global prefix, dirname, branches, bmarks
     global marks, blob_marks, parsed_refs
@@ -766,6 +774,9 @@ def main(args):
     repo = get_repo(url, alias)
     prefix = 'refs/hg/%s' % alias
 
+    if not is_tmp:
+        fix_path(alias, peer or repo, url)
+
     if not os.path.exists(dirname):
         os.makedirs(dirname)
 
index 3e76d9fb60bff8b25f5b2751c6e11cfabb6c9dc6..7e3967f5b6f0c19939bf1c0c30be0d756ec6aa04 100755 (executable)
@@ -109,6 +109,74 @@ setup () {
 
 setup
 
+test_expect_success 'executable bit' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       git init -q gitrepo &&
+       cd gitrepo &&
+       echo alpha > alpha &&
+       chmod 0644 alpha &&
+       git add alpha &&
+       git commit -m "add alpha" &&
+       chmod 0755 alpha &&
+       git add alpha &&
+       git commit -m "set executable bit" &&
+       chmod 0644 alpha &&
+       git add alpha &&
+       git commit -m "clear executable bit"
+       ) &&
+
+       for x in hg git; do
+               (
+               hg_clone_$x gitrepo hgrepo-$x &&
+               cd hgrepo-$x &&
+               hg_log . &&
+               hg manifest -r 1 -v &&
+               hg manifest -v
+               ) > output-$x &&
+
+               git_clone_$x hgrepo-$x gitrepo2-$x &&
+               git_log gitrepo2-$x > log-$x
+       done &&
+       cp -r log-* output-* /tmp/foo/ &&
+
+       test_cmp output-hg output-git &&
+       test_cmp log-hg log-git
+'
+
+test_expect_success 'symlink' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       git init -q gitrepo &&
+       cd gitrepo &&
+       echo alpha > alpha &&
+       git add alpha &&
+       git commit -m "add alpha" &&
+       ln -s alpha beta &&
+       git add beta &&
+       git commit -m "add beta"
+       ) &&
+
+       for x in hg git; do
+               (
+               hg_clone_$x gitrepo hgrepo-$x &&
+               cd hgrepo-$x &&
+               hg_log . &&
+               hg manifest -v
+               ) > output-$x &&
+
+               git_clone_$x hgrepo-$x gitrepo2-$x &&
+               git_log gitrepo2-$x > log-$x
+       done &&
+
+       test_cmp output-hg output-git &&
+       test_cmp log-hg log-git
+'
+
 test_expect_success 'merge conflict 1' '
        mkdir -p tmp && cd tmp &&
        test_when_finished "cd .. && rm -rf tmp" &&
index 17cf6f961f5fdf1e8f790acf7f498dc863e11e45..4e78a1c3cda15659256c7aa64eb98cab7737c61f 100755 (executable)
@@ -14,7 +14,7 @@
 
 def getrevlimit():
         var = 'SVNRMAX'
-        if os.environ.has_key(var):
+        if var in os.environ:
                 return os.environ[var]
         return None
 
@@ -44,7 +44,7 @@ def writedump(url, lower, upper):
 
 if __name__ == "__main__":
         if not (len(sys.argv) in (3, 4, 5)):
-                print "usage: %s dump URL -rLOWER:UPPER"
+                print("usage: %s dump URL -rLOWER:UPPER")
                 sys.exit(1)
         if not sys.argv[1] == 'dump': raise NotImplementedError('only "dump" is suppported.')
         url = sys.argv[2]
index 66021550c32f86e662fe5da84c852e80ae790450..3520252d3abaf49d4b7682ad083da8b7ae6be4c6 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -457,7 +457,7 @@ static struct convert_driver {
 
 static int read_convert_config(const char *var, const char *value, void *cb)
 {
-       const char *ep, *name;
+       const char *key, *name;
        int namelen;
        struct convert_driver *drv;
 
@@ -465,10 +465,8 @@ static int read_convert_config(const char *var, const char *value, void *cb)
         * External conversion drivers are configured using
         * "filter.<name>.variable".
         */
-       if (prefixcmp(var, "filter.") || (ep = strrchr(var, '.')) == var + 6)
+       if (parse_config_key(var, "filter", &name, &namelen, &key) < 0 || !name)
                return 0;
-       name = var + 7;
-       namelen = ep - name;
        for (drv = user_convert; drv; drv = drv->next)
                if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
                        break;
@@ -479,8 +477,6 @@ static int read_convert_config(const char *var, const char *value, void *cb)
                user_convert_tail = &(drv->next);
        }
 
-       ep++;
-
        /*
         * filter.<name>.smudge and filter.<name>.clean specifies
         * the command line:
@@ -490,13 +486,13 @@ static int read_convert_config(const char *var, const char *value, void *cb)
         * The command-line will not be interpolated in any way.
         */
 
-       if (!strcmp("smudge", ep))
+       if (!strcmp("smudge", key))
                return git_config_string(&drv->smudge, var, value);
 
-       if (!strcmp("clean", ep))
+       if (!strcmp("clean", key))
                return git_config_string(&drv->clean, var, value);
 
-       if (!strcmp("required", ep)) {
+       if (!strcmp("required", key)) {
                drv->required = git_config_bool(var, value);
                return 0;
        }
diff --git a/dir.c b/dir.c
index 9dde68a2f3a1a325d2c5b90fa4d20dbf7670652c..57394e452eb0de117b27f64804e529b617a6c7e0 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -194,12 +194,19 @@ static int match_one(const char *match, const char *name, int namelen)
 }
 
 /*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs.  The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with.  A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
  */
 int match_pathspec(const char **pathspec, const char *name, int namelen,
                int prefix, char *seen)
@@ -269,12 +276,19 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
 }
 
 /*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs.  The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with.  A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
  */
 int match_pathspec_depth(const struct pathspec *ps,
                         const char *name, int namelen,
@@ -379,7 +393,7 @@ void parse_exclude_pattern(const char **pattern,
 }
 
 void add_exclude(const char *string, const char *base,
-                int baselen, struct exclude_list *el)
+                int baselen, struct exclude_list *el, int srcpos)
 {
        struct exclude *x;
        int patternlen;
@@ -403,8 +417,10 @@ void add_exclude(const char *string, const char *base,
        x->base = base;
        x->baselen = baselen;
        x->flags = flags;
+       x->srcpos = srcpos;
        ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
        el->excludes[el->nr++] = x;
+       x->el = el;
 }
 
 static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
@@ -441,20 +457,21 @@ void clear_exclude_list(struct exclude_list *el)
        for (i = 0; i < el->nr; i++)
                free(el->excludes[i]);
        free(el->excludes);
+       free(el->filebuf);
 
        el->nr = 0;
        el->excludes = NULL;
+       el->filebuf = NULL;
 }
 
 int add_excludes_from_file_to_list(const char *fname,
                                   const char *base,
                                   int baselen,
-                                  char **buf_p,
                                   struct exclude_list *el,
                                   int check_index)
 {
        struct stat st;
-       int fd, i;
+       int fd, i, lineno = 1;
        size_t size = 0;
        char *buf, *entry;
 
@@ -492,25 +509,43 @@ int add_excludes_from_file_to_list(const char *fname,
                close(fd);
        }
 
-       if (buf_p)
-               *buf_p = buf;
+       el->filebuf = buf;
        entry = buf;
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
                                buf[i - (i && buf[i-1] == '\r')] = 0;
-                               add_exclude(entry, base, baselen, el);
+                               add_exclude(entry, base, baselen, el, lineno);
                        }
+                       lineno++;
                        entry = buf + i + 1;
                }
        }
        return 0;
 }
 
+struct exclude_list *add_exclude_list(struct dir_struct *dir,
+                                     int group_type, const char *src)
+{
+       struct exclude_list *el;
+       struct exclude_list_group *group;
+
+       group = &dir->exclude_list_group[group_type];
+       ALLOC_GROW(group->el, group->nr + 1, group->alloc);
+       el = &group->el[group->nr++];
+       memset(el, 0, sizeof(*el));
+       el->src = src;
+       return el;
+}
+
+/*
+ * Used to set up core.excludesfile and .git/info/exclude lists.
+ */
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
-       if (add_excludes_from_file_to_list(fname, "", 0, NULL,
-                                          &dir->exclude_list[EXC_FILE], 0) < 0)
+       struct exclude_list *el;
+       el = add_exclude_list(dir, EXC_FILE, fname);
+       if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
                die("cannot use %s as an exclude file", fname);
 }
 
@@ -520,6 +555,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
  */
 static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
+       struct exclude_list_group *group;
        struct exclude_list *el;
        struct exclude_stack *stk = NULL;
        int current;
@@ -528,17 +564,21 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
            (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
                return; /* too long a path -- ignore */
 
-       /* Pop the directories that are not the prefix of the path being checked. */
-       el = &dir->exclude_list[EXC_DIRS];
+       group = &dir->exclude_list_group[EXC_DIRS];
+
+       /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
+        * which originate from directories not in the prefix of the
+        * path being checked. */
        while ((stk = dir->exclude_stack) != NULL) {
                if (stk->baselen <= baselen &&
                    !strncmp(dir->basebuf, base, stk->baselen))
                        break;
+               el = &group->el[dir->exclude_stack->exclude_ix];
                dir->exclude_stack = stk->prev;
-               while (stk->exclude_ix < el->nr)
-                       free(el->excludes[--el->nr]);
-               free(stk->filebuf);
+               free((char *)el->src); /* see strdup() below */
+               clear_exclude_list(el);
                free(stk);
+               group->nr--;
        }
 
        /* Read from the parent directories and push them down. */
@@ -559,13 +599,22 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                }
                stk->prev = dir->exclude_stack;
                stk->baselen = cp - base;
-               stk->exclude_ix = el->nr;
                memcpy(dir->basebuf + current, base + current,
                       stk->baselen - current);
                strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
+               /*
+                * dir->basebuf gets reused by the traversal, but we
+                * need fname to remain unchanged to ensure the src
+                * member of each struct exclude correctly
+                * back-references its source file.  Other invocations
+                * of add_exclude_list provide stable strings, so we
+                * strdup() and free() here in the caller.
+                */
+               el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));
+               stk->exclude_ix = group->nr - 1;
                add_excludes_from_file_to_list(dir->basebuf,
                                               dir->basebuf, stk->baselen,
-                                              &stk->filebuf, el, 1);
+                                              el, 1);
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
@@ -636,7 +685,8 @@ int match_pathname(const char *pathname, int pathlen,
        }
 
        return wildmatch(pattern, name,
-                        ignore_case ? FNM_CASEFOLD : 0) == 0;
+                        WM_PATHNAME | (ignore_case ? WM_CASEFOLD : 0),
+                        NULL) == 0;
 }
 
 /*
@@ -712,18 +762,23 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,
                                             int *dtype_p)
 {
        int pathlen = strlen(pathname);
-       int st;
+       int i, j;
+       struct exclude_list_group *group;
        struct exclude *exclude;
        const char *basename = strrchr(pathname, '/');
        basename = (basename) ? basename+1 : pathname;
 
        prep_exclude(dir, pathname, basename-pathname);
-       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               exclude = last_exclude_matching_from_list(
-                       pathname, pathlen, basename, dtype_p,
-                       &dir->exclude_list[st]);
-               if (exclude)
-                       return exclude;
+
+       for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+               group = &dir->exclude_list_group[i];
+               for (j = group->nr - 1; j >= 0; j--) {
+                       exclude = last_exclude_matching_from_list(
+                               pathname, pathlen, basename, dtype_p,
+                               &group->el[j]);
+                       if (exclude)
+                               return exclude;
+               }
        }
        return NULL;
 }
@@ -1627,3 +1682,33 @@ int limit_pathspec_to_literal(void)
                flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
        return flag;
 }
+
+/*
+ * Frees memory within dir which was allocated for exclude lists and
+ * the exclude_stack.  Does not free dir itself.
+ */
+void clear_directory(struct dir_struct *dir)
+{
+       int i, j;
+       struct exclude_list_group *group;
+       struct exclude_list *el;
+       struct exclude_stack *stk;
+
+       for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+               group = &dir->exclude_list_group[i];
+               for (j = 0; j < group->nr; j++) {
+                       el = &group->el[j];
+                       if (i == EXC_DIRS)
+                               free((char *)el->src);
+                       clear_exclude_list(el);
+               }
+               free(group->el);
+       }
+
+       stk = dir->exclude_stack;
+       while (stk) {
+               struct exclude_stack *prev = stk->prev;
+               free(stk);
+               stk = prev;
+       }
+}
diff --git a/dir.h b/dir.h
index ae1bc467ae909e6db151b055e212ded45fac34f6..c3eb4b520effb63e73653194e4266c0ef628cccd 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -16,21 +16,41 @@ struct dir_entry {
 #define EXC_FLAG_NEGATIVE 16
 
 /*
- * Each .gitignore file will be parsed into patterns which are then
- * appended to the relevant exclude_list (either EXC_DIRS or
- * EXC_FILE).  exclude_lists are also used to represent the list of
- * --exclude values passed via CLI args (EXC_CMDL).
+ * Each excludes file will be parsed into a fresh exclude_list which
+ * is appended to the relevant exclude_list_group (either EXC_DIRS or
+ * EXC_FILE).  An exclude_list within the EXC_CMDL exclude_list_group
+ * can also be used to represent the list of --exclude values passed
+ * via CLI args.
  */
 struct exclude_list {
        int nr;
        int alloc;
+
+       /* remember pointer to exclude file contents so we can free() */
+       char *filebuf;
+
+       /* origin of list, e.g. path to filename, or descriptive string */
+       const char *src;
+
        struct exclude {
+               /*
+                * This allows callers of last_exclude_matching() etc.
+                * to determine the origin of the matching pattern.
+                */
+               struct exclude_list *el;
+
                const char *pattern;
                int patternlen;
                int nowildcardlen;
                const char *base;
                int baselen;
                int flags;
+
+               /*
+                * Counting starts from 1 for line numbers in ignore files,
+                * and from -1 decrementing for patterns from CLI args.
+                */
+               int srcpos;
        } **excludes;
 };
 
@@ -42,9 +62,13 @@ struct exclude_list {
  */
 struct exclude_stack {
        struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
-       char *filebuf; /* remember pointer to per-directory exclude file contents so we can free() */
        int baselen;
-       int exclude_ix;
+       int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
+};
+
+struct exclude_list_group {
+       int nr, alloc;
+       struct exclude_list *el;
 };
 
 struct dir_struct {
@@ -62,16 +86,23 @@ struct dir_struct {
 
        /* Exclude info */
        const char *exclude_per_dir;
-       struct exclude_list exclude_list[3];
+
        /*
-        * We maintain three exclude pattern lists:
+        * We maintain three groups of exclude pattern lists:
+        *
         * EXC_CMDL lists patterns explicitly given on the command line.
         * EXC_DIRS lists patterns obtained from per-directory ignore files.
-        * EXC_FILE lists patterns from fallback ignore files.
+        * EXC_FILE lists patterns from fallback ignore files, e.g.
+        *   - .git/info/exclude
+        *   - core.excludesfile
+        *
+        * Each group contains multiple exclude lists, a single list
+        * per source.
         */
 #define EXC_CMDL 0
 #define EXC_DIRS 1
 #define EXC_FILE 2
+       struct exclude_list_group exclude_list_group[3];
 
        /*
         * Temporary variables which are used during loading of the
@@ -85,6 +116,12 @@ struct dir_struct {
        char basebuf[PATH_MAX];
 };
 
+/*
+ * The ordering of these constants is significant, with
+ * higher-numbered match types signifying "closer" (i.e. more
+ * specific) matches which will override lower-numbered match types
+ * when populating the seen[] array.
+ */
 #define MATCHED_RECURSIVELY 1
 #define MATCHED_FNMATCH 2
 #define MATCHED_EXACTLY 3
@@ -129,13 +166,16 @@ extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, c
 extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
 
 
+extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
+                                            int group_type, const char *src);
 extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
-                                         char **buf_p, struct exclude_list *el, int check_index);
+                                         struct exclude_list *el, int check_index);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
 extern void add_exclude(const char *string, const char *base,
-                       int baselen, struct exclude_list *el);
+                       int baselen, struct exclude_list *el, int srcpos);
 extern void clear_exclude_list(struct exclude_list *el);
+extern void clear_directory(struct dir_struct *dir);
 extern int file_exists(const char *);
 
 extern int is_inside_dir(const char *dir);
index 85edd7f95adecd8316b49638d7bda2841839206d..89d6c70c15a95aff82f0063e3c0dd6bdab026213 100644 (file)
@@ -13,6 +13,7 @@
 
 int trust_executable_bit = 1;
 int trust_ctime = 1;
+int check_stat = 1;
 int has_symlinks = 1;
 int minimum_abbrev = 4, default_abbrev = 7;
 int ignore_case;
@@ -62,6 +63,12 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 struct startup_info *startup_info;
 unsigned long pack_size_limit_cfg;
 
+/*
+ * The character that begins a commented line in user-editable file
+ * that is subject to stripspace.
+ */
+char comment_line_char = '#';
+
 /* Parallel index stat data preload? */
 int core_preload_index = 0;
 
index f0acdf7331eeaf7a55ad09309a788c5971fe9b7f..6d8926a5504a6cd93e19860f85d58816bdd5c222 100644 (file)
@@ -594,6 +594,9 @@ static int everything_local(struct fetch_pack_args *args,
        for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
 
+               if (!has_sha1_file(ref->old_sha1))
+                       continue;
+
                o = parse_object(ref->old_sha1);
                if (!o)
                        continue;
index c682d34094d618734ac52d6d7e15a6c10ccfdeb6..202130f888bee14e73b8cc108f39f2da8a14d23b 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -334,7 +334,7 @@ split_patches () {
                        # Since we cannot guarantee that the commit message is in
                        # git-friendly format, we put no Subject: line and just consume
                        # all of the message as the body
-                       perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
+                       LANG=C LC_ALL=C perl -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 }
                                if ($subject) { print ; }
                                elsif (/^\# User /) { s/\# User/From:/ ; print ; }
                                elsif (/^\# Date /) {
@@ -664,7 +664,7 @@ do
                        sed -e '1,/^$/d' >"$dotest/msg-clean"
                        echo "$commit" >"$dotest/original-commit"
                        get_author_ident_from_commit "$commit" >"$dotest/author-script"
-                       git diff-tree --root --binary "$commit" >"$dotest/patch"
+                       git diff-tree --root --binary --full-index "$commit" >"$dotest/patch"
                else
                        git mailinfo $keep $no_inbody_headers $scissors $utf8 "$dotest/msg" "$dotest/patch" \
                                <"$dotest/$msgnum" >"$dotest/info" ||
index e5a4b7450bfa7a700ce1c76f597266a9724a19f5..cc2abeea0debcbe70c31320abb904e6d55cfe2bc 100644 (file)
 #include <sys/time.h>
 #include <time.h>
 #include <signal.h>
+#ifndef USE_WILDMATCH
 #include <fnmatch.h>
+#endif
 #include <assert.h>
 #include <regex.h>
 #include <utime.h>
@@ -280,6 +282,17 @@ extern char *gitbasename(char *);
 
 #include "compat/bswap.h"
 
+#ifdef USE_WILDMATCH
+#include "wildmatch.h"
+#define FNM_PATHNAME WM_PATHNAME
+#define FNM_CASEFOLD WM_CASEFOLD
+#define FNM_NOMATCH  WM_NOMATCH
+static inline int fnmatch(const char *pattern, const char *string, int flags)
+{
+       return wildmatch(pattern, string, flags, NULL);
+}
+#endif
+
 /* General helper functions */
 extern void vreportf(const char *prefix, const char *err, va_list params);
 extern void vwritef(int fd, const char *prefix, const char *err, va_list params);
@@ -297,7 +310,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)))
  * behavior. But since we're only trying to help gcc, anyway, it's OK; other
  * compilers will fall back to using the function as usual.
  */
-#ifdef __GNUC__
+#if defined(__GNUC__) && ! defined(__clang__)
 #define error(fmt, ...) (error((fmt), ##__VA_ARGS__), -1)
 #endif
 
index c5ebfa06365da82d68103300ad6e8c818ab37fb6..36790749830251854fa475f2bd8d33ece07203f6 100755 (executable)
@@ -60,6 +60,7 @@
     'Valid-responses' => \&req_Validresponses,
     'valid-requests'  => \&req_validrequests,
     'Directory'       => \&req_Directory,
+    'Sticky'          => \&req_Sticky,
     'Entry'           => \&req_Entry,
     'Modified'        => \&req_Modified,
     'Unchanged'       => \&req_Unchanged,
@@ -470,11 +471,19 @@ sub req_Directory
     {
         $log->info("Setting prepend to '$state->{path}'");
         $state->{prependdir} = $state->{path};
+        my %entries;
         foreach my $entry ( keys %{$state->{entries}} )
         {
-            $state->{entries}{$state->{prependdir} . $entry} = $state->{entries}{$entry};
-            delete $state->{entries}{$entry};
+            $entries{$state->{prependdir} . $entry} = $state->{entries}{$entry};
         }
+        $state->{entries}=\%entries;
+
+        my %dirMap;
+        foreach my $dir ( keys %{$state->{dirMap}} )
+        {
+            $dirMap{$state->{prependdir} . $dir} = $state->{dirMap}{$dir};
+        }
+        $state->{dirMap}=\%dirMap;
     }
 
     if ( defined ( $state->{prependdir} ) )
@@ -482,9 +491,60 @@ sub req_Directory
         $log->debug("Prepending '$state->{prependdir}' to state|directory");
         $state->{directory} = $state->{prependdir} . $state->{directory}
     }
+
+    if ( ! defined($state->{dirMap}{$state->{directory}}) )
+    {
+        $state->{dirMap}{$state->{directory}} =
+            {
+                'names' => {}
+                #'tagspec' => undef
+            };
+    }
+
     $log->debug("req_Directory : localdir=$data repository=$repository path=$state->{path} directory=$state->{directory} module=$state->{module}");
 }
 
+# Sticky tagspec \n
+#     Response expected: no. Tell the server that the directory most
+#     recently specified with Directory has a sticky tag or date
+#     tagspec. The first character of tagspec is T for a tag, D for
+#     a date, or some other character supplied by a Set-sticky
+#     response from a previous request to the server. The remainder
+#     of tagspec contains the actual tag or date, again as supplied
+#     by Set-sticky.
+#          The server should remember Static-directory and Sticky requests
+#     for a particular directory; the client need not resend them each
+#     time it sends a Directory request for a given directory. However,
+#     the server is not obliged to remember them beyond the context
+#     of a single command.
+sub req_Sticky
+{
+    my ( $cmd, $tagspec ) = @_;
+
+    my ( $stickyInfo );
+    if($tagspec eq "")
+    {
+        # nothing
+    }
+    elsif($tagspec=~/^T([^ ]+)\s*$/)
+    {
+        $stickyInfo = { 'tag' => $1 };
+    }
+    elsif($tagspec=~/^D([0-9.]+)\s*$/)
+    {
+        $stickyInfo= { 'date' => $1 };
+    }
+    else
+    {
+        die "Unknown tag_or_date format\n";
+    }
+    $state->{dirMap}{$state->{directory}}{stickyInfo}=$stickyInfo;
+
+    $log->debug("req_Sticky : tagspec=$tagspec repository=$state->{repository}"
+                . " path=$state->{path} directory=$state->{directory}"
+                . " module=$state->{module}");
+}
+
 # Entry entry-line \n
 #     Response expected: no. Tell the server what version of a file is on the
 #     local machine. The name in entry-line is a name relative to the directory
@@ -511,6 +571,8 @@ sub req_Entry
         tag_or_date => $data[5],
     };
 
+    $state->{dirMap}{$state->{directory}}{names}{$data[1]} = 'F';
+
     $log->info("Received entry line '$data' => '" . $state->{directory} . $data[1] . "'");
 }
 
@@ -549,7 +611,10 @@ sub req_add
     {
         $filename = filecleanup($filename);
 
-        my $meta = $updater->getmeta($filename);
+        # no -r, -A, or -D with add
+        my $stickyInfo = resolveStickyInfo($filename);
+
+        my $meta = $updater->getmeta($filename,$stickyInfo);
         my $wrev = revparse($filename);
 
         if ($wrev && $meta && ($wrev=~/^-/))
@@ -572,8 +637,10 @@ sub req_add
 
                 # this is an "entries" line
                 my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
-                $log->debug("/$filepart/$meta->{revision}//$kopts/");
-                print "/$filepart/$meta->{revision}//$kopts/\n";
+                my $entryLine = "/$filepart/$meta->{revision}//$kopts/";
+                $entryLine .= getStickyTagOrDate($stickyInfo);
+                $log->debug($entryLine);
+                print "$entryLine\n";
                 # permissions
                 $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
                 print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
@@ -604,7 +671,8 @@ sub req_add
         print "$filename\n";
         my $kopts = kopts_from_path($filename,"file",
                         $state->{entries}{$filename}{modified_filename});
-        print "/$filepart/0//$kopts/\n";
+        print "/$filepart/0//$kopts/" .
+              getStickyTagOrDate($stickyInfo) . "\n";
 
         my $requestedKopts = $state->{opt}{k};
         if(defined($requestedKopts))
@@ -672,7 +740,10 @@ sub req_remove
             next;
         }
 
-        my $meta = $updater->getmeta($filename);
+        # only from entries
+        my $stickyInfo = resolveStickyInfo($filename);
+
+        my $meta = $updater->getmeta($filename,$stickyInfo);
         my $wrev = revparse($filename);
 
         unless ( defined ( $wrev ) )
@@ -702,7 +773,7 @@ sub req_remove
         print "Checked-in $dirpart\n";
         print "$filename\n";
         my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
-        print "/$filepart/-$wrev//$kopts/\n";
+        print "/$filepart/-$wrev//$kopts/" . getStickyTagOrDate($stickyInfo) . "\n";
 
         $rmcount++;
     }
@@ -882,6 +953,9 @@ sub req_co
         return 1;
     }
 
+    my $stickyInfo = { 'tag' => $state->{opt}{r},
+                       'date' => $state->{opt}{D} };
+
     my $module = $state->{args}[0];
     $state->{module} = $module;
     my $checkout_path = $module;
@@ -899,64 +973,32 @@ sub req_co
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log);
     $updater->update();
 
-    $checkout_path =~ s|/$||; # get rid of trailing slashes
+    my $headHash;
+    if( defined($stickyInfo) && defined($stickyInfo->{tag}) )
+    {
+        $headHash = $updater->lookupCommitRef($stickyInfo->{tag});
+        if( !defined($headHash) )
+        {
+            print "error 1 no such tag `$stickyInfo->{tag}'\n";
+            cleanupWorkTree();
+            exit;
+        }
+    }
 
-    # Eclipse seems to need the Clear-sticky command
-    # to prepare the 'Entries' file for the new directory.
-    print "Clear-sticky $checkout_path/\n";
-    print $state->{CVSROOT} . "/$module/\n";
-    print "Clear-static-directory $checkout_path/\n";
-    print $state->{CVSROOT} . "/$module/\n";
-    print "Clear-sticky $checkout_path/\n"; # yes, twice
-    print $state->{CVSROOT} . "/$module/\n";
-    print "Template $checkout_path/\n";
-    print $state->{CVSROOT} . "/$module/\n";
-    print "0\n";
-
-    # instruct the client that we're checking out to $checkout_path
-    print "E cvs checkout: Updating $checkout_path\n";
+    $checkout_path =~ s|/$||; # get rid of trailing slashes
 
     my %seendirs = ();
     my $lastdir ='';
 
-    # recursive
-    sub prepdir {
-       my ($dir, $repodir, $remotedir, $seendirs) = @_;
-       my $parent = dirname($dir);
-       $dir       =~ s|/+$||;
-       $repodir   =~ s|/+$||;
-       $remotedir =~ s|/+$||;
-       $parent    =~ s|/+$||;
-       $log->debug("announcedir $dir, $repodir, $remotedir" );
-
-       if ($parent eq '.' || $parent eq './') {
-           $parent = '';
-       }
-       # recurse to announce unseen parents first
-       if (length($parent) && !exists($seendirs->{$parent})) {
-           prepdir($parent, $repodir, $remotedir, $seendirs);
-       }
-       # Announce that we are going to modify at the parent level
-       if ($parent) {
-           print "E cvs checkout: Updating $remotedir/$parent\n";
-       } else {
-           print "E cvs checkout: Updating $remotedir\n";
-       }
-       print "Clear-sticky $remotedir/$parent/\n";
-       print "$repodir/$parent/\n";
-
-       print "Clear-static-directory $remotedir/$dir/\n";
-       print "$repodir/$dir/\n";
-       print "Clear-sticky $remotedir/$parent/\n"; # yes, twice
-       print "$repodir/$parent/\n";
-       print "Template $remotedir/$dir/\n";
-       print "$repodir/$dir/\n";
-       print "0\n";
-
-       $seendirs->{$dir} = 1;
-    }
-
-    foreach my $git ( @{$updater->gethead} )
+    prepDirForOutput(
+            ".",
+            $state->{CVSROOT} . "/$module",
+            $checkout_path,
+            \%seendirs,
+            'checkout',
+            $state->{dirArgs} );
+
+    foreach my $git ( @{$updater->getAnyHead($headHash)} )
     {
         # Don't want to check out deleted files
         next if ( $git->{filehash} eq "deleted" );
@@ -964,16 +1006,13 @@ sub req_co
         my $fullName = $git->{name};
         ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
 
-       if (length($git->{dir}) && $git->{dir} ne './'
-           && $git->{dir} ne $lastdir ) {
-           unless (exists($seendirs{$git->{dir}})) {
-               prepdir($git->{dir}, $state->{CVSROOT} . "/$module/",
-                       $checkout_path, \%seendirs);
-               $lastdir = $git->{dir};
-               $seendirs{$git->{dir}} = 1;
-           }
-           print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
-       }
+        unless (exists($seendirs{$git->{dir}})) {
+            prepDirForOutput($git->{dir}, $state->{CVSROOT} . "/$module/",
+                             $checkout_path, \%seendirs, 'checkout',
+                             $state->{dirArgs} );
+            $lastdir = $git->{dir};
+            $seendirs{$git->{dir}} = 1;
+        }
 
         # modification time of this file
         print "Mod-time $git->{modified}\n";
@@ -993,7 +1032,8 @@ sub req_co
 
         # this is an "entries" line
         my $kopts = kopts_from_path($fullName,"sha1",$git->{filehash});
-        print "/$git->{name}/$git->{revision}//$kopts/\n";
+        print "/$git->{name}/$git->{revision}//$kopts/" .
+                        getStickyTagOrDate($stickyInfo) . "\n";
         # permissions
         print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n";
 
@@ -1006,6 +1046,119 @@ sub req_co
     statecleanup();
 }
 
+# used by req_co and req_update to set up directories for files
+# recursively handles parents
+sub prepDirForOutput
+{
+    my ($dir, $repodir, $remotedir, $seendirs, $request, $dirArgs) = @_;
+
+    my $parent = dirname($dir);
+    $dir       =~ s|/+$||;
+    $repodir   =~ s|/+$||;
+    $remotedir =~ s|/+$||;
+    $parent    =~ s|/+$||;
+
+    if ($parent eq '.' || $parent eq './')
+    {
+        $parent = '';
+    }
+    # recurse to announce unseen parents first
+    if( length($parent) &&
+        !exists($seendirs->{$parent}) &&
+        ( $request eq "checkout" ||
+          exists($dirArgs->{$parent}) ) )
+    {
+        prepDirForOutput($parent, $repodir, $remotedir,
+                         $seendirs, $request, $dirArgs);
+    }
+    # Announce that we are going to modify at the parent level
+    if ($dir eq '.' || $dir eq './')
+    {
+        $dir = '';
+    }
+    if(exists($seendirs->{$dir}))
+    {
+        return;
+    }
+    $log->debug("announcedir $dir, $repodir, $remotedir" );
+    my($thisRemoteDir,$thisRepoDir);
+    if ($dir ne "")
+    {
+        $thisRepoDir="$repodir/$dir";
+        if($remotedir eq ".")
+        {
+            $thisRemoteDir=$dir;
+        }
+        else
+        {
+            $thisRemoteDir="$remotedir/$dir";
+        }
+    }
+    else
+    {
+        $thisRepoDir=$repodir;
+        $thisRemoteDir=$remotedir;
+    }
+    unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
+    {
+        print "E cvs $request: Updating $thisRemoteDir\n";
+    }
+
+    my ($opt_r)=$state->{opt}{r};
+    my $stickyInfo;
+    if(exists($state->{opt}{A}))
+    {
+        # $stickyInfo=undef;
+    }
+    elsif( defined($opt_r) && $opt_r ne "" )
+           # || ( defined($state->{opt}{D}) && $state->{opt}{D} ne "" ) # TODO
+    {
+        $stickyInfo={ 'tag' => (defined($opt_r)?$opt_r:undef) };
+
+        # TODO: Convert -D value into the form 2011.04.10.04.46.57,
+        #   similar to an entry line's sticky date, without the D prefix.
+        #   It sometimes (always?) arrives as something more like
+        #   '10 Apr 2011 04:46:57 -0000'...
+        # $stickyInfo={ 'date' => (defined($stickyDate)?$stickyDate:undef) };
+    }
+    else
+    {
+        $stickyInfo=getDirStickyInfo($state->{prependdir} . $dir);
+    }
+
+    my $stickyResponse;
+    if(defined($stickyInfo))
+    {
+        $stickyResponse = "Set-sticky $thisRemoteDir/\n" .
+                          "$thisRepoDir/\n" .
+                          getStickyTagOrDate($stickyInfo) . "\n";
+    }
+    else
+    {
+        $stickyResponse = "Clear-sticky $thisRemoteDir/\n" .
+                          "$thisRepoDir/\n";
+    }
+
+    unless ( $state->{globaloptions}{-n} )
+    {
+        print $stickyResponse;
+
+        print "Clear-static-directory $thisRemoteDir/\n";
+        print "$thisRepoDir/\n";
+        print $stickyResponse; # yes, twice
+        print "Template $thisRemoteDir/\n";
+        print "$thisRepoDir/\n";
+        print "0\n";
+    }
+
+    $seendirs->{$dir} = 1;
+
+    # FUTURE: This would more accurately emulate CVS by sending
+    #   another copy of sticky after processing the files in that
+    #   directory.  Or intermediate: perhaps send all sticky's for
+    #   $seendirs after after processing all files.
+}
+
 # update \n
 #     Response expected: yes. Actually do a cvs update command. This uses any
 #     previous Argument, Directory, Entry, or Modified requests, if they have
@@ -1049,29 +1202,19 @@ sub req_update
 
     #$log->debug("update state : " . Dumper($state));
 
-    my $last_dirname = "///";
+    my($repoDir);
+    $repoDir=$state->{CVSROOT} . "/$state->{module}/$state->{prependdir}";
+
+    my %seendirs = ();
 
     # foreach file specified on the command line ...
-    foreach my $filename ( @{$state->{args}} )
+    foreach my $argsFilename ( @{$state->{args}} )
     {
-        $filename = filecleanup($filename);
+        my $filename;
+        $filename = filecleanup($argsFilename);
 
         $log->debug("Processing file $filename");
 
-        unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
-        {
-            my $cur_dirname = dirname($filename);
-            if ( $cur_dirname ne $last_dirname )
-            {
-                $last_dirname = $cur_dirname;
-                if ( $cur_dirname eq "" )
-                {
-                    $cur_dirname = ".";
-                }
-                print "E cvs update: Updating $cur_dirname\n";
-            }
-        }
-
         # if we have a -C we should pretend we never saw modified stuff
         if ( exists ( $state->{opt}{C} ) )
         {
@@ -1080,13 +1223,11 @@ sub req_update
             $state->{entries}{$filename}{unchanged} = 1;
         }
 
-        my $meta;
-        if ( defined($state->{opt}{r}) and $state->{opt}{r} =~ /^(1\.\d+)$/ )
-        {
-            $meta = $updater->getmeta($filename, $1);
-        } else {
-            $meta = $updater->getmeta($filename);
-        }
+        my $stickyInfo = resolveStickyInfo($filename,
+                                           $state->{opt}{r},
+                                           $state->{opt}{D},
+                                           exists($state->{opt}{A}));
+        my $meta = $updater->getmeta($filename, $stickyInfo);
 
         # If -p was given, "print" the contents of the requested revision.
         if ( exists ( $state->{opt}{p} ) ) {
@@ -1099,6 +1240,17 @@ sub req_update
             next;
         }
 
+        # Directories:
+        prepDirForOutput(
+                dirname($argsFilename),
+                $repoDir,
+                ".",
+                \%seendirs,
+                "update",
+                $state->{dirArgs} );
+
+        my $wrev = revparse($filename);
+
        if ( ! defined $meta )
        {
            $meta = {
@@ -1106,16 +1258,23 @@ sub req_update
                revision => '0',
                filehash => 'added'
            };
+           if($wrev ne "0")
+           {
+               $meta->{filehash}='deleted';
+           }
        }
 
         my $oldmeta = $meta;
 
-        my $wrev = revparse($filename);
-
         # If the working copy is an old revision, lets get that version too for comparison.
-        if ( defined($wrev) and $wrev ne $meta->{revision} )
+        my $oldWrev=$wrev;
+        if(defined($oldWrev))
         {
-            $oldmeta = $updater->getmeta($filename, $wrev);
+            $oldWrev=~s/^-//;
+            if($oldWrev ne $meta->{revision})
+            {
+                $oldmeta = $updater->getmeta($filename, $oldWrev);
+            }
         }
 
         #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");
@@ -1133,6 +1292,7 @@ sub req_update
         if ( defined ( $wrev )
              and defined($meta->{revision})
              and $wrev eq $meta->{revision}
+             and $wrev ne "0"
              and defined($state->{entries}{$filename}{modified_hash})
              and not exists ( $state->{opt}{C} ) )
         {
@@ -1143,7 +1303,7 @@ sub req_update
             next;
         }
 
-        if ( $meta->{filehash} eq "deleted" )
+        if ( $meta->{filehash} eq "deleted" && $wrev ne "0" )
         {
             # TODO: If it has been modified in the sandbox, error out
             #   with the appropriate message, rather than deleting a modified
@@ -1205,10 +1365,6 @@ sub req_update
                    $log->debug("Updating existing file 'Update-existing $dirpart'");
                } else {
                    # instruct client we're sending a file to put in this path as a new file
-                   print "Clear-static-directory $dirpart\n";
-                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
-                   print "Clear-sticky $dirpart\n";
-                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
 
                    $log->debug("Creating new file 'Created $dirpart'");
                    print "Created $dirpart\n";
@@ -1217,8 +1373,10 @@ sub req_update
 
                # this is an "entries" line
                my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
-               $log->debug("/$filepart/$meta->{revision}//$kopts/");
-               print "/$filepart/$meta->{revision}//$kopts/\n";
+                my $entriesLine = "/$filepart/$meta->{revision}//$kopts/";
+                $entriesLine .= getStickyTagOrDate($stickyInfo);
+               $log->debug($entriesLine);
+               print "$entriesLine\n";
 
                # permissions
                $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
@@ -1266,7 +1424,9 @@ sub req_update
                     my $kopts = kopts_from_path("$dirpart/$filepart",
                                                 "file",$mergedFile);
                     $log->debug("/$filepart/$meta->{revision}//$kopts/");
-                    print "/$filepart/$meta->{revision}//$kopts/\n";
+                    my $entriesLine="/$filepart/$meta->{revision}//$kopts/";
+                    $entriesLine .= getStickyTagOrDate($stickyInfo);
+                    print "$entriesLine\n";
                 }
             }
             elsif ( $return == 1 )
@@ -1282,7 +1442,9 @@ sub req_update
                     print $state->{CVSROOT} . "/$state->{module}/$filename\n";
                     my $kopts = kopts_from_path("$dirpart/$filepart",
                                                 "file",$mergedFile);
-                    print "/$filepart/$meta->{revision}/+/$kopts/\n";
+                    my $entriesLine = "/$filepart/$meta->{revision}/+/$kopts/";
+                    $entriesLine .= getStickyTagOrDate($stickyInfo);
+                    print "$entriesLine\n";
                 }
             }
             else
@@ -1310,6 +1472,43 @@ sub req_update
 
     }
 
+    # prepDirForOutput() any other existing directories unless they already
+    # have the right sticky tag:
+    unless ( $state->{globaloptions}{n} )
+    {
+        my $dir;
+        foreach $dir (keys(%{$state->{dirMap}}))
+        {
+            if( ! $seendirs{$dir} &&
+                exists($state->{dirArgs}{$dir}) )
+            {
+                my($oldTag);
+                $oldTag=$state->{dirMap}{$dir}{tagspec};
+
+                unless( ( exists($state->{opt}{A}) &&
+                          defined($oldTag) ) ||
+                          ( defined($state->{opt}{r}) &&
+                            ( !defined($oldTag) ||
+                              $state->{opt}{r} ne $oldTag ) ) )
+                        # TODO?: OR sticky dir is different...
+                {
+                    next;
+                }
+
+                prepDirForOutput(
+                        $dir,
+                        $repoDir,
+                        ".",
+                        \%seendirs,
+                        'update',
+                        $state->{dirArgs} );
+            }
+
+            # TODO?: Consider sending a final duplicate Sticky response
+            #   to more closely mimic real CVS.
+        }
+    }
+
     print "ok\n";
 }
 
@@ -1342,23 +1541,11 @@ sub req_ci
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
     $updater->update();
 
-    # Remember where the head was at the beginning.
-    my $parenthash = `git show-ref -s refs/heads/$state->{module}`;
-    chomp $parenthash;
-    if ($parenthash !~ /^[0-9a-f]{40}$/) {
-           print "error 1 pserver cannot find the current HEAD of module";
-           cleanupWorkTree();
-           exit;
-    }
-
-    setupWorkTree($parenthash);
-
-    $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'");
-
-    $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?");
-
     my @committedfiles = ();
     my %oldmeta;
+    my $stickyInfo;
+    my $branchRef;
+    my $parenthash;
 
     # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
@@ -1368,7 +1555,67 @@ sub req_ci
 
         next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
 
-        my $meta = $updater->getmeta($filename);
+        #####
+        # Figure out which branch and parenthash we are committing
+        # to, and setup worktree:
+
+        # should always come from entries:
+        my $fileStickyInfo = resolveStickyInfo($filename);
+        if( !defined($branchRef) )
+        {
+            $stickyInfo = $fileStickyInfo;
+            if( defined($stickyInfo) &&
+                ( defined($stickyInfo->{date}) ||
+                  !defined($stickyInfo->{tag}) ) )
+            {
+                print "error 1 cannot commit with sticky date for file `$filename'\n";
+                cleanupWorkTree();
+                exit;
+            }
+
+            $branchRef = "refs/heads/$state->{module}";
+            if ( defined($stickyInfo) && defined($stickyInfo->{tag}) )
+            {
+                $branchRef = "refs/heads/$stickyInfo->{tag}";
+            }
+
+            $parenthash = `git show-ref -s $branchRef`;
+            chomp $parenthash;
+            if ($parenthash !~ /^[0-9a-f]{40}$/)
+            {
+                if ( defined($stickyInfo) && defined($stickyInfo->{tag}) )
+                {
+                    print "error 1 sticky tag `$stickyInfo->{tag}' for file `$filename' is not a branch\n";
+                }
+                else
+                {
+                    print "error 1 pserver cannot find the current HEAD of module";
+                }
+                cleanupWorkTree();
+                exit;
+            }
+
+            setupWorkTree($parenthash);
+
+            $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'");
+
+            $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?");
+        }
+        elsif( !refHashEqual($stickyInfo,$fileStickyInfo) )
+        {
+            #TODO: We could split the cvs commit into multiple
+            #  git commits by distinct stickyTag values, but that
+            #  is lowish priority.
+            print "error 1 Committing different files to different"
+                  . " branches is not currently supported\n";
+            cleanupWorkTree();
+            exit;
+        }
+
+        #####
+        # Process this file:
+
+        my $meta = $updater->getmeta($filename,$stickyInfo);
        $oldmeta{$filename} = $meta;
 
         my $wrev = revparse($filename);
@@ -1470,7 +1717,7 @@ sub req_ci
     }
 
        ### Emulate git-receive-pack by running hooks/update
-       my @hook = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}",
+       my @hook = ( $ENV{GIT_DIR}.'hooks/update', $branchRef,
                        $parenthash, $commithash );
        if( -x $hook[0] ) {
                unless( system( @hook ) == 0 )
@@ -1484,7 +1731,7 @@ sub req_ci
 
        ### Update the ref
        if (system(qw(git update-ref -m), "cvsserver ci",
-                       "refs/heads/$state->{module}", $commithash, $parenthash)) {
+                       $branchRef, $commithash, $parenthash)) {
                $log->warn("update-ref for $state->{module} failed.");
                print "error 1 Cannot commit -- update first\n";
                cleanupWorkTree();
@@ -1498,7 +1745,7 @@ sub req_ci
 
                local $SIG{PIPE} = sub { die 'pipe broke' };
 
-               print $pipe "$parenthash $commithash refs/heads/$state->{module}\n";
+               print $pipe "$parenthash $commithash $branchRef\n";
 
                close $pipe || die "bad pipe: $! $?";
        }
@@ -1508,7 +1755,7 @@ sub req_ci
        ### Then hooks/post-update
        $hook = $ENV{GIT_DIR}.'hooks/post-update';
        if (-x $hook) {
-               system($hook, "refs/heads/$state->{module}");
+               system($hook, $branchRef);
        }
 
     # foreach file specified on the command line ...
@@ -1516,7 +1763,7 @@ sub req_ci
     {
         $filename = filecleanup($filename);
 
-        my $meta = $updater->getmeta($filename);
+        my $meta = $updater->getmeta($filename,$stickyInfo);
        unless (defined $meta->{revision}) {
          $meta->{revision} = "1.1";
        }
@@ -1540,7 +1787,8 @@ sub req_ci
             print "Checked-in $dirpart\n";
             print "$filename\n";
             my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
-            print "/$filepart/$meta->{revision}//$kopts/\n";
+            print "/$filepart/$meta->{revision}//$kopts/" .
+                  getStickyTagOrDate($stickyInfo) . "\n";
         }
     }
 
@@ -1577,16 +1825,19 @@ sub req_status
            next;
         }
 
-        my $meta = $updater->getmeta($filename);
-        my $oldmeta = $meta;
-
         my $wrev = revparse($filename);
 
+        my $stickyInfo = resolveStickyInfo($filename);
+        my $meta = $updater->getmeta($filename,$stickyInfo);
+        my $oldmeta = $meta;
+
         # If the working copy is an old revision, lets get that
         # version too for comparison.
         if ( defined($wrev) and $wrev ne $meta->{revision} )
         {
-            $oldmeta = $updater->getmeta($filename, $wrev);
+            my($rmRev)=$wrev;
+            $rmRev=~s/^-//;
+            $oldmeta = $updater->getmeta($filename, $rmRev);
         }
 
         # TODO : All possible statuses aren't yet implemented
@@ -1629,6 +1880,7 @@ sub req_status
         # same revision but there are local changes
         if ( defined ( $wrev ) and defined($meta->{revision}) and
              $wrev eq $meta->{revision} and
+             $wrev ne "0" and
              $state->{entries}{$filename}{modified_filename} )
         {
             $status ||= "Locally Modified";
@@ -1644,7 +1896,8 @@ sub req_status
         }
 
         if ( defined ( $state->{entries}{$filename}{revision} ) and
-             not defined ( $meta->{revision} ) )
+             ( !defined($meta->{revision}) ||
+               $meta->{revision} eq "0" ) )
         {
             $status ||= "Locally Added";
         }
@@ -1740,98 +1993,133 @@ sub req_diff
     # be providing status on ...
     argsfromdir($updater);
 
+    my($foundDiff);
+
     # foreach file specified on the command line ...
-    foreach my $filename ( @{$state->{args}} )
+    foreach my $argFilename ( @{$state->{args}} )
     {
-        $filename = filecleanup($filename);
+        my($filename) = filecleanup($argFilename);
 
         my ( $fh, $file1, $file2, $meta1, $meta2, $filediff );
 
         my $wrev = revparse($filename);
 
-        # We need _something_ to diff against
-        next unless ( defined ( $wrev ) );
+        # Priority for revision1:
+        #  1. First -r (missing file: check -N)
+        #  2. wrev from client's Entry line
+        #      - missing line/file: check -N
+        #      - "0": added file not committed (empty contents for rev1)
+        #      - Prefixed with dash (to be removed): check -N
 
-        # if we have a -r switch, use it
         if ( defined ( $revision1 ) )
         {
-            ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
             $meta1 = $updater->getmeta($filename, $revision1);
-            unless ( defined ( $meta1 ) and $meta1->{filehash} ne "deleted" )
+        }
+        elsif( defined($wrev) && $wrev ne "0" )
+        {
+            my($rmRev)=$wrev;
+            $rmRev=~s/^-//;
+            $meta1 = $updater->getmeta($filename, $rmRev);
+        }
+        if ( !defined($meta1) ||
+             $meta1->{filehash} eq "deleted" )
+        {
+            if( !exists($state->{opt}{N}) )
             {
-                print "E File $filename at revision $revision1 doesn't exist\n";
+                if(!defined($revision1))
+                {
+                    print "E File $filename at revision $revision1 doesn't exist\n";
+                }
                 next;
             }
-            transmitfile($meta1->{filehash}, { targetfile => $file1 });
-        }
-        # otherwise we just use the working copy revision
-        else
-        {
-            ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
-            $meta1 = $updater->getmeta($filename, $wrev);
-            transmitfile($meta1->{filehash}, { targetfile => $file1 });
+            elsif( !defined($meta1) )
+            {
+                $meta1 = {
+                    name => $filename,
+                    revision => '0',
+                    filehash => 'deleted'
+                };
+            }
         }
 
+        # Priority for revision2:
+        #  1. Second -r (missing file: check -N)
+        #  2. Modified file contents from client
+        #  3. wrev from client's Entry line
+        #      - missing line/file: check -N
+        #      - Prefixed with dash (to be removed): check -N
+
         # if we have a second -r switch, use it too
         if ( defined ( $revision2 ) )
         {
-            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
             $meta2 = $updater->getmeta($filename, $revision2);
-
-            unless ( defined ( $meta2 ) and $meta2->{filehash} ne "deleted" )
-            {
-                print "E File $filename at revision $revision2 doesn't exist\n";
-                next;
-            }
-
-            transmitfile($meta2->{filehash}, { targetfile => $file2 });
         }
-        # otherwise we just use the working copy
-        else
+        elsif(defined($state->{entries}{$filename}{modified_filename}))
         {
             $file2 = $state->{entries}{$filename}{modified_filename};
+           $meta2 = {
+                name => $filename,
+               revision => '0',
+               filehash => 'modified'
+            };
         }
-
-        # if we have been given -r, and we don't have a $file2 yet, lets
-        # get one
-        if ( defined ( $revision1 ) and not defined ( $file2 ) )
+        elsif( defined($wrev) && ($wrev!~/^-/) )
         {
-            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            if(!defined($revision1))  # no revision and no modifications:
+            {
+                next;
+            }
             $meta2 = $updater->getmeta($filename, $wrev);
-            transmitfile($meta2->{filehash}, { targetfile => $file2 });
+        }
+        if(!defined($file2))
+        {
+            if ( !defined($meta2) ||
+                 $meta2->{filehash} eq "deleted" )
+            {
+                if( !exists($state->{opt}{N}) )
+                {
+                    if(!defined($revision2))
+                    {
+                        print "E File $filename at revision $revision2 doesn't exist\n";
+                    }
+                    next;
+                }
+                elsif( !defined($meta2) )
+                {
+                   $meta2 = {
+                        name => $filename,
+                       revision => '0',
+                       filehash => 'deleted'
+                    };
+                }
+            }
         }
 
-        # We need to have retrieved something useful
-        next unless ( defined ( $meta1 ) );
-
-        # Files to date if the working copy and repo copy have the same
-        # revision, and the working copy is unmodified
-        if ( not defined ( $meta2 ) and $wrev eq $meta1->{revision} and
-             ( ( $state->{entries}{$filename}{unchanged} and
-                 ( not defined ( $state->{entries}{$filename}{conflict} ) or
-                   $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) or
-               ( defined($state->{entries}{$filename}{modified_hash}) and
-                 $state->{entries}{$filename}{modified_hash} eq
-                        $meta1->{filehash} ) ) )
+        if( $meta1->{filehash} eq $meta2->{filehash} )
         {
+            $log->info("unchanged $filename");
             next;
         }
 
-        # Apparently we only show diffs for locally modified files
-        unless ( defined($meta2) or
-                 defined ( $state->{entries}{$filename}{modified_filename} ) )
+        # Retrieve revision contents:
+        ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+        transmitfile($meta1->{filehash}, { targetfile => $file1 });
+
+        if(!defined($file2))
         {
-            next;
+            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            transmitfile($meta2->{filehash}, { targetfile => $file2 });
         }
 
-        print "M Index: $filename\n";
+        # Generate the actual diff:
+        print "M Index: $argFilename\n";
         print "M =======" . ( "=" x 60 ) . "\n";
         print "M RCS file: $state->{CVSROOT}/$state->{module}/$filename,v\n";
-        if ( defined ( $meta1 ) )
+        if ( defined ( $meta1 ) && $meta1->{revision} ne "0" )
         {
             print "M retrieving revision $meta1->{revision}\n"
         }
-        if ( defined ( $meta2 ) )
+        if ( defined ( $meta2 ) && $meta2->{revision} ne "0" )
         {
             print "M retrieving revision $meta2->{revision}\n"
         }
@@ -1852,33 +2140,73 @@ sub req_diff
                 }
             }
         }
-        print "$filename\n";
+        print "$argFilename\n";
 
         $log->info("Diffing $filename -r $meta1->{revision} -r " .
                    ( $meta2->{revision} or "workingcopy" ));
 
-        ( $fh, $filediff ) = tempfile ( DIR => $TEMP_DIR );
-
-        if ( exists $state->{opt}{u} )
+        # TODO: Use --label instead of -L because -L is no longer
+        #  documented and may go away someday.  Not sure if there there are
+        #  versions that only support -L, which would make this change risky?
+        #  http://osdir.com/ml/bug-gnu-utils-gnu/2010-12/msg00060.html
+        #    ("man diff" should actually document the best migration strategy,
+        #  [current behavior, future changes, old compatibility issues
+        #  or lack thereof, etc], not just stop mentioning the option...)
+        # TODO: Real CVS seems to include a date in the label, before
+        #  the revision part, without the keyword "revision".  The following
+        #  has minimal changes compared to original versions of
+        #  git-cvsserver.perl.  (Mostly tab vs space after filename.)
+
+        my (@diffCmd) = ( 'diff' );
+        if ( exists($state->{opt}{N}) )
         {
-            system("diff -u -L '$filename revision $meta1->{revision}'" .
-                        " -L '$filename " .
-                        ( defined($meta2->{revision}) ?
-                                "revision $meta2->{revision}" :
-                                "working copy" ) .
-                        "' $file1 $file2 > $filediff" );
-        } else {
-            system("diff $file1 $file2 > $filediff");
+            push @diffCmd,"-N";
         }
+        if ( exists $state->{opt}{u} )
+        {
+            push @diffCmd,("-u","-L");
+            if( $meta1->{filehash} eq "deleted" )
+            {
+                push @diffCmd,"/dev/null";
+            } else {
+                push @diffCmd,("$argFilename\trevision $meta1->{revision}");
+            }
 
-        while ( <$fh> )
+            if( defined($meta2->{filehash}) )
+            {
+                if( $meta2->{filehash} eq "deleted" )
+                {
+                    push @diffCmd,("-L","/dev/null");
+                } else {
+                    push @diffCmd,("-L",
+                                   "$argFilename\trevision $meta2->{revision}");
+                }
+            } else {
+                push @diffCmd,("-L","$argFilename\tworking copy");
+            }
+        }
+        push @diffCmd,($file1,$file2);
+        if(!open(DIFF,"-|",@diffCmd))
         {
-            print "M $_";
+            $log->warn("Unable to run diff: $!");
         }
-        close $fh;
+        my($diffLine);
+        while(defined($diffLine=<DIFF>))
+        {
+            print "M $diffLine";
+            $foundDiff=1;
+        }
+        close(DIFF);
     }
 
-    print "ok\n";
+    if($foundDiff)
+    {
+        print "error  \n";
+    }
+    else
+    {
+        print "ok\n";
+    }
 }
 
 sub req_log
@@ -2098,7 +2426,7 @@ sub argsplit
         $opt = { A => 0, N => 0, P => 0, R => 0, c => 0, f => 0, l => 0, n => 0, p => 0, s => 0, r => 1, D => 1, d => 1, k => 1, j => 1, } if ( $type eq "co" );
         $opt = { v => 0, l => 0, R => 0 } if ( $type eq "status" );
         $opt = { A => 0, P => 0, C => 0, d => 0, f => 0, l => 0, R => 0, p => 0, k => 1, r => 1, D => 1, j => 1, I => 1, W => 1 } if ( $type eq "update" );
-        $opt = { l => 0, R => 0, k => 1, D => 1, D => 1, r => 2 } if ( $type eq "diff" );
+        $opt = { l => 0, R => 0, k => 1, D => 1, D => 1, r => 2, N => 0 } if ( $type eq "diff" );
         $opt = { c => 0, R => 0, l => 0, f => 0, F => 1, m => 1, r => 1 } if ( $type eq "ci" );
         $opt = { k => 1, m => 1 } if ( $type eq "add" );
         $opt = { f => 0, l => 0, R => 0 } if ( $type eq "remove" );
@@ -2164,62 +2492,335 @@ sub argsplit
     }
 }
 
-# This method uses $state->{directory} to populate $state->{args} with a list of filenames
-sub argsfromdir
+# Used by argsfromdir
+sub expandArg
 {
-    my $updater = shift;
-
-    $state->{args} = [] if ( scalar(@{$state->{args}}) == 1 and $state->{args}[0] eq "." );
+    my ($updater,$outNameMap,$outDirMap,$path,$isDir) = @_;
 
-    return if ( scalar ( @{$state->{args}} ) > 1 );
+    my $fullPath = filecleanup($path);
 
-    my @gethead = @{$updater->gethead};
+      # Is it a directory?
+    if( defined($state->{dirMap}{$fullPath}) ||
+        defined($state->{dirMap}{"$fullPath/"}) )
+    {
+          # It is a directory in the user's sandbox.
+        $isDir=1;
 
-    # push added files
-    foreach my $file (keys %{$state->{entries}}) {
-       if ( exists $state->{entries}{$file}{revision} &&
-               $state->{entries}{$file}{revision} eq '0' )
-       {
-           push @gethead, { name => $file, filehash => 'added' };
-       }
+        if(defined($state->{entries}{$fullPath}))
+        {
+            $log->fatal("Inconsistent file/dir type");
+            die "Inconsistent file/dir type";
+        }
     }
-
-    if ( scalar(@{$state->{args}}) == 1 )
+    elsif(defined($state->{entries}{$fullPath}))
     {
-        my $arg = $state->{args}[0];
-        $arg .= $state->{prependdir} if ( defined ( $state->{prependdir} ) );
-
-        $log->info("Only one arg specified, checking for directory expansion on '$arg'");
-
-        foreach my $file ( @gethead )
+          # It is a file in the user's sandbox.
+        $isDir=0;
+    }
+    my($revDirMap,$otherRevDirMap);
+    if(!defined($isDir) || $isDir)
+    {
+          # Resolve version tree for sticky tag:
+          # (for now we only want list of files for the version, not
+          # particular versions of those files: assume it is a directory
+          # for the moment; ignore Entry's stick tag)
+
+          # Order of precedence of sticky tags:
+          #    -A       [head]
+          #    -r /tag/
+          #    [file entry sticky tag, but that is only relevant to files]
+          #    [the tag specified in dir req_Sticky]
+          #    [the tag specified in a parent dir req_Sticky]
+          #    [head]
+          # Also, -r may appear twice (for diff).
+          #
+          # FUTURE: When/if -j (merges) are supported, we also
+          #  need to add relevant files from one or two
+          #  versions specified with -j.
+
+        if(exists($state->{opt}{A}))
         {
-            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
-            next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg  );
-            push @{$state->{args}}, $file->{name};
+            $revDirMap=$updater->getRevisionDirMap();
         }
-
-        shift @{$state->{args}} if ( scalar(@{$state->{args}}) > 1 );
-    } else {
-        $log->info("Only one arg specified, populating file list automatically");
-
-        $state->{args} = [];
-
-        foreach my $file ( @gethead )
+        elsif( defined($state->{opt}{r}) and
+               ref $state->{opt}{r} eq "ARRAY" )
         {
-            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
-            next unless ( $file->{name} =~ s/^$state->{prependdir}// );
-            push @{$state->{args}}, $file->{name};
+            $revDirMap=$updater->getRevisionDirMap($state->{opt}{r}[0]);
+            $otherRevDirMap=$updater->getRevisionDirMap($state->{opt}{r}[1]);
         }
-    }
-}
-
-# This method cleans up the $state variable after a command that uses arguments has run
+        elsif(defined($state->{opt}{r}))
+        {
+            $revDirMap=$updater->getRevisionDirMap($state->{opt}{r});
+        }
+        else
+        {
+            my($sticky)=getDirStickyInfo($fullPath);
+            $revDirMap=$updater->getRevisionDirMap($sticky->{tag});
+        }
+
+          # Is it a directory?
+        if( defined($revDirMap->{$fullPath}) ||
+            defined($otherRevDirMap->{$fullPath}) )
+        {
+            $isDir=1;
+        }
+    }
+
+      # What to do with it?
+    if(!$isDir)
+    {
+        $outNameMap->{$fullPath}=1;
+    }
+    else
+    {
+        $outDirMap->{$fullPath}=1;
+
+        if(defined($revDirMap->{$fullPath}))
+        {
+            addDirMapFiles($updater,$outNameMap,$outDirMap,
+                           $revDirMap->{$fullPath});
+        }
+        if( defined($otherRevDirMap) &&
+            defined($otherRevDirMap->{$fullPath}) )
+        {
+            addDirMapFiles($updater,$outNameMap,$outDirMap,
+                           $otherRevDirMap->{$fullPath});
+        }
+    }
+}
+
+# Used by argsfromdir
+# Add entries from dirMap to outNameMap.  Also recurse into entries
+# that are subdirectories.
+sub addDirMapFiles
+{
+    my($updater,$outNameMap,$outDirMap,$dirMap)=@_;
+
+    my($fullName);
+    foreach $fullName (keys(%$dirMap))
+    {
+        my $cleanName=$fullName;
+        if(defined($state->{prependdir}))
+        {
+            if(!($cleanName=~s/^\Q$state->{prependdir}\E//))
+            {
+                $log->fatal("internal error stripping prependdir");
+                die "internal error stripping prependdir";
+            }
+        }
+
+        if($dirMap->{$fullName} eq "F")
+        {
+            $outNameMap->{$cleanName}=1;
+        }
+        elsif($dirMap->{$fullName} eq "D")
+        {
+            if(!$state->{opt}{l})
+            {
+                expandArg($updater,$outNameMap,$outDirMap,$cleanName,1);
+            }
+        }
+        else
+        {
+            $log->fatal("internal error in addDirMapFiles");
+            die "internal error in addDirMapFiles";
+        }
+    }
+}
+
+# This method replaces $state->{args} with a directory-expanded
+# list of all relevant filenames (recursively unless -d), based
+# on $state->{entries}, and the "current" list of files in
+# each directory.  "Current" files as determined by
+# either the requested (-r/-A) or "req_Sticky" version of
+# that directory.
+#    Both the input args and the new output args are relative
+# to the cvs-client's CWD, although some of the internal
+# computations are relative to the top of the project.
+sub argsfromdir
+{
+    my $updater = shift;
+
+    # Notes about requirements for specific callers:
+    #   update # "standard" case (entries; a single -r/-A/default; -l)
+    #          # Special case: -d for create missing directories.
+    #   diff # 0 or 1 -r's: "standard" case.
+    #        # 2 -r's: We could ignore entries (just use the two -r's),
+    #        # but it doesn't really matter.
+    #   annotate # "standard" case
+    #   log # Punting: log -r has a more complex non-"standard"
+    #       # meaning, and we don't currently try to support log'ing
+    #       # branches at all (need a lot of work to
+    #       # support CVS-consistent branch relative version
+    #       # numbering).
+#HERE: But we still want to expand directories.  Maybe we should
+#  essentially force "-A".
+    #   status # "standard", except that -r/-A/default are not possible.
+    #          # Mostly only used to expand entries only)
+    #
+    # Don't use argsfromdir at all:
+    #   add # Explicit arguments required.  Directory args imply add
+    #       # the directory itself, not the files in it.
+    #   co  # Obtain list directly.
+    #   remove # HERE: TEST: MAYBE client does the recursion for us,
+    #          # since it only makes sense to remove stuff already in
+    #          # the sandobx?
+    #   ci # HERE: Similar to remove...
+    #      # Don't try to implement the confusing/weird
+    #      # ci -r bug er.."feature".
+
+    if(scalar(@{$state->{args}})==0)
+    {
+        $state->{args} = [ "." ];
+    }
+    my %allArgs;
+    my %allDirs;
+    for my $file (@{$state->{args}})
+    {
+        expandArg($updater,\%allArgs,\%allDirs,$file);
+    }
+
+    # Include any entries from sandbox.  Generally client won't
+    # send entries that shouldn't be used.
+    foreach my $file (keys %{$state->{entries}})
+    {
+        $allArgs{remove_prependdir($file)} = 1;
+    }
+
+    $state->{dirArgs} = \%allDirs;
+    $state->{args} = [
+        sort {
+                # Sort priority: by directory depth, then actual file name:
+            my @piecesA=split('/',$a);
+            my @piecesB=split('/',$b);
+
+            my $count=scalar(@piecesA);
+            my $tmp=scalar(@piecesB);
+            return $count<=>$tmp if($count!=$tmp);
+
+            for($tmp=0;$tmp<$count;$tmp++)
+            {
+                if($piecesA[$tmp] ne $piecesB[$tmp])
+                {
+                    return $piecesA[$tmp] cmp $piecesB[$tmp]
+                }
+            }
+            return 0;
+        } keys(%allArgs) ];
+}
+
+## look up directory sticky tag, of either fullPath or a parent:
+sub getDirStickyInfo
+{
+    my($fullPath)=@_;
+
+    $fullPath=~s%/+$%%;
+    while($fullPath ne "" && !defined($state->{dirMap}{"$fullPath/"}))
+    {
+        $fullPath=~s%/?[^/]*$%%;
+    }
+
+    if( !defined($state->{dirMap}{"$fullPath/"}) &&
+        ( $fullPath eq "" ||
+          $fullPath eq "." ) )
+    {
+        return $state->{dirMap}{""}{stickyInfo};
+    }
+    else
+    {
+        return $state->{dirMap}{"$fullPath/"}{stickyInfo};
+    }
+}
+
+# Resolve precedence of various ways of specifying which version of
+# a file you want.  Returns undef (for default head), or a ref to a hash
+# that contains "tag" and/or "date" keys.
+sub resolveStickyInfo
+{
+    my($filename,$stickyTag,$stickyDate,$reset) = @_;
+
+    # Order of precedence of sticky tags:
+    #    -A       [head]
+    #    -r /tag/
+    #    [file entry sticky tag]
+    #    [the tag specified in dir req_Sticky]
+    #    [the tag specified in a parent dir req_Sticky]
+    #    [head]
+
+    my $result;
+    if($reset)
+    {
+        # $result=undef;
+    }
+    elsif( defined($stickyTag) && $stickyTag ne "" )
+           # || ( defined($stickyDate) && $stickyDate ne "" )   # TODO
+    {
+        $result={ 'tag' => (defined($stickyTag)?$stickyTag:undef) };
+
+        # TODO: Convert -D value into the form 2011.04.10.04.46.57,
+        #   similar to an entry line's sticky date, without the D prefix.
+        #   It sometimes (always?) arrives as something more like
+        #   '10 Apr 2011 04:46:57 -0000'...
+        # $result={ 'date' => (defined($stickyDate)?$stickyDate:undef) };
+    }
+    elsif( defined($state->{entries}{$filename}) &&
+           defined($state->{entries}{$filename}{tag_or_date}) &&
+           $state->{entries}{$filename}{tag_or_date} ne "" )
+    {
+        my($tagOrDate)=$state->{entries}{$filename}{tag_or_date};
+        if($tagOrDate=~/^T([^ ]+)\s*$/)
+        {
+            $result = { 'tag' => $1 };
+        }
+        elsif($tagOrDate=~/^D([0-9.]+)\s*$/)
+        {
+            $result= { 'date' => $1 };
+        }
+        else
+        {
+            die "Unknown tag_or_date format\n";
+        }
+    }
+    else
+    {
+        $result=getDirStickyInfo($filename);
+    }
+
+    return $result;
+}
+
+# Convert a stickyInfo (ref to a hash) as returned by resolveStickyInfo into
+# a form appropriate for the sticky tag field of an Entries
+# line (field index 5, 0-based).
+sub getStickyTagOrDate
+{
+    my($stickyInfo)=@_;
+
+    my $result;
+    if(defined($stickyInfo) && defined($stickyInfo->{tag}))
+    {
+        $result="T$stickyInfo->{tag}";
+    }
+    # TODO: When/if we actually pick versions by {date} properly,
+    #   also handle it here:
+    #   "D$stickyInfo->{date}" (example: "D2011.04.13.20.37.07").
+    else
+    {
+        $result="";
+    }
+
+    return $result;
+}
+
+# This method cleans up the $state variable after a command that uses arguments has run
 sub statecleanup
 {
     $state->{files} = [];
+    $state->{dirArgs} = {};
     $state->{args} = [];
     $state->{arguments} = [];
     $state->{entries} = {};
+    $state->{dirMap} = {};
 }
 
 # Return working directory CVS revision "1.X" out
@@ -2309,6 +2910,9 @@ sub filenamesplit
     return ( $filepart, $dirpart );
 }
 
+# Cleanup various junk in filename (try to canonicalize it), and
+# add prependdir to accomodate running CVS client from a
+# subdirectory (so the output is relative to top directory of the project).
 sub filecleanup
 {
     my $filename = shift;
@@ -2320,11 +2924,36 @@ sub filecleanup
         return undef;
     }
 
+    if($filename eq ".")
+    {
+        $filename="";
+    }
     $filename =~ s/^\.\///g;
+    $filename =~ s%/+%/%g;
     $filename = $state->{prependdir} . $filename;
+    $filename =~ s%/$%%;
     return $filename;
 }
 
+# Remove prependdir from the path, so that is is relative to the directory
+# the CVS client was started from, rather than the top of the project.
+# Essentially the inverse of filecleanup().
+sub remove_prependdir
+{
+    my($path) = @_;
+    if(defined($state->{prependdir}) && $state->{prependdir} ne "")
+    {
+        my($pre)=$state->{prependdir};
+        $pre=~s%/$%%;
+        if(!($path=~s%^\Q$pre\E/?%%))
+        {
+            $log->fatal("internal error missing prependdir");
+            die("internal error missing prependdir");
+        }
+    }
+    return $path;
+}
+
 sub validateGitDir
 {
     if( !defined($state->{CVSROOT}) )
@@ -2738,6 +3367,45 @@ sub descramble
     return $ret;
 }
 
+# Test if the (deep) values of two references to a hash are the same.
+sub refHashEqual
+{
+    my($v1,$v2) = @_;
+
+    my $out;
+    if(!defined($v1))
+    {
+        if(!defined($v2))
+        {
+            $out=1;
+        }
+    }
+    elsif( !defined($v2) ||
+           scalar(keys(%{$v1})) != scalar(keys(%{$v2})) )
+    {
+        # $out=undef;
+    }
+    else
+    {
+        $out=1;
+
+        my $key;
+        foreach $key (keys(%{$v1}))
+        {
+            if( !exists($v2->{$key}) ||
+                defined($v1->{$key}) ne defined($v2->{$key}) ||
+                ( defined($v1->{$key}) &&
+                  $v1->{$key} ne $v2->{$key} ) )
+            {
+               $out=undef;
+               last;
+            }
+        }
+    }
+
+    return $out;
+}
+
 
 package GITCVS::log;
 
@@ -2958,6 +3626,9 @@ sub new
 
     die "Git repo '$self->{git_path}' doesn't exist" unless ( -d $self->{git_path} );
 
+    # Stores full sha1's for various branch/tag names, abbreviations, etc:
+    $self->{commitRefCache} = {};
+
     $self->{dbdriver} = $cfg->{gitcvs}{$state->{method}}{dbdriver} ||
         $cfg->{gitcvs}{dbdriver} || "SQLite";
     $self->{dbname} = $cfg->{gitcvs}{$state->{method}}{dbname} ||
@@ -3140,6 +3811,8 @@ sub update
     my $lastcommit = $self->_get_prop("last_commit");
 
     if (defined $lastcommit && $lastcommit eq $commitsha1) { # up-to-date
+         # invalidate the gethead cache
+         $self->clearCommitRefCaches();
          return 1;
     }
 
@@ -3157,45 +3830,10 @@ sub update
         push @git_log_params, $self->{module};
     }
     # git-rev-list is the backend / plumbing version of git-log
-    open(GITLOG, '-|', 'git', 'rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
-
-    my @commits;
-
-    my %commit = ();
-
-    while ( <GITLOG> )
-    {
-        chomp;
-        if (m/^commit\s+(.*)$/) {
-            # on ^commit lines put the just seen commit in the stack
-            # and prime things for the next one
-            if (keys %commit) {
-                my %copy = %commit;
-                unshift @commits, \%copy;
-                %commit = ();
-            }
-            my @parents = split(m/\s+/, $1);
-            $commit{hash} = shift @parents;
-            $commit{parents} = \@parents;
-        } elsif (m/^(\w+?):\s+(.*)$/ && !exists($commit{message})) {
-            # on rfc822-like lines seen before we see any message,
-            # lowercase the entry and put it in the hash as key-value
-            $commit{lc($1)} = $2;
-        } else {
-            # message lines - skip initial empty line
-            # and trim whitespace
-            if (!exists($commit{message}) && m/^\s*$/) {
-                # define it to mark the end of headers
-                $commit{message} = '';
-                next;
-            }
-            s/^\s+//; s/\s+$//; # trim ws
-            $commit{message} .= $_ . "\n";
-        }
-    }
-    close GITLOG;
-
-    unshift @commits, \%commit if ( keys %commit );
+    open(my $gitLogPipe, '-|', 'git', 'rev-list', @git_log_params)
+                or die "Cannot call git-rev-list: $!";
+    my @commits=readCommits($gitLogPipe);
+    close $gitLogPipe;
 
     # Now all the commits are in the @commits bucket
     # ordered by time DESC. for each commit that needs processing,
@@ -3294,7 +3932,7 @@ sub update
         }
 
         # convert the date to CVS-happy format
-        $commit->{date} = "$2 $1 $4 $3 $5" if ( $commit->{date} =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+:\d+:\d+)\s+(\d+)\s+([+-]\d+)$/ );
+        my $cvsDate = convertToCvsDate($commit->{date});
 
         if ( defined ( $lastpicked ) )
         {
@@ -3303,7 +3941,7 @@ sub update
             while ( <FILELIST> )
             {
                chomp;
-                unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)$/o )
+                unless ( /^:\d{6}\s+([0-7]{6})\s+[a-f0-9]{40}\s+([a-f0-9]{40})\s+(\w)$/o )
                 {
                     die("Couldn't process git-diff-tree line : $_");
                 }
@@ -3313,11 +3951,7 @@ sub update
 
                 # $log->debug("File mode=$mode, hash=$hash, change=$change, name=$name");
 
-                my $git_perms = "";
-                $git_perms .= "r" if ( $mode & 4 );
-                $git_perms .= "w" if ( $mode & 2 );
-                $git_perms .= "x" if ( $mode & 1 );
-                $git_perms = "rw" if ( $git_perms eq "" );
+                my $dbMode = convertToDbMode($mode);
 
                 if ( $change eq "D" )
                 {
@@ -3327,11 +3961,11 @@ sub update
                         revision => $head->{$name}{revision} + 1,
                         filehash => "deleted",
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
-                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
                 elsif ( $change eq "M" || $change eq "T" )
                 {
@@ -3341,11 +3975,11 @@ sub update
                         revision => $head->{$name}{revision} + 1,
                         filehash => $hash,
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
-                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
                 elsif ( $change eq "A" )
                 {
@@ -3355,11 +3989,11 @@ sub update
                         revision => $head->{$name}{revision} ? $head->{$name}{revision}+1 : 1,
                         filehash => $hash,
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
-                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
                 else
                 {
@@ -3382,7 +4016,7 @@ sub update
                     die("Couldn't process git-ls-tree line : $_");
                 }
 
-                my ( $git_perms, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
+                my ( $mode, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
 
                 $seen_files->{$git_filename} = 1;
 
@@ -3392,18 +4026,10 @@ sub update
                     $head->{$git_filename}{mode}
                 );
 
-                if ( $git_perms =~ /^\d\d\d(\d)\d\d/o )
-                {
-                    $git_perms = "";
-                    $git_perms .= "r" if ( $1 & 4 );
-                    $git_perms .= "w" if ( $1 & 2 );
-                    $git_perms .= "x" if ( $1 & 1 );
-                } else {
-                    $git_perms = "rw";
-                }
+                my $dbMode = convertToDbMode($mode);
 
                 # unless the file exists with the same hash, we need to update it ...
-                unless ( defined($oldhash) and $oldhash eq $git_hash and defined($oldmode) and $oldmode eq $git_perms )
+                unless ( defined($oldhash) and $oldhash eq $git_hash and defined($oldmode) and $oldmode eq $dbMode )
                 {
                     my $newrevision = ( $oldrevision or 0 ) + 1;
 
@@ -3412,13 +4038,13 @@ sub update
                         revision => $newrevision,
                         filehash => $git_hash,
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
 
 
-                    $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
             }
             close FILELIST;
@@ -3431,10 +4057,10 @@ sub update
                     $head->{$file}{revision}++;
                     $head->{$file}{filehash} = "deleted";
                     $head->{$file}{commithash} = $commit->{hash};
-                    $head->{$file}{modified} = $commit->{date};
+                    $head->{$file}{modified} = $cvsDate;
                     $head->{$file}{author} = $commit->{author};
 
-                    $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
+                    $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $cvsDate, $commit->{author}, $head->{$file}{mode});
                 }
             }
             # END : "Detect deleted files"
@@ -3465,13 +4091,94 @@ sub update
         );
     }
     # invalidate the gethead cache
-    $self->{gethead_cache} = undef;
+    $self->clearCommitRefCaches();
 
 
     # Ending exclusive lock here
     $self->{dbh}->commit() or die "Failed to commit changes to SQLite";
 }
 
+sub readCommits
+{
+    my $pipeHandle = shift;
+    my @commits;
+
+    my %commit = ();
+
+    while ( <$pipeHandle> )
+    {
+        chomp;
+        if (m/^commit\s+(.*)$/) {
+            # on ^commit lines put the just seen commit in the stack
+            # and prime things for the next one
+            if (keys %commit) {
+                my %copy = %commit;
+                unshift @commits, \%copy;
+                %commit = ();
+            }
+            my @parents = split(m/\s+/, $1);
+            $commit{hash} = shift @parents;
+            $commit{parents} = \@parents;
+        } elsif (m/^(\w+?):\s+(.*)$/ && !exists($commit{message})) {
+            # on rfc822-like lines seen before we see any message,
+            # lowercase the entry and put it in the hash as key-value
+            $commit{lc($1)} = $2;
+        } else {
+            # message lines - skip initial empty line
+            # and trim whitespace
+            if (!exists($commit{message}) && m/^\s*$/) {
+                # define it to mark the end of headers
+                $commit{message} = '';
+                next;
+            }
+            s/^\s+//; s/\s+$//; # trim ws
+            $commit{message} .= $_ . "\n";
+        }
+    }
+
+    unshift @commits, \%commit if ( keys %commit );
+
+    return @commits;
+}
+
+sub convertToCvsDate
+{
+    my $date = shift;
+    # Convert from: "git rev-list --pretty" formatted date
+    # Convert to: "the format specified by RFC822 as modified by RFC1123."
+    # Example: 26 May 1997 13:01:40 -0400
+    if( $date =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+:\d+:\d+)\s+(\d+)\s+([+-]\d+)$/ )
+    {
+        $date = "$2 $1 $4 $3 $5";
+    }
+
+    return $date;
+}
+
+sub convertToDbMode
+{
+    my $mode = shift;
+
+    # NOTE: The CVS protocol uses a string similar "u=rw,g=rw,o=rw",
+    #  but the database "mode" column historically (and currently)
+    #  only stores the "rw" (for user) part of the string.
+    #    FUTURE: It might make more sense to persist the raw
+    #  octal mode (or perhaps the final full CVS form) instead of
+    #  this half-converted form, but it isn't currently worth the
+    #  backwards compatibility headaches.
+
+    $mode=~/^\d\d(\d)\d{3}$/;
+    my $userBits=$1;
+
+    my $dbMode = "";
+    $dbMode .= "r" if ( $userBits & 4 );
+    $dbMode .= "w" if ( $userBits & 2 );
+    $dbMode .= "x" if ( $userBits & 1 );
+    $dbMode = "rw" if ( $dbMode eq "" );
+
+    return $dbMode;
+}
+
 sub insert_rev
 {
     my $self = shift;
@@ -3586,6 +4293,169 @@ sub gethead
     return $tree;
 }
 
+=head2 getAnyHead
+
+Returns a reference to an array of getmeta structures, one
+per file in the specified tree hash.
+
+=cut
+
+sub getAnyHead
+{
+    my ($self,$hash) = @_;
+
+    if(!defined($hash))
+    {
+        return $self->gethead();
+    }
+
+    my @files;
+    {
+        open(my $filePipe, '-|', 'git', 'ls-tree', '-z', '-r', $hash)
+                or die("Cannot call git-ls-tree : $!");
+        local $/ = "\0";
+        @files=<$filePipe>;
+        close $filePipe;
+    }
+
+    my $tree=[];
+    my($line);
+    foreach $line (@files)
+    {
+        $line=~s/\0$//;
+        unless ( $line=~/^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
+        {
+            die("Couldn't process git-ls-tree line : $_");
+        }
+
+        my($mode, $git_type, $git_hash, $git_filename) = ($1, $2, $3, $4);
+        push @$tree, $self->getMetaFromCommithash($git_filename,$hash);
+    }
+
+    return $tree;
+}
+
+=head2 getRevisionDirMap
+
+A "revision dir map" contains all the plain-file filenames associated
+with a particular revision (treeish), organized by directory:
+
+  $type = $out->{$dir}{$fullName}
+
+The type of each is "F" (for ordinary file) or "D" (for directory,
+for which the map $out->{$fullName} will also exist).
+
+=cut
+
+sub getRevisionDirMap
+{
+    my ($self,$ver)=@_;
+
+    if(!defined($self->{revisionDirMapCache}))
+    {
+        $self->{revisionDirMapCache}={};
+    }
+
+        # Get file list (previously cached results are dependent on HEAD,
+        # but are early in each case):
+    my $cacheKey;
+    my (@fileList);
+    if( !defined($ver) || $ver eq "" )
+    {
+        $cacheKey="";
+        if( defined($self->{revisionDirMapCache}{$cacheKey}) )
+        {
+            return $self->{revisionDirMapCache}{$cacheKey};
+        }
+
+        my @head = @{$self->gethead()};
+        foreach my $file ( @head )
+        {
+            next if ( $file->{filehash} eq "deleted" );
+
+            push @fileList,$file->{name};
+        }
+    }
+    else
+    {
+        my ($hash)=$self->lookupCommitRef($ver);
+        if( !defined($hash) )
+        {
+            return undef;
+        }
+
+        $cacheKey=$hash;
+        if( defined($self->{revisionDirMapCache}{$cacheKey}) )
+        {
+            return $self->{revisionDirMapCache}{$cacheKey};
+        }
+
+        open(my $filePipe, '-|', 'git', 'ls-tree', '-z', '-r', $hash)
+                or die("Cannot call git-ls-tree : $!");
+        local $/ = "\0";
+        while ( <$filePipe> )
+        {
+            chomp;
+            unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
+            {
+                die("Couldn't process git-ls-tree line : $_");
+            }
+
+            my($mode, $git_type, $git_hash, $git_filename) = ($1, $2, $3, $4);
+
+            push @fileList, $git_filename;
+        }
+        close $filePipe;
+    }
+
+        # Convert to normalized form:
+    my %revMap;
+    my $file;
+    foreach $file (@fileList)
+    {
+        my($dir) = ($file=~m%^(?:(.*)/)?([^/]*)$%);
+        $dir='' if(!defined($dir));
+
+            # parent directories:
+            # ... create empty dir maps for parent dirs:
+        my($td)=$dir;
+        while(!defined($revMap{$td}))
+        {
+            $revMap{$td}={};
+
+            my($tp)=($td=~m%^(?:(.*)/)?([^/]*)$%);
+            $tp='' if(!defined($tp));
+            $td=$tp;
+        }
+            # ... add children to parent maps (now that they exist):
+        $td=$dir;
+        while($td ne "")
+        {
+            my($tp)=($td=~m%^(?:(.*)/)?([^/]*)$%);
+            $tp='' if(!defined($tp));
+
+            if(defined($revMap{$tp}{$td}))
+            {
+                if($revMap{$tp}{$td} ne 'D')
+                {
+                    die "Weird file/directory inconsistency in $cacheKey";
+                }
+                last;   # loop exit
+            }
+            $revMap{$tp}{$td}='D';
+
+            $td=$tp;
+        }
+
+            # file
+        $revMap{$dir}{$file}='F';
+    }
+
+        # Save in cache:
+    $self->{revisionDirMapCache}{$cacheKey}=\%revMap;
+    return $self->{revisionDirMapCache}{$cacheKey};
+}
+
 =head2 getlog
 
 See also gethistorydense().
@@ -3646,6 +4516,19 @@ =head2 getmeta
 This function takes a filename (with path) argument and returns a hashref of
 metadata for that file.
 
+There are several ways $revision can be specified:
+
+   - A reference to hash that contains a "tag" that is the
+     actual revision (one of the below).  TODO: Also allow it to
+     specify a "date" in the hash.
+   - undef, to refer to the latest version on the main branch.
+   - Full CVS client revision number (mapped to integer in DB, without the
+     "1." prefix),
+   - Complex CVS-compatible "special" revision number for
+     non-linear history (see comment below)
+   - git commit sha1 hash
+   - branch or tag name
+
 =cut
 
 sub getmeta
@@ -3656,23 +4539,144 @@ sub getmeta
     my $tablename_rev = $self->tablename("revision");
     my $tablename_head = $self->tablename("head");
 
-    my $db_query;
-    if ( defined($revision) and $revision =~ /^1\.(\d+)$/ )
+    if ( ref($revision) eq "HASH" )
     {
-        my ($intRev) = $1;
-        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND revision=?",{},1);
-        $db_query->execute($filename, $intRev);
+        $revision = $revision->{tag};
     }
-    elsif ( defined($revision) and $revision =~ /^[a-zA-Z0-9]{40}$/ )
+
+    # Overview of CVS revision numbers:
+    #
+    # General CVS numbering scheme:
+    #   - Basic mainline branch numbers: "1.1", "1.2", "1.3", etc.
+    #   - Result of "cvs checkin -r" (possible, but not really
+    #     recommended): "2.1", "2.2", etc
+    #   - Branch tag: "1.2.0.n", where "1.2" is revision it was branched
+    #     from, "0" is a magic placeholder that identifies it as a
+    #     branch tag instead of a version tag, and n is 2 times the
+    #     branch number off of "1.2", starting with "2".
+    #   - Version on a branch: "1.2.n.x", where "1.2" is branch-from, "n"
+    #     is branch number off of "1.2" (like n above), and "x" is
+    #     the version number on the branch.
+    #   - Branches can branch off of branches: "1.3.2.7.4.1" (even number
+    #     of components).
+    #   - Odd "n"s are used by "vendor branches" that result
+    #     from "cvs import".  Vendor branches have additional
+    #     strangeness in the sense that the main rcs "head" of the main
+    #     branch will (temporarily until first normal commit) point
+    #     to the version on the vendor branch, rather than the actual
+    #     main branch.  (FUTURE: This may provide an opportunity
+    #     to use "strange" revision numbers for fast-forward-merged
+    #     branch tip when CVS client is asking for the main branch.)
+    #
+    # git-cvsserver CVS-compatible special numbering schemes:
+    #   - Currently git-cvsserver only tries to be identical to CVS for
+    #     simple "1.x" numbers on the "main" branch (as identified
+    #     by the module name that was originally cvs checkout'ed).
+    #   - The database only stores the "x" part, for historical reasons.
+    #     But most of the rest of the cvsserver preserves
+    #     and thinks using the full revision number.
+    #   - To handle non-linear history, it uses a version of the form
+    #     "2.1.1.2000.b.b.b."..., where the 2.1.1.2000 is to help uniquely
+    #     identify this as a special revision number, and there are
+    #     20 b's that together encode the sha1 git commit from which
+    #     this version of this file originated.  Each b is
+    #     the numerical value of the corresponding byte plus
+    #     100.
+    #      - "plus 100" avoids "0"s, and also reduces the
+    #        likelyhood of a collision in the case that someone someday
+    #        writes an import tool that tries to preserve original
+    #        CVS revision numbers, and the original CVS data had done
+    #        lots of branches off of branches and other strangeness to
+    #        end up with a real version number that just happens to look
+    #        like this special revision number form.  Also, if needed
+    #        there are several ways to extend/identify alternative encodings
+    #        within the "2.1.1.2000" part if necessary.
+    #      - Unlike real CVS revisions, you can't really reconstruct what
+    #        relation a revision of this form has to other revisions.
+    #   - FUTURE: TODO: Rework database somehow to make up and remember
+    #     fully-CVS-compatible branches and branch version numbers.
+
+    my $meta;
+    if ( defined($revision) )
     {
-        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",{},1);
-        $db_query->execute($filename, $revision);
-    } else {
-        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_head WHERE name=?",{},1);
+        if ( $revision =~ /^1\.(\d+)$/ )
+        {
+            my ($intRev) = $1;
+            my $db_query;
+            $db_query = $self->{dbh}->prepare_cached(
+                "SELECT * FROM $tablename_rev WHERE name=? AND revision=?",
+                {},1);
+            $db_query->execute($filename, $intRev);
+            $meta = $db_query->fetchrow_hashref;
+        }
+        elsif ( $revision =~ /^2\.1\.1\.2000(\.[1-3][0-9][0-9]){20}$/ )
+        {
+            my ($commitHash)=($revision=~/^2\.1\.1\.2000(.*)$/);
+            $commitHash=~s/\.([0-9]+)/sprintf("%02x",$1-100)/eg;
+            if($commitHash=~/^[0-9a-f]{40}$/)
+            {
+                return $self->getMetaFromCommithash($filename,$commitHash);
+            }
+
+            # error recovery: fall back on head version below
+            print "E Failed to find $filename version=$revision or commit=$commitHash\n";
+            $log->warning("failed get $revision with commithash=$commitHash");
+            undef $revision;
+        }
+        elsif ( $revision =~ /^[0-9a-f]{40}$/ )
+        {
+            # Try DB first.  This is mostly only useful for req_annotate(),
+            # which only calls this for stuff that should already be in
+            # the DB.  It is fairly likely to be a waste of time
+            # in most other cases [unless the file happened to be
+            # modified in $revision specifically], but
+            # it is probably in the noise compared to how long
+            # getMetaFromCommithash() will take.
+            my $db_query;
+            $db_query = $self->{dbh}->prepare_cached(
+                "SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",
+                {},1);
+            $db_query->execute($filename, $revision);
+            $meta = $db_query->fetchrow_hashref;
+
+            if(! $meta)
+            {
+                my($revCommit)=$self->lookupCommitRef($revision);
+                if($revCommit=~/^[0-9a-f]{40}$/)
+                {
+                    return $self->getMetaFromCommithash($filename,$revCommit);
+                }
+
+                # error recovery: nothing found:
+                print "E Failed to find $filename version=$revision\n";
+                $log->warning("failed get $revision");
+                return $meta;
+            }
+        }
+        else
+        {
+            my($revCommit)=$self->lookupCommitRef($revision);
+            if($revCommit=~/^[0-9a-f]{40}$/)
+            {
+                return $self->getMetaFromCommithash($filename,$revCommit);
+            }
+
+            # error recovery: fall back on head version below
+            print "E Failed to find $filename version=$revision\n";
+            $log->warning("failed get $revision");
+            undef $revision;  # Allow fallback
+        }
+    }
+
+    if(!defined($revision))
+    {
+        my $db_query;
+        $db_query = $self->{dbh}->prepare_cached(
+                "SELECT * FROM $tablename_head WHERE name=?",{},1);
         $db_query->execute($filename);
+        $meta = $db_query->fetchrow_hashref;
     }
 
-    my $meta = $db_query->fetchrow_hashref;
     if($meta)
     {
         $meta->{revision} = "1.$meta->{revision}";
@@ -3680,6 +4684,204 @@ sub getmeta
     return $meta;
 }
 
+sub getMetaFromCommithash
+{
+    my $self = shift;
+    my $filename = shift;
+    my $revCommit = shift;
+
+    # NOTE: This function doesn't scale well (lots of forks), especially
+    #   if you have many files that have not been modified for many commits
+    #   (each git-rev-parse redoes a lot of work for each file
+    #   that theoretically could be done in parallel by smarter
+    #   graph traversal).
+    #
+    # TODO: Possible optimization strategies:
+    #   - Solve the issue of assigning and remembering "real" CVS
+    #     revision numbers for branches, and ensure the
+    #     data structure can do this efficiently.  Perhaps something
+    #     similar to "git notes", and carefully structured to take
+    #     advantage same-sha1-is-same-contents, to roll the same
+    #     unmodified subdirectory data onto multiple commits?
+    #   - Write and use a C tool that is like git-blame, but
+    #     operates on multiple files with file granularity, instead
+    #     of one file with line granularity.  Cache
+    #     most-recently-modified in $self->{commitRefCache}{$revCommit}.
+    #     Try to be intelligent about how many files we do with
+    #     one fork (perhaps one directory at a time, without recursion,
+    #     and/or include directory as one line item, recurse from here
+    #     instead of in C tool?).
+    #   - Perhaps we could ask the DB for (filename,fileHash),
+    #     and just guess that it is correct (that the file hadn't
+    #     changed between $revCommit and the found commit, then
+    #     changed back, confusing anything trying to interpret
+    #     history).  Probably need to add another index to revisions
+    #     DB table for this.
+    #   - NOTE: Trying to store all (commit,file) keys in DB [to
+    #     find "lastModfiedCommit] (instead of
+    #     just files that changed in each commit as we do now) is
+    #     probably not practical from a disk space perspective.
+
+        # Does the file exist in $revCommit?
+    # TODO: Include file hash in dirmap cache.
+    my($dirMap)=$self->getRevisionDirMap($revCommit);
+    my($dir,$file)=($filename=~m%^(?:(.*)/)?([^/]*$)%);
+    if(!defined($dir))
+    {
+        $dir="";
+    }
+    if( !defined($dirMap->{$dir}) ||
+        !defined($dirMap->{$dir}{$filename}) )
+    {
+        my($fileHash)="deleted";
+
+        my($retVal)={};
+        $retVal->{name}=$filename;
+        $retVal->{filehash}=$fileHash;
+
+            # not needed and difficult to compute:
+        $retVal->{revision}="0";  # $revision;
+        $retVal->{commithash}=$revCommit;
+        #$retVal->{author}=$commit->{author};
+        #$retVal->{modified}=convertToCvsDate($commit->{date});
+        #$retVal->{mode}=convertToDbMode($mode);
+
+        return $retVal;
+    }
+
+    my($fileHash)=safe_pipe_capture("git","rev-parse","$revCommit:$filename");
+    chomp $fileHash;
+    if(!($fileHash=~/^[0-9a-f]{40}$/))
+    {
+        die "Invalid fileHash '$fileHash' looking up"
+                    ." '$revCommit:$filename'\n";
+    }
+
+    # information about most recent commit to modify $filename:
+    open(my $gitLogPipe, '-|', 'git', 'rev-list',
+         '--max-count=1', '--pretty', '--parents',
+         $revCommit, '--', $filename)
+                or die "Cannot call git-rev-list: $!";
+    my @commits=readCommits($gitLogPipe);
+    close $gitLogPipe;
+    if(scalar(@commits)!=1)
+    {
+        die "Can't find most recent commit changing $filename\n";
+    }
+    my($commit)=$commits[0];
+    if( !defined($commit) || !defined($commit->{hash}) )
+    {
+        return undef;
+    }
+
+    # does this (commit,file) have a real assigned CVS revision number?
+    my $tablename_rev = $self->tablename("revision");
+    my $db_query;
+    $db_query = $self->{dbh}->prepare_cached(
+        "SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",
+        {},1);
+    $db_query->execute($filename, $commit->{hash});
+    my($meta)=$db_query->fetchrow_hashref;
+    if($meta)
+    {
+        $meta->{revision} = "1.$meta->{revision}";
+        return $meta;
+    }
+
+    # fall back on special revision number
+    my($revision)=$commit->{hash};
+    $revision=~s/(..)/'.' . (hex($1)+100)/eg;
+    $revision="2.1.1.2000$revision";
+
+    # meta data about $filename:
+    open(my $filePipe, '-|', 'git', 'ls-tree', '-z',
+                $commit->{hash}, '--', $filename)
+            or die("Cannot call git-ls-tree : $!");
+    local $/ = "\0";
+    my $line;
+    $line=<$filePipe>;
+    if(defined(<$filePipe>))
+    {
+        die "Expected only a single file for git-ls-tree $filename\n";
+    }
+    close $filePipe;
+
+    chomp $line;
+    unless ( $line=~m/^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
+    {
+        die("Couldn't process git-ls-tree line : $line\n");
+    }
+    my ( $mode, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
+
+    # save result:
+    my($retVal)={};
+    $retVal->{name}=$filename;
+    $retVal->{revision}=$revision;
+    $retVal->{filehash}=$fileHash;
+    $retVal->{commithash}=$revCommit;
+    $retVal->{author}=$commit->{author};
+    $retVal->{modified}=convertToCvsDate($commit->{date});
+    $retVal->{mode}=convertToDbMode($mode);
+
+    return $retVal;
+}
+
+=head2 lookupCommitRef
+
+Convert tag/branch/abbreviation/etc into a commit sha1 hash.  Caches
+the result so looking it up again is fast.
+
+=cut
+
+sub lookupCommitRef
+{
+    my $self = shift;
+    my $ref = shift;
+
+    my $commitHash = $self->{commitRefCache}{$ref};
+    if(defined($commitHash))
+    {
+        return $commitHash;
+    }
+
+    $commitHash=safe_pipe_capture("git","rev-parse","--verify","--quiet",
+                                  $self->unescapeRefName($ref));
+    $commitHash=~s/\s*$//;
+    if(!($commitHash=~/^[0-9a-f]{40}$/))
+    {
+        $commitHash=undef;
+    }
+
+    if( defined($commitHash) )
+    {
+        my $type=safe_pipe_capture("git","cat-file","-t",$commitHash);
+        if( ! ($type=~/^commit\s*$/ ) )
+        {
+            $commitHash=undef;
+        }
+    }
+    if(defined($commitHash))
+    {
+        $self->{commitRefCache}{$ref}=$commitHash;
+    }
+    return $commitHash;
+}
+
+=head2 clearCommitRefCaches
+
+Clears cached commit cache (sha1's for various tags/abbeviations/etc),
+and related caches.
+
+=cut
+
+sub clearCommitRefCaches
+{
+    my $self = shift;
+    $self->{commitRefCache} = {};
+    $self->{revisionDirMapCache} = undef;
+    $self->{gethead_cache} = undef;
+}
+
 =head2 commitmessage
 
 this function takes a commithash and returns the commit message for that commit
@@ -3745,6 +4947,97 @@ sub gethistorydense
     return $result;
 }
 
+=head2 escapeRefName
+
+Apply an escape mechanism to compensate for characters that
+git ref names can have that CVS tags can not.
+
+=cut
+sub escapeRefName
+{
+    my($self,$refName)=@_;
+
+    # CVS officially only allows [-_A-Za-z0-9] in tag names (or in
+    # many contexts it can also be a CVS revision number).
+    #
+    # Git tags commonly use '/' and '.' as well, but also handle
+    # anything else just in case:
+    #
+    #   = "_-s-"  For '/'.
+    #   = "_-p-"  For '.'.
+    #   = "_-u-"  For underscore, in case someone wants a literal "_-" in
+    #     a tag name.
+    #   = "_-xx-" Where "xx" is the hexadecimal representation of the
+    #     desired ASCII character byte. (for anything else)
+
+    if(! $refName=~/^[1-9][0-9]*(\.[1-9][0-9]*)*$/)
+    {
+        $refName=~s/_-/_-u--/g;
+        $refName=~s/\./_-p-/g;
+        $refName=~s%/%_-s-%g;
+        $refName=~s/[^-_a-zA-Z0-9]/sprintf("_-%02x-",$1)/eg;
+    }
+}
+
+=head2 unescapeRefName
+
+Undo an escape mechanism to compensate for characters that
+git ref names can have that CVS tags can not.
+
+=cut
+sub unescapeRefName
+{
+    my($self,$refName)=@_;
+
+    # see escapeRefName() for description of escape mechanism.
+
+    $refName=~s/_-([spu]|[0-9a-f][0-9a-f])-/unescapeRefNameChar($1)/eg;
+
+    # allowed tag names
+    # TODO: Perhaps use git check-ref-format, with an in-process cache of
+    #  validated names?
+    if( !( $refName=~m%^[^-][-a-zA-Z0-9_/.]*$% ) ||
+        ( $refName=~m%[/.]$% ) ||
+        ( $refName=~/\.lock$/ ) ||
+        ( $refName=~m%\.\.|/\.|[[\\:?*~]|\@\{% ) )  # matching }
+    {
+        # Error:
+        $log->warn("illegal refName: $refName");
+        $refName=undef;
+    }
+    return $refName;
+}
+
+sub unescapeRefNameChar
+{
+    my($char)=@_;
+
+    if($char eq "s")
+    {
+        $char="/";
+    }
+    elsif($char eq "p")
+    {
+        $char=".";
+    }
+    elsif($char eq "u")
+    {
+        $char="_";
+    }
+    elsif($char=~/^[0-9a-f][0-9a-f]$/)
+    {
+        $char=chr(hex($char));
+    }
+    else
+    {
+        # Error case: Maybe it has come straight from user, and
+        # wasn't supposed to be escaped?  Restore it the way we got it:
+        $char="_-$char-";
+    }
+
+    return $char;
+}
+
 =head2 in_array()
 
 from Array::PAT - mimics the in_array() function
index 69f1452cf80066a002fe58bb1eeebda7fe63ba8b..647f11020257598a8245eb1e84f64b80a2c55aca 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -7,16 +7,36 @@
 #            2007 Trolltech ASA
 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
 #
-
 import sys
 if sys.hexversion < 0x02040000:
     # The limiter is the subprocess module
     sys.stderr.write("git-p4: requires Python 2.4 or later.\n")
     sys.exit(1)
-
-import optparse, os, marshal, subprocess, shelve
-import tempfile, getopt, os.path, time, platform
-import re, shutil
+import os
+import optparse
+import marshal
+import subprocess
+import tempfile
+import time
+import platform
+import re
+import shutil
+import stat
+
+try:
+    from subprocess import CalledProcessError
+except ImportError:
+    # from python2.7:subprocess.py
+    # Exception classes used by this module.
+    class CalledProcessError(Exception):
+        """This exception is raised when a process run by check_call() returns
+        a non-zero exit status.  The exit status will be stored in the
+        returncode attribute."""
+        def __init__(self, returncode, cmd):
+            self.returncode = returncode
+            self.cmd = cmd
+        def __str__(self):
+            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
 
 verbose = False
 
@@ -158,13 +178,33 @@ def system(cmd):
     expand = isinstance(cmd,basestring)
     if verbose:
         sys.stderr.write("executing %s\n" % str(cmd))
-    subprocess.check_call(cmd, shell=expand)
+    retcode = subprocess.call(cmd, shell=expand)
+    if retcode:
+        raise CalledProcessError(retcode, cmd)
 
 def p4_system(cmd):
     """Specifically invoke p4 as the system command. """
     real_cmd = p4_build_cmd(cmd)
     expand = isinstance(real_cmd, basestring)
-    subprocess.check_call(real_cmd, shell=expand)
+    retcode = subprocess.call(real_cmd, shell=expand)
+    if retcode:
+        raise CalledProcessError(retcode, real_cmd)
+
+_p4_version_string = None
+def p4_version_string():
+    """Read the version string, showing just the last line, which
+       hopefully is the interesting version bit.
+
+       $ p4 -V
+       Perforce - The Fast Software Configuration Management System.
+       Copyright 1995-2011 Perforce Software.  All rights reserved.
+       Rev. P4/NTX86/2011.1/393975 (2011/12/16).
+    """
+    global _p4_version_string
+    if not _p4_version_string:
+        a = p4_read_pipe_lines(["-V"])
+        _p4_version_string = a[-1].rstrip()
+    return _p4_version_string
 
 def p4_integrate(src, dest):
     p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
@@ -539,43 +579,75 @@ def gitBranchExists(branch):
     return proc.wait() == 0;
 
 _gitConfig = {}
-def gitConfig(key, args = None): # set args to "--bool", for instance
+
+def gitConfig(key):
+    if not _gitConfig.has_key(key):
+        cmd = [ "git", "config", key ]
+        s = read_pipe(cmd, ignore_error=True)
+        _gitConfig[key] = s.strip()
+    return _gitConfig[key]
+
+def gitConfigBool(key):
+    """Return a bool, using git config --bool.  It is True only if the
+       variable is set to true, and False if set to false or not present
+       in the config."""
+
     if not _gitConfig.has_key(key):
-        argsFilter = ""
-        if args != None:
-            argsFilter = "%s " % args
-        cmd = "git config %s%s" % (argsFilter, key)
-        _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
+        cmd = [ "git", "config", "--bool", key ]
+        s = read_pipe(cmd, ignore_error=True)
+        v = s.strip()
+        _gitConfig[key] = v == "true"
     return _gitConfig[key]
 
 def gitConfigList(key):
     if not _gitConfig.has_key(key):
-        _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
+        s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
+        _gitConfig[key] = s.strip().split(os.linesep)
     return _gitConfig[key]
 
-def p4BranchesInGit(branchesAreInRemotes = True):
+def p4BranchesInGit(branchesAreInRemotes=True):
+    """Find all the branches whose names start with "p4/", looking
+       in remotes or heads as specified by the argument.  Return
+       a dictionary of { branch: revision } for each one found.
+       The branch names are the short names, without any
+       "p4/" prefix."""
+
     branches = {}
 
     cmdline = "git rev-parse --symbolic "
     if branchesAreInRemotes:
-        cmdline += " --remotes"
+        cmdline += "--remotes"
     else:
-        cmdline += " --branches"
+        cmdline += "--branches"
 
     for line in read_pipe_lines(cmdline):
         line = line.strip()
 
-        ## only import to p4/
-        if not line.startswith('p4/') or line == "p4/HEAD":
+        # only import to p4/
+        if not line.startswith('p4/'):
+            continue
+        # special symbolic ref to p4/master
+        if line == "p4/HEAD":
             continue
-        branch = line
 
-        # strip off p4
-        branch = re.sub ("^p4/", "", line)
+        # strip off p4/ prefix
+        branch = line[len("p4/"):]
 
         branches[branch] = parseRevision(line)
+
     return branches
 
+def branch_exists(branch):
+    """Make sure that the given ref name really exists."""
+
+    cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, _ = p.communicate()
+    if p.returncode:
+        return False
+    # expect exactly one line of output: the branch name
+    return out.rstrip() == branch
+
 def findUpstreamBranchPoint(head = "HEAD"):
     branches = p4BranchesInGit()
     # map from depot-path to branch name
@@ -677,8 +749,7 @@ def p4PathStartsWith(path, prefix):
     #
     # we may or may not have a problem. If you have core.ignorecase=true,
     # we treat DirA and dira as the same directory
-    ignorecase = gitConfig("core.ignorecase", "--bool") == "true"
-    if ignorecase:
+    if gitConfigBool("core.ignorecase"):
         return path.lower().startswith(prefix.lower())
     return path.startswith(prefix)
 
@@ -748,7 +819,8 @@ def wildcard_encode(path):
     return path
 
 def wildcard_present(path):
-    return path.translate(None, "*#@%") != path
+    m = re.search("[*#@%]", path)
+    return m is not None
 
 class Command:
     def __init__(self):
@@ -907,19 +979,21 @@ def __init__(self):
                 optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
                 optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
                 optparse.make_option("--conflict", dest="conflict_behavior",
-                                     choices=self.conflict_behavior_choices)
+                                     choices=self.conflict_behavior_choices),
+                optparse.make_option("--branch", dest="branch"),
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
         self.origin = ""
         self.detectRenames = False
-        self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
+        self.preserveUser = gitConfigBool("git-p4.preserveUser")
         self.dry_run = False
         self.prepare_p4_only = False
         self.conflict_behavior = None
         self.isWindows = (platform.system() == "Windows")
         self.exportLabels = False
         self.p4HasMoveCommand = p4_has_move_command()
+        self.branch = None
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
@@ -1007,7 +1081,8 @@ def patchRCSKeywords(self, file, pattern):
     def p4UserForCommit(self,id):
         # Return the tuple (perforce user,git email) for a given git commit id
         self.getUserMapFromPerforceServer()
-        gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
+        gitEmail = read_pipe(["git", "log", "--max-count=1",
+                              "--format=%ae", id])
         gitEmail = gitEmail.strip()
         if not self.emails.has_key(gitEmail):
             return (None,gitEmail)
@@ -1020,7 +1095,7 @@ def checkValidP4Users(self,commits):
             (user,email) = self.p4UserForCommit(id)
             if not user:
                 msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
-                if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
+                if gitConfigBool("git-p4.allowMissingP4Users"):
                     print "%s" % msg
                 else:
                     die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
@@ -1115,7 +1190,7 @@ def edit_template(self, template_file):
            message.  Return true if okay to continue with the submit."""
 
         # if configured to skip the editing part, just submit
-        if gitConfig("git-p4.skipSubmitEdit") == "true":
+        if gitConfigBool("git-p4.skipSubmitEdit"):
             return True
 
         # look at the modification time, to check later if the user saved
@@ -1131,7 +1206,7 @@ def edit_template(self, template_file):
 
         # If the file was not saved, prompt to see if this patch should
         # be skipped.  But skip this verification step if configured so.
-        if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+        if gitConfigBool("git-p4.skipSubmitEditCheck"):
             return True
 
         # modification time updated means user saved the file
@@ -1189,6 +1264,9 @@ def applyCommit(self, id):
                     p4_edit(dest)
                     pureRenameCopy.discard(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
+                if self.isWindows:
+                    # turn off read-only attribute
+                    os.chmod(dest, stat.S_IWRITE)
                 os.unlink(dest)
                 editedFiles.add(dest)
             elif modifier == "R":
@@ -1207,6 +1285,8 @@ def applyCommit(self, id):
                         p4_edit(dest)   # with move: already open, writable
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 if not self.p4HasMoveCommand:
+                    if self.isWindows:
+                        os.chmod(dest, stat.S_IWRITE)
                     os.unlink(dest)
                     filesToDelete.add(src)
                 editedFiles.add(dest)
@@ -1226,7 +1306,7 @@ def applyCommit(self, id):
 
             # Patch failed, maybe it's just RCS keyword woes. Look through
             # the patch to see if that's possible.
-            if gitConfig("git-p4.attemptRCSCleanup","--bool") == "true":
+            if gitConfigBool("git-p4.attemptRCSCleanup"):
                 file = None
                 pattern = None
                 kwfiles = {}
@@ -1247,6 +1327,10 @@ def applyCommit(self, id):
                 for file in kwfiles:
                     if verbose:
                         print "zapping %s with %s" % (line,pattern)
+                    # File is being deleted, so not open in p4.  Must
+                    # disable the read-only bit on windows.
+                    if self.isWindows and file not in editedFiles:
+                        os.chmod(file, stat.S_IWRITE)
                     self.patchRCSKeywords(file, kwfiles[file])
                     fixed_rcs_keywords = True
 
@@ -1517,7 +1601,7 @@ def run(self, args):
             sys.exit(128)
 
         self.useClientSpec = False
-        if gitConfig("git-p4.useclientspec", "--bool") == "true":
+        if gitConfigBool("git-p4.useclientspec"):
             self.useClientSpec = True
         if self.useClientSpec:
             self.clientSpecDirs = getClientSpec()
@@ -1553,11 +1637,11 @@ def run(self, args):
         self.check()
 
         commits = []
-        for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+        for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, self.master)]):
             commits.append(line.strip())
         commits.reverse()
 
-        if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"):
+        if self.preserveUser or gitConfigBool("git-p4.skipUserNameCheck"):
             self.checkAuthorship = False
         else:
             self.checkAuthorship = True
@@ -1593,7 +1677,7 @@ def run(self, args):
         else:
             self.diffOpts += " -C%s" % detectCopies
 
-        if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
+        if gitConfigBool("git-p4.detectCopiesHarder"):
             self.diffOpts += " --find-copies-harder"
 
         #
@@ -1656,6 +1740,8 @@ def run(self, args):
             print "All commits applied!"
 
             sync = P4Sync()
+            if self.branch:
+                sync.branch = self.branch
             sync.run([])
 
             rebase = P4Rebase()
@@ -1675,7 +1761,7 @@ def run(self, args):
                                            "--format=format:%h %s",  c])
                 print "You will have to do 'git p4 sync' and rebase."
 
-        if gitConfig("git-p4.exportLabels", "--bool") == "true":
+        if gitConfigBool("git-p4.exportLabels"):
             self.exportLabels = True
 
         if self.exportLabels:
@@ -1945,7 +2031,6 @@ def __init__(self):
         self.syncWithOrigin = True
         self.importIntoRemotes = True
         self.maxChanges = ""
-        self.isWindows = (platform.system() == "Windows")
         self.keepRepoPath = False
         self.depotPaths = None
         self.p4BranchesInGit = []
@@ -2090,7 +2175,14 @@ def streamOneP4File(self, file, contents):
             # operations.  utf16 is converted to ascii or utf8, perhaps.
             # But ascii text saved as -t utf16 is completely mangled.
             # Invoke print -o to get the real contents.
+            #
+            # On windows, the newlines will always be mangled by print, so put
+            # them back too.  This is not needed to the cygwin windows version,
+            # just the native "NT" type.
+            #
             text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
+            if p4_version_string().find("/NT") >= 0:
+                text = text.replace("\r\n", "\n")
             contents = [ text ]
 
         if type_base == "apple":
@@ -2106,15 +2198,6 @@ def streamOneP4File(self, file, contents):
             print "\nIgnoring apple filetype file %s" % file['depotFile']
             return
 
-        # Perhaps windows wants unicode, utf16 newlines translated too;
-        # but this is not doing it.
-        if self.isWindows and type_base == "text":
-            mangled = []
-            for data in contents:
-                data = data.replace("\r\n", "\n")
-                mangled.append(data)
-            contents = mangled
-
         # Note that we do not try to de-mangle keywords on utf16 files,
         # even though in theory somebody may want that.
         pattern = p4_keywords_regexp_for_type(type_base, type_mods)
@@ -2509,13 +2592,6 @@ def getBranchMappingFromGitBranches(self):
                 branch = branch[len(self.projectName):]
             self.knownBranches[branch] = branch
 
-    def listExistingP4GitBranches(self):
-        # branches holds mapping from name to commit
-        branches = p4BranchesInGit(self.importIntoRemotes)
-        self.p4BranchesInGit = branches.keys()
-        for branch in branches.keys():
-            self.initialParents[self.refPrefix + branch] = branches[branch]
-
     def updateOptionDict(self, d):
         option_keys = {}
         if self.keepRepoPath:
@@ -2599,7 +2675,8 @@ def importNewBranch(self, branch, maxChange):
 
     def searchParent(self, parent, branch, target):
         parentFound = False
-        for blob in read_pipe_lines(["git", "rev-list", "--reverse", "--no-merges", parent]):
+        for blob in read_pipe_lines(["git", "rev-list", "--reverse",
+                                     "--no-merges", parent]):
             blob = blob.strip()
             if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
                 parentFound = True
@@ -2670,7 +2747,7 @@ def importChanges(self, changes):
 
                         blob = None
                         if len(parent) > 0:
-                            tempBranch = os.path.join(self.tempBranchLocation, "%d" % (change))
+                            tempBranch = "%s/%d" % (self.tempBranchLocation, change)
                             if self.verbose:
                                 print "Creating temporary branch: " + tempBranch
                             self.commit(description, filesForCommit, tempBranch)
@@ -2687,6 +2764,7 @@ def importChanges(self, changes):
                     files = self.extractFilesFromCommit(description)
                     self.commit(description, files, self.branch,
                                 self.initialParent)
+                    # only needed once, to connect to the previous commit
                     self.initialParent = ""
             except IOError:
                 print self.gitError.read()
@@ -2752,41 +2830,38 @@ def importHeadRevision(self, revision):
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
-        self.initialParent = ""
         self.previousDepotPaths = []
+        self.hasOrigin = False
 
         # map from branch depot path to parent branch
         self.knownBranches = {}
         self.initialParents = {}
-        self.hasOrigin = originP4BranchesExist()
-        if not self.syncWithOrigin:
-            self.hasOrigin = False
 
         if self.importIntoRemotes:
             self.refPrefix = "refs/remotes/p4/"
         else:
             self.refPrefix = "refs/heads/p4/"
 
-        if self.syncWithOrigin and self.hasOrigin:
-            if not self.silent:
-                print "Syncing with origin first by calling git fetch origin"
-            system("git fetch origin")
+        if self.syncWithOrigin:
+            self.hasOrigin = originP4BranchesExist()
+            if self.hasOrigin:
+                if not self.silent:
+                    print 'Syncing with origin first, using "git fetch origin"'
+                system("git fetch origin")
 
+        branch_arg_given = bool(self.branch)
         if len(self.branch) == 0:
             self.branch = self.refPrefix + "master"
             if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
                 system("git update-ref %s refs/heads/p4" % self.branch)
-                system("git branch -D p4");
-            # create it /after/ importing, when master exists
-            if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
-                system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+                system("git branch -D p4")
 
         # accept either the command-line option, or the configuration variable
         if self.useClientSpec:
             # will use this after clone to set the variable
             self.useClientSpec_from_options = True
         else:
-            if gitConfig("git-p4.useclientspec", "--bool") == "true":
+            if gitConfigBool("git-p4.useclientspec"):
                 self.useClientSpec = True
         if self.useClientSpec:
             self.clientSpecDirs = getClientSpec()
@@ -2796,12 +2871,25 @@ def run(self, args):
         if args == []:
             if self.hasOrigin:
                 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
-            self.listExistingP4GitBranches()
+
+            # branches holds mapping from branch name to sha1
+            branches = p4BranchesInGit(self.importIntoRemotes)
+
+            # restrict to just this one, disabling detect-branches
+            if branch_arg_given:
+                short = self.branch.split("/")[-1]
+                if short in branches:
+                    self.p4BranchesInGit = [ short ]
+            else:
+                self.p4BranchesInGit = branches.keys()
 
             if len(self.p4BranchesInGit) > 1:
                 if not self.silent:
                     print "Importing from/into multiple branches"
                 self.detectBranches = True
+                for branch in branches.keys():
+                    self.initialParents[self.refPrefix + branch] = \
+                        branches[branch]
 
             if self.verbose:
                 print "branches: %s" % self.p4BranchesInGit
@@ -2838,13 +2926,21 @@ def run(self, args):
             if p4Change > 0:
                 self.depotPaths = sorted(self.previousDepotPaths)
                 self.changeRange = "@%s,#head" % p4Change
-                if not self.detectBranches:
-                    self.initialParent = parseRevision(self.branch)
                 if not self.silent and not self.detectBranches:
                     print "Performing incremental import into %s git branch" % self.branch
 
+        # accept multiple ref name abbreviations:
+        #    refs/foo/bar/branch -> use it exactly
+        #    p4/branch -> prepend refs/remotes/ or refs/heads/
+        #    branch -> prepend refs/remotes/p4/ or refs/heads/p4/
         if not self.branch.startswith("refs/"):
-            self.branch = "refs/heads/" + self.branch
+            if self.importIntoRemotes:
+                prepend = "refs/remotes/"
+            else:
+                prepend = "refs/heads/"
+            if not self.branch.startswith("p4/"):
+                prepend += "p4/"
+            self.branch = prepend + self.branch
 
         if len(args) == 0 and self.depotPaths:
             if not self.silent:
@@ -2955,8 +3051,21 @@ def run(self, args):
             else:
                 # catch "git p4 sync" with no new branches, in a repo that
                 # does not have any existing p4 branches
-                if len(args) == 0 and not self.p4BranchesInGit:
-                    die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.");
+                if len(args) == 0:
+                    if not self.p4BranchesInGit:
+                        die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.")
+
+                    # The default branch is master, unless --branch is used to
+                    # specify something else.  Make sure it exists, or complain
+                    # nicely about how to use --branch.
+                    if not self.detectBranches:
+                        if not branch_exists(self.branch):
+                            if branch_arg_given:
+                                die("Error: branch %s does not exist." % self.branch)
+                            else:
+                                die("Error: no branch %s; perhaps specify one with --branch." %
+                                    self.branch)
+
                 if self.verbose:
                     print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
                                                               self.changeRange)
@@ -2974,6 +3083,14 @@ def run(self, args):
 
                 self.updatedBranches = set()
 
+                if not self.detectBranches:
+                    if args:
+                        # start a new branch
+                        self.initialParent = ""
+                    else:
+                        # build on a previous revision
+                        self.initialParent = parseRevision(self.branch)
+
                 self.importChanges(changes)
 
                 if not self.silent:
@@ -2984,7 +3101,7 @@ def run(self, args):
                             sys.stdout.write("%s " % b)
                         sys.stdout.write("\n")
 
-        if gitConfig("git-p4.importLabels", "--bool") == "true":
+        if gitConfigBool("git-p4.importLabels"):
             self.importLabels = True
 
         if self.importLabels:
@@ -3006,6 +3123,13 @@ def run(self, args):
                 read_pipe("git update-ref -d %s" % branch)
             os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
 
+        # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
+        # a convenient shortcut refname "p4".
+        if self.importIntoRemotes:
+            head_ref = self.refPrefix + "HEAD"
+            if not gitBranchExists(head_ref) and gitBranchExists(self.branch):
+                system(["git", "symbolic-ref", head_ref, self.branch])
+
         return True
 
 class P4Rebase(Command):
@@ -3095,6 +3219,7 @@ def run(self, args):
         self.cloneExclude = ["/"+p for p in self.cloneExclude]
         for p in depotPaths:
             if not p.startswith("//"):
+                sys.stderr.write('Depot paths must start with "//": %s\n' % p)
                 return False
 
         if not self.cloneDestination:
@@ -3109,21 +3234,21 @@ def run(self, args):
         init_cmd = [ "git", "init" ]
         if self.cloneBare:
             init_cmd.append("--bare")
-        subprocess.check_call(init_cmd)
+        retcode = subprocess.call(init_cmd)
+        if retcode:
+            raise CalledProcessError(retcode, init_cmd)
 
         if not P4Sync.run(self, depotPaths):
             return False
-        if self.branch != "master":
-            if self.importIntoRemotes:
-                masterbranch = "refs/remotes/p4/master"
-            else:
-                masterbranch = "refs/heads/p4/master"
-            if gitBranchExists(masterbranch):
-                system("git branch master %s" % masterbranch)
-                if not self.cloneBare:
-                    system("git checkout -f")
-            else:
-                print "Could not detect main branch. No checkout/master branch created."
+
+        # create a master branch and check out a work tree
+        if gitBranchExists(self.branch):
+            system([ "git", "branch", "master", self.branch ])
+            if not self.cloneBare:
+                system([ "git", "checkout", "-f" ])
+        else:
+            print 'Not checking out any branch, use ' \
+                  '"git checkout -q -b master <branch>"'
 
         # auto-set this variable if invoked with --use-client-spec
         if self.useClientSpec_from_options:
index 44901d53c43d972e03a71bfbe0b769f2e8f22d7b..8ed7fccc18507b6f3e2508bb117ce056a764de77 100644 (file)
@@ -190,6 +190,11 @@ is_empty_commit() {
        test "$tree" = "$ptree"
 }
 
+is_merge_commit()
+{
+       git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -874,7 +879,7 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 while read -r shortsha1 rest
 do
 
-       if test -z "$keep_empty" && is_empty_commit $shortsha1
+       if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1
        then
                comment_out="# "
        else
index d94a66a87094436efcb6cf8dd0171b0683f0376c..ca6789996adcdcb0e4c7a045db8ea4091a9c0058 100644 (file)
 from git_remote_helpers.git.importer import GitImporter
 from git_remote_helpers.git.non_local import NonLocalGit
 
-if sys.hexversion < 0x01050200:
-    # os.makedirs() is the limiter
-    sys.stderr.write("git-remote-testgit: requires Python 1.5.2 or later.\n")
+if sys.hexversion < 0x02000000:
+    # string.encode() is the limiter
+    sys.stderr.write("git-remote-testgit: requires Python 2.0 or later.\n")
     sys.exit(1)
 
+
+def encode_filepath(path):
+    """Encodes a Unicode file path to a byte string.
+
+    On Python 2 this is a no-op; on Python 3 we encode the string as
+    suggested by [1] which allows an exact round-trip from the command line
+    to the filesystem.
+
+    [1] http://docs.python.org/3/c-api/unicode.html#file-system-encoding
+
+    """
+    if sys.hexversion < 0x03000000:
+        return path
+    return path.encode(sys.getfilesystemencoding(), 'surrogateescape')
+
+
 def get_repo(alias, url):
     """Returns a git repository object initialized for usage.
     """
@@ -45,7 +61,7 @@ def get_repo(alias, url):
     repo.get_head()
 
     hasher = _digest()
-    hasher.update(repo.path)
+    hasher.update(encode_filepath(repo.path))
     repo.hash = hasher.hexdigest()
 
     repo.get_base_path = lambda base: os.path.join(
@@ -87,9 +103,9 @@ def do_capabilities(repo, args):
     """Prints the supported capabilities.
     """
 
-    print "import"
-    print "export"
-    print "refspec refs/heads/*:%s*" % repo.prefix
+    print("import")
+    print("export")
+    print("refspec refs/heads/*:%s*" % repo.prefix)
 
     dirname = repo.get_base_path(repo.gitdir)
 
@@ -98,11 +114,11 @@ def do_capabilities(repo, args):
 
     path = os.path.join(dirname, 'git.marks')
 
-    print "*export-marks %s" % path
+    print("*export-marks %s" % path)
     if os.path.exists(path):
-        print "*import-marks %s" % path
+        print("*import-marks %s" % path)
 
-    print # end capabilities
+    print('') # end capabilities
 
 
 def do_list(repo, args):
@@ -115,16 +131,16 @@ def do_list(repo, args):
 
     for ref in repo.revs:
         debug("? refs/heads/%s", ref)
-        print "? refs/heads/%s" % ref
+        print("? refs/heads/%s" % ref)
 
     if repo.head:
         debug("@refs/heads/%s HEAD" % repo.head)
-        print "@refs/heads/%s HEAD" % repo.head
+        print("@refs/heads/%s HEAD" % repo.head)
     else:
         debug("@refs/heads/master HEAD")
-        print "@refs/heads/master HEAD"
+        print("@refs/heads/master HEAD")
 
-    print # end list
+    print('') # end list
 
 
 def update_local_repo(repo):
@@ -154,7 +170,7 @@ def do_import(repo, args):
     refs = [ref]
 
     while True:
-        line = sys.stdin.readline()
+        line = sys.stdin.readline().decode()
         if line == '\n':
             break
         if not line.startswith('import '):
@@ -164,7 +180,7 @@ def do_import(repo, args):
         ref = line[7:].strip()
         refs.append(ref)
 
-    print "feature done"
+    print("feature done")
 
     if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
         die('Told to fail')
@@ -172,7 +188,7 @@ def do_import(repo, args):
     repo = update_local_repo(repo)
     repo.exporter.export_repo(repo.gitdir, refs)
 
-    print "done"
+    print("done")
 
 
 def do_export(repo, args):
@@ -192,8 +208,8 @@ def do_export(repo, args):
         repo.non_local.push(repo.gitdir)
 
     for ref in changed:
-        print "ok %s" % ref
-    print
+        print("ok %s" % ref)
+    print('')
 
 
 COMMANDS = {
@@ -225,7 +241,7 @@ def read_one_line(repo):
 
     line = sys.stdin.readline()
 
-    cmdline = line
+    cmdline = line.decode()
 
     if not cmdline:
         warn("Unexpected EOF")
@@ -277,7 +293,11 @@ def main(args):
 
     more = True
 
-    sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
+    # Use binary mode since Python 3 does not permit unbuffered I/O in text
+    # mode.  Unbuffered I/O is required to avoid data that should be going
+    # to git-fast-import after an "export" command getting caught in our
+    # stdin buffer instead.
+    sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
     while (more):
         more = read_one_line(repo)
 
index 22ec5b63b4cd36dd10277e842970f9534a623a2f..004c034bc09449416ff6d66b7570d48b651f96cb 100755 (executable)
@@ -976,12 +976,12 @@ cmd_summary() {
        done |
        if test -n "$for_status"; then
                if [ -n "$files" ]; then
-                       gettextln "# Submodules changed but not updated:"
+                       gettextln "Submodules changed but not updated:" | git stripspace -c
                else
-                       gettextln "# Submodule changes to be committed:"
+                       gettextln "Submodule changes to be committed:" | git stripspace -c
                fi
-               echo "#"
-               sed -e 's|^|# |' -e 's|^# $|#|'
+               printf "\n" | git stripspace -c
+               git stripspace -c
        else
                cat
        fi
index d0866946cee0076dd167cf6234dfec192d8d194e..b46795f5935dd3d61a2f3e45f979aec04d12afcb 100755 (executable)
@@ -61,8 +61,6 @@ BEGIN
        command_oneline([qw/rev-parse --show-prefix/], STDERR => 0)
 } || '';
 
-my $git_dir_user_set = 1 if defined $ENV{GIT_DIR};
-$ENV{GIT_DIR} ||= '.git';
 $Git::SVN::Ra::_log_window_size = 100;
 
 if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) {
@@ -327,27 +325,20 @@ package main;
 };
 
 # make sure we're always running at the top-level working directory
-unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
-       unless (-d $ENV{GIT_DIR}) {
-               if ($git_dir_user_set) {
-                       die "GIT_DIR=$ENV{GIT_DIR} explicitly set, ",
-                           "but it is not a directory\n";
-               }
-               my $git_dir = delete $ENV{GIT_DIR};
-               my $cdup = undef;
-               git_cmd_try {
-                       $cdup = command_oneline(qw/rev-parse --show-cdup/);
-                       $git_dir = '.' unless ($cdup);
-                       chomp $cdup if ($cdup);
-                       $cdup = "." unless ($cdup && length $cdup);
-               } "Already at toplevel, but $git_dir not found\n";
-               chdir $cdup or die "Unable to chdir up to '$cdup'\n";
-               unless (-d $git_dir) {
-                       die "$git_dir still not found after going to ",
-                           "'$cdup'\n";
-               }
-               $ENV{GIT_DIR} = $git_dir;
-       }
+if ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {
+       $ENV{GIT_DIR} ||= ".git";
+} else {
+       my ($git_dir, $cdup);
+       git_cmd_try {
+               $git_dir = command_oneline([qw/rev-parse --git-dir/]);
+       } "Unable to find .git directory\n";
+       git_cmd_try {
+               $cdup = command_oneline(qw/rev-parse --show-cdup/);
+               chomp $cdup if ($cdup);
+               $cdup = "." unless ($cdup && length $cdup);
+       } "Already at toplevel, but $git_dir not found\n";
+       $ENV{GIT_DIR} = $git_dir;
+       chdir $cdup or die "Unable to chdir up to '$cdup'\n";
        $_repository = Git->repository(Repository => $ENV{GIT_DIR});
 }
 
diff --git a/git.c b/git.c
index ed66c660d15010af9a8b0c2ea1da8839e316e1db..b10c18b0419904631d7e8fa4a1fd11e8533b3034 100644 (file)
--- a/git.c
+++ b/git.c
@@ -313,6 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "check-attr", cmd_check_attr, RUN_SETUP },
+               { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
                { "check-ref-format", cmd_check_ref_format },
                { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
index 2247d5f95a7193c7221b9464debe167763b4fae3..cf040af3f5858948c871310ae1a23108900500d6 100644 (file)
@@ -1,2 +1,3 @@
+/GIT-PYTHON-VERSION
 /build
 /dist
index 74b05dc91e42414147d5f3dc7b4fc66fb86c0eca..3d122328c8d1d5e6f53c008aeee0da2c7d2fba80 100644 (file)
@@ -23,10 +23,16 @@ endif
 
 PYLIBDIR=$(shell $(PYTHON_PATH) -c \
         "import sys; \
-        print 'lib/python%i.%i/site-packages' % sys.version_info[:2]")
+        print('lib/python%i.%i/site-packages' % sys.version_info[:2])")
+
+py_version=$(shell $(PYTHON_PATH) -c \
+       'import sys; print("%i.%i" % sys.version_info[:2])')
 
 all: $(pysetupfile)
-       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build
+       $(QUIET)test "$$(cat GIT-PYTHON-VERSION 2>/dev/null)" = "$(py_version)" || \
+       flags=--force; \
+       $(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build $$flags
+       $(QUIET)echo "$(py_version)" >GIT-PYTHON-VERSION
 
 install: $(pysetupfile)
        $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix)
@@ -36,4 +42,4 @@ instlibdir: $(pysetupfile)
 
 clean:
        $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a
-       $(RM) *.pyo *.pyc
+       $(RM) *.pyo *.pyc GIT-PYTHON-VERSION
index e28cc8f98655b16e5930c9c623a1f3098ae0ca93..d3f90e1024a1b3ac8e7b4204b873e0ac2fffeaf9 100644 (file)
@@ -18,13 +18,16 @@ def __init__(self, repo):
 
     def get_refs(self, gitdir):
         """Returns a dictionary with refs.
+
+        Note that the keys in the returned dictionary are byte strings as
+        read from git.
         """
         args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"]
-        lines = check_output(args).strip().split('\n')
+        lines = check_output(args).strip().split('\n'.encode('ascii'))
         refs = {}
         for line in lines:
-            value, name = line.split(' ')
-            name = name.strip('commit\t')
+            value, name = line.split(' '.encode('ascii'))
+            name = name.strip('commit\t'.encode('ascii'))
             refs[name] = value
         return refs
 
index 4d434b65cbf5c42a455d5cd3bced030bfb51a245..6de41deb4435e76e349e67cc886bb92b8cb92e78 100644 (file)
@@ -4,6 +4,15 @@
 
 from distutils.core import setup
 
+# If building under Python3 we need to run 2to3 on the code, do this by
+# trying to import distutils' 2to3 builder, which is only available in
+# Python3.
+try:
+    from distutils.command.build_py import build_py_2to3 as build_py
+except ImportError:
+    # 2.x
+    from distutils.command.build_py import build_py
+
 setup(
     name = 'git_remote_helpers',
     version = '0.1.0',
@@ -14,4 +23,5 @@
     url = 'http://www.git-scm.com/',
     package_dir = {'git_remote_helpers': ''},
     packages = ['git_remote_helpers', 'git_remote_helpers.git'],
+    cmdclass = {'build_py': build_py},
 )
diff --git a/gitk-git/.gitignore b/gitk-git/.gitignore
new file mode 100644 (file)
index 0000000..d7ebcaf
--- /dev/null
@@ -0,0 +1,2 @@
+/GIT-TCLTK-VARS
+/gitk-wish
index e1b6045605865cbfc4ae0d57039111d5df825649..5acdc900abdfb3ccc1ad7616fecb098ce6f060e2 100644 (file)
@@ -17,6 +17,16 @@ DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 bindir_SQ = $(subst ','\'',$(bindir))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
+### Detect Tck/Tk interpreter path changes
+TRACK_TCLTK = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
+
+GIT-TCLTK-VARS: FORCE
+       @VARS='$(TRACK_TCLTK)'; \
+               if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
+                       echo 1>&2 "    * new Tcl/Tk interpreter location"; \
+                       echo "$$VARS" >$@; \
+               fi
+
 ## po-file creation rules
 XGETTEXT   ?= xgettext
 ifdef NO_MSGFMT
@@ -49,9 +59,9 @@ uninstall::
        $(RM) '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
 
 clean::
-       $(RM) gitk-wish po/*.msg
+       $(RM) gitk-wish po/*.msg GIT-TCLTK-VARS
 
-gitk-wish: gitk
+gitk-wish: gitk GIT-TCLTK-VARS
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
        chmod +x $@+ && \
@@ -65,3 +75,5 @@ $(ALL_MSGFILES): %.msg : %.po
        @echo Generating catalog $@
        $(MSGFMT) --statistics --tcl $< -l $(basename $(notdir $<)) -d $(dir $@)
 
+.PHONY: all install uninstall clean update-po
+.PHONY: FORCE
index d93bd990a9d7fc9ad7c926e25f5d58f4e7514260..b3706fc9b9bef1361f6872b00905443dea584a7d 100755 (executable)
@@ -2161,7 +2161,7 @@ proc makewindow {} {
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
 
-    image create bitmap bm-left -data {
+    set bm_left_data {
        #define left_width 16
        #define left_height 16
        static unsigned char left_bits[] = {
@@ -2169,7 +2169,7 @@ proc makewindow {} {
        0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
        0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
     }
-    image create bitmap bm-right -data {
+    set bm_right_data {
        #define right_width 16
        #define right_height 16
        static unsigned char right_bits[] = {
@@ -2177,11 +2177,24 @@ proc makewindow {} {
        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
     }
-    ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
-       -state disabled -width 26
+    image create bitmap bm-left -data $bm_left_data
+    image create bitmap bm-left-gray -data $bm_left_data -foreground "#999"
+    image create bitmap bm-right -data $bm_right_data
+    image create bitmap bm-right-gray -data $bm_right_data -foreground "#999"
+
+    ${NS}::button .tf.bar.leftbut -command goback -state disabled -width 26
+    if {$use_ttk} {
+       .tf.bar.leftbut configure -image [list bm-left disabled bm-left-gray]
+    } else {
+       .tf.bar.leftbut configure -image bm-left
+    }
     pack .tf.bar.leftbut -side left -fill y
-    ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
-       -state disabled -width 26
+    ${NS}::button .tf.bar.rightbut -command goforw -state disabled -width 26
+    if {$use_ttk} {
+       .tf.bar.rightbut configure -image [list bm-right disabled bm-right-gray]
+    } else {
+       .tf.bar.rightbut configure -image bm-right
+    }
     pack .tf.bar.rightbut -side left -fill y
 
     ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
@@ -2361,6 +2374,8 @@ proc makewindow {} {
     $ctext tag conf mresult -font textfontbold
     $ctext tag conf msep -font textfontbold
     $ctext tag conf found -back yellow
+    $ctext tag conf currentsearchhit -back orange
+    $ctext tag conf wwrap -wrap word
 
     .pwbottom add .bleft
     if {!$use_ttk} {
@@ -2495,10 +2510,9 @@ proc makewindow {} {
     bindkey ? {dofind -1 1}
     bindkey f nextfile
     bind . <F5> updatecommits
-    bind . <Shift-F5> reloadcommits
+    bindmodfunctionkey Shift 5 reloadcommits
     bind . <F2> showrefs
-    bind . <Shift-F4> {newview 0}
-    catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
+    bindmodfunctionkey Shift 4 {newview 0}
     bind . <F4> edit_or_newview
     bind . <$M1B-q> doquit
     bind . <$M1B-f> {dofind 1 1}
@@ -2523,6 +2537,7 @@ proc makewindow {} {
     bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
     bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
     bind $ctext <Button-1> {focus %W}
+    bind $ctext <<Selection>> rehighlight_search_results
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -2646,6 +2661,11 @@ proc bindkey {ev script} {
     }
 }
 
+proc bindmodfunctionkey {mod n script} {
+    bind . <$mod-F$n> $script
+    catch { bind . <$mod-XF86_Switch_VT_$n> $script }
+}
+
 # set the focus back to the toplevel for any click outside
 # the entry widgets
 proc click {w} {
@@ -2702,7 +2722,7 @@ proc savestuff {w} {
     global cmitmode wrapcomment datetimeformat limitdiffs
     global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
     global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
-    global hideremotes want_ttk
+    global hideremotes want_ttk maxrefs
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -2724,6 +2744,7 @@ proc savestuff {w} {
        puts $f [list set autoselect $autoselect]
        puts $f [list set autosellen $autosellen]
        puts $f [list set showneartags $showneartags]
+       puts $f [list set maxrefs $maxrefs]
        puts $f [list set hideremotes $hideremotes]
        puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set datetimeformat $datetimeformat]
@@ -3309,6 +3330,7 @@ proc sel_flist {w x y} {
     } else {
        catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
     }
+    suppress_highlighting_file_for_current_scrollpos
 }
 
 proc pop_flist_menu {w X Y x y} {
@@ -6857,7 +6879,7 @@ proc viewnextline {dir} {
 # add a list of tag or branch names at position pos
 # returns the number of names inserted
 proc appendrefs {pos ids var} {
-    global ctext linknum curview $var maxrefs
+    global ctext linknum curview $var maxrefs mainheadid
 
     if {[catch {$ctext index $pos}]} {
        return 0
@@ -6870,24 +6892,54 @@ proc appendrefs {pos ids var} {
            lappend tags [list $tag $id]
        }
     }
+
+    set sep {}
+    set tags [lsort -index 0 -decreasing $tags]
+    set nutags 0
+
     if {[llength $tags] > $maxrefs} {
-       $ctext insert $pos "[mc "many"] ([llength $tags])"
-    } else {
-       set tags [lsort -index 0 -decreasing $tags]
-       set sep {}
-       foreach ti $tags {
-           set id [lindex $ti 1]
-           set lk link$linknum
-           incr linknum
-           $ctext tag delete $lk
-           $ctext insert $pos $sep
-           $ctext insert $pos [lindex $ti 0] $lk
-           setlink $id $lk
-           set sep ", "
+       # If we are displaying heads, and there are too many,
+       # see if there are some important heads to display.
+       # Currently this means "master" and the current head.
+       set itags {}
+       if {$var eq "idheads"} {
+           set utags {}
+           foreach ti $tags {
+               set hname [lindex $ti 0]
+               set id [lindex $ti 1]
+               if {($hname eq "master" || $id eq $mainheadid) &&
+                   [llength $itags] < $maxrefs} {
+                   lappend itags $ti
+               } else {
+                   lappend utags $ti
+               }
+           }
+           set tags $utags
+       }
+       if {$itags ne {}} {
+           set str [mc "and many more"]
+           set sep " "
+       } else {
+           set str [mc "many"]
        }
+       $ctext insert $pos "$str ([llength $tags])"
+       set nutags [llength $tags]
+       set tags $itags
+    }
+
+    foreach ti $tags {
+       set id [lindex $ti 1]
+       set lk link$linknum
+       incr linknum
+       $ctext tag delete $lk
+       $ctext insert $pos $sep
+       $ctext insert $pos [lindex $ti 0] $lk
+       setlink $id $lk
+       set sep ", "
     }
+    $ctext tag add wwrap "$pos linestart" "$pos lineend"
     $ctext conf -state disabled
-    return [llength $tags]
+    return [expr {[llength $tags] + $nutags}]
 }
 
 # called when we have finished computing the nearby tags
@@ -7947,32 +7999,45 @@ proc changediffdisp {} {
     $ctext tag conf dresult -elide [lindex $diffelide 1]
 }
 
-proc highlightfile {loc cline} {
-    global ctext cflist cflist_top
+proc highlightfile {cline} {
+    global cflist cflist_top
+
+    if {![info exists cflist_top]} return
 
-    $ctext yview $loc
     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
     $cflist tag add highlight $cline.0 "$cline.0 lineend"
     $cflist see $cline.0
     set cflist_top $cline
 }
 
+proc highlightfile_for_scrollpos {topidx} {
+    global cmitmode difffilestart
+
+    if {$cmitmode eq "tree"} return
+    if {![info exists difffilestart]} return
+
+    set top [lindex [split $topidx .] 0]
+    if {$difffilestart eq {} || $top < [lindex $difffilestart 0]} {
+       highlightfile 0
+    } else {
+       highlightfile [expr {[bsearch $difffilestart $top] + 2}]
+    }
+}
+
 proc prevfile {} {
     global difffilestart ctext cmitmode
 
     if {$cmitmode eq "tree"} return
     set prev 0.0
-    set prevline 1
     set here [$ctext index @0,0]
     foreach loc $difffilestart {
        if {[$ctext compare $loc >= $here]} {
-           highlightfile $prev $prevline
+           $ctext yview $prev
            return
        }
        set prev $loc
-       incr prevline
     }
-    highlightfile $prev $prevline
+    $ctext yview $prev
 }
 
 proc nextfile {} {
@@ -7980,11 +8045,9 @@ proc nextfile {} {
 
     if {$cmitmode eq "tree"} return
     set here [$ctext index @0,0]
-    set line 1
     foreach loc $difffilestart {
-       incr line
        if {[$ctext compare $loc > $here]} {
-           highlightfile $loc $line
+           $ctext yview $loc
            return
        }
     }
@@ -8030,7 +8093,6 @@ proc settabs {{firstab {}}} {
 proc incrsearch {name ix op} {
     global ctext searchstring searchdirn
 
-    $ctext tag remove found 1.0 end
     if {[catch {$ctext index anchor}]} {
        # no anchor set, use start of selection, or of visible area
        set sel [$ctext tag ranges sel]
@@ -8043,12 +8105,17 @@ proc incrsearch {name ix op} {
        }
     }
     if {$searchstring ne {}} {
-       set here [$ctext search $searchdirn -- $searchstring anchor]
+       set here [$ctext search -count mlen $searchdirn -- $searchstring anchor]
        if {$here ne {}} {
            $ctext see $here
+           set mend "$here + $mlen c"
+           $ctext tag remove sel 1.0 end
+           $ctext tag add sel $here $mend
+           suppress_highlighting_file_for_current_scrollpos
+           highlightfile_for_scrollpos $here
        }
-       searchmarkvisible 1
     }
+    rehighlight_search_results
 }
 
 proc dosearch {} {
@@ -8071,9 +8138,12 @@ proc dosearch {} {
            return
        }
        $ctext see $match
+       suppress_highlighting_file_for_current_scrollpos
+       highlightfile_for_scrollpos $match
        set mend "$match + $mlen c"
        $ctext tag add sel $match $mend
        $ctext mark unset anchor
+       rehighlight_search_results
     }
 }
 
@@ -8097,21 +8167,41 @@ proc dosearchback {} {
            return
        }
        $ctext see $match
+       suppress_highlighting_file_for_current_scrollpos
+       highlightfile_for_scrollpos $match
        set mend "$match + $ml c"
        $ctext tag add sel $match $mend
        $ctext mark unset anchor
+       rehighlight_search_results
+    }
+}
+
+proc rehighlight_search_results {} {
+    global ctext searchstring
+
+    $ctext tag remove found 1.0 end
+    $ctext tag remove currentsearchhit 1.0 end
+
+    if {$searchstring ne {}} {
+       searchmarkvisible 1
     }
 }
 
 proc searchmark {first last} {
     global ctext searchstring
 
+    set sel [$ctext tag ranges sel]
+
     set mend $first.0
     while {1} {
        set match [$ctext search -count mlen -- $searchstring $mend $last.end]
        if {$match eq {}} break
        set mend "$match + $mlen c"
-       $ctext tag add found $match $mend
+       if {$sel ne {} && [$ctext compare $match == [lindex $sel 0]]} {
+           $ctext tag add currentsearchhit $match $mend
+       } else {
+           $ctext tag add found $match $mend
+       }
     }
 }
 
@@ -8137,8 +8227,23 @@ proc searchmarkvisible {doall} {
     }
 }
 
+proc suppress_highlighting_file_for_current_scrollpos {} {
+    global ctext suppress_highlighting_file_for_this_scrollpos
+
+    set suppress_highlighting_file_for_this_scrollpos [$ctext index @0,0]
+}
+
 proc scrolltext {f0 f1} {
-    global searchstring
+    global searchstring cmitmode ctext
+    global suppress_highlighting_file_for_this_scrollpos
+
+    set topidx [$ctext index @0,0]
+    if {![info exists suppress_highlighting_file_for_this_scrollpos]
+       || $topidx ne $suppress_highlighting_file_for_this_scrollpos} {
+       highlightfile_for_scrollpos $topidx
+    }
+
+    catch {unset suppress_highlighting_file_for_this_scrollpos}
 
     .bleft.bottom.sb set $f0 $f1
     if {$searchstring ne {}} {
@@ -10509,13 +10614,13 @@ proc anctags {id} {
 # including id itself if it has a head.
 proc descheads {id} {
     global arcnos arcstart arcids archeads idheads cached_dheads
-    global allparents
+    global allparents arcout
 
     if {![info exists allparents($id)]} {
        return {}
     }
     set aret {}
-    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+    if {![info exists arcout($id)]} {
        # part-way along an arc; check it first
        set a [lindex $arcnos($id) 0]
        if {$archeads($a) ne {}} {
@@ -10864,7 +10969,7 @@ proc create_prefs_page {w} {
 proc prefspage_general {notebook} {
     global NS maxwidth maxgraphpct showneartags showlocalchanges
     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
-    global hideremotes want_ttk have_ttk
+    global hideremotes want_ttk have_ttk maxrefs
 
     set page [create_prefs_page $notebook.general]
 
@@ -10893,9 +10998,12 @@ proc prefspage_general {notebook} {
     ${NS}::label $page.tabstopl -text [mc "Tab spacing"]
     spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
     grid x $page.tabstopl $page.tabstop -sticky w
-    ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags"] \
+    ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags/heads"] \
        -variable showneartags
     grid x $page.ntag -sticky w
+    ${NS}::label $page.maxrefsl -text [mc "Maximum # tags/heads to show"]
+    spinbox $page.maxrefs -from 1 -to 1000 -width 4 -textvariable maxrefs
+    grid x $page.maxrefsl $page.maxrefs -sticky w
     ${NS}::checkbutton $page.ldiff -text [mc "Limit diffs to listed paths"] \
        -variable limitdiffs
     grid x $page.ldiff -sticky w
index 2f07a2e2d93d655cfe5cf2d3535e7b86422233c6..8cc98dc0794d6a6fae196b4fa17b922ca5394436 100644 (file)
@@ -1,40 +1,50 @@
 # Swedish translation for gitk
-# Copyright (C) 2005-2010 Paul Mackerras
+# Copyright (C) 2005-2012 Paul Mackerras
 # This file is distributed under the same license as the gitk package.
 #
-# Peter Krefting <peter@softwolves.pp.se>, 2008-2010.
 # Mikael Magnusson <mikachu@gmail.com>, 2008.
+# Peter Krefting <peter@softwolves.pp.se>, 2008, 2009, 2010, 2012.
+#
 msgid ""
 msgstr ""
 "Project-Id-Version: sv\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-09-12 21:14+0100\n"
-"PO-Revision-Date: 2010-09-12 21:16+0100\n"
+"POT-Creation-Date: 2012-10-03 08:09+0100\n"
+"PO-Revision-Date: 2012-10-03 08:13+0100\n"
 "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
 "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
+"Language: sv\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit"
+"Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:115
+#: gitk:140
 msgid "Couldn't get list of unmerged files:"
 msgstr "Kunde inte hämta lista Ã¶ver ej sammanslagna filer:"
 
-#: gitk:274
+#: gitk:210 gitk:2317
+msgid "Color words"
+msgstr "Färga ord"
+
+#: gitk:215 gitk:2317 gitk:7888 gitk:7921
+msgid "Markup words"
+msgstr "Märk upp ord"
+
+#: gitk:312
 msgid "Error parsing revisions:"
 msgstr "Fel vid tolkning av revisioner:"
 
-#: gitk:330
+#: gitk:368
 msgid "Error executing --argscmd command:"
 msgstr "Fel vid körning av --argscmd-kommando:"
 
-#: gitk:343
+#: gitk:381
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer som inte har "
 "slagits samman."
 
-#: gitk:346
+#: gitk:384
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -42,605 +52,617 @@ msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer inom "
 "filbegränsningen."
 
-#: gitk:368 gitk:516
+#: gitk:406 gitk:554
 msgid "Error executing git log:"
 msgstr "Fel vid körning av git log:"
 
-#: gitk:386 gitk:532
+#: gitk:424 gitk:570
 msgid "Reading"
 msgstr "Läser"
 
-#: gitk:446 gitk:4271
+#: gitk:484 gitk:4353
 msgid "Reading commits..."
 msgstr "Läser incheckningar..."
 
-#: gitk:449 gitk:1580 gitk:4274
+#: gitk:487 gitk:1625 gitk:4356
 msgid "No commits selected"
 msgstr "Inga incheckningar markerade"
 
-#: gitk:1456
+#: gitk:1499
 msgid "Can't parse git log output:"
 msgstr "Kan inte tolka utdata frÃ¥n git log:"
 
-#: gitk:1676
+#: gitk:1719
 msgid "No commit information available"
 msgstr "Ingen incheckningsinformation Ã¤r tillgänglig"
 
-#: gitk:1818
+#: gitk:1876
 msgid "mc"
 msgstr "mc"
 
-#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
+#: gitk:1911 gitk:4146 gitk:9282 gitk:10824 gitk:11100
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
-#: gitk:10608 gitk:10818
+#: gitk:1913 gitk:4148 gitk:8871 gitk:8950 gitk:9065 gitk:9114 gitk:9284
+#: gitk:10825 gitk:11101
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: gitk:1980
+#: gitk:2040
 msgid "Update"
 msgstr "Uppdatera"
 
-#: gitk:1981
+#: gitk:2041
 msgid "Reload"
 msgstr "Ladda om"
 
-#: gitk:1982
+#: gitk:2042
 msgid "Reread references"
 msgstr "Läs om referenser"
 
-#: gitk:1983
+#: gitk:2043
 msgid "List references"
 msgstr "Visa referenser"
 
-#: gitk:1985
+#: gitk:2045
 msgid "Start git gui"
 msgstr "Starta git gui"
 
-#: gitk:1987
+#: gitk:2047
 msgid "Quit"
 msgstr "Avsluta"
 
-#: gitk:1979
+#: gitk:2039
 msgid "File"
 msgstr "Arkiv"
 
-#: gitk:1991
+#: gitk:2051
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: gitk:1990
+#: gitk:2050
 msgid "Edit"
 msgstr "Redigera"
 
-#: gitk:1995
+#: gitk:2055
 msgid "New view..."
 msgstr "Ny vy..."
 
-#: gitk:1996
+#: gitk:2056
 msgid "Edit view..."
 msgstr "Ändra vy..."
 
-#: gitk:1997
+#: gitk:2057
 msgid "Delete view"
 msgstr "Ta bort vy"
 
-#: gitk:1999
+#: gitk:2059
 msgid "All files"
 msgstr "Alla filer"
 
-#: gitk:1994 gitk:3817
+#: gitk:2054 gitk:3899
 msgid "View"
 msgstr "Visa"
 
-#: gitk:2004 gitk:2014 gitk:2787
+#: gitk:2064 gitk:2074 gitk:2869
 msgid "About gitk"
 msgstr "Om gitk"
 
-#: gitk:2005 gitk:2019
+#: gitk:2065 gitk:2079
 msgid "Key bindings"
 msgstr "Tangentbordsbindningar"
 
-#: gitk:2003 gitk:2018
+#: gitk:2063 gitk:2078
 msgid "Help"
 msgstr "Hjälp"
 
-#: gitk:2096 gitk:8132
+#: gitk:2156 gitk:8330
 msgid "SHA1 ID:"
 msgstr "SHA1-id:"
 
-#: gitk:2127
+#: gitk:2192
 msgid "Row"
 msgstr "Rad"
 
-#: gitk:2165
+#: gitk:2230
 msgid "Find"
 msgstr "Sök"
 
-#: gitk:2166
+#: gitk:2231
 msgid "next"
 msgstr "nästa"
 
-#: gitk:2167
+#: gitk:2232
 msgid "prev"
 msgstr "föreg"
 
-#: gitk:2168
+#: gitk:2233
 msgid "commit"
 msgstr "incheckning"
 
-#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
-#: gitk:6576
+#: gitk:2236 gitk:2238 gitk:4514 gitk:4537 gitk:4561 gitk:6528 gitk:6600
+#: gitk:6685
 msgid "containing:"
 msgstr "som innehÃ¥ller:"
 
-#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
+#: gitk:2239 gitk:3381 gitk:3386 gitk:4590
 msgid "touching paths:"
 msgstr "som rör sökväg:"
 
-#: gitk:2175 gitk:4512
+#: gitk:2240 gitk:4604
 msgid "adding/removing string:"
 msgstr "som lägger/till tar bort sträng:"
 
-#: gitk:2184 gitk:2186
+#: gitk:2249 gitk:2251 gitk:4593
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:2186 gitk:4587 gitk:6388
+#: gitk:2251 gitk:4679 gitk:6496
 msgid "IgnCase"
 msgstr "IgnVersaler"
 
-#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
+#: gitk:2251 gitk:4563 gitk:4677 gitk:6492
 msgid "Regexp"
 msgstr "Reg.uttr."
 
-#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
+#: gitk:2253 gitk:2254 gitk:4699 gitk:4729 gitk:4736 gitk:6621 gitk:6689
 msgid "All fields"
 msgstr "Alla fält"
 
-#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
+#: gitk:2254 gitk:4696 gitk:4729 gitk:6559
 msgid "Headline"
 msgstr "Rubrik"
 
-#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
+#: gitk:2255 gitk:4696 gitk:6559 gitk:6689 gitk:7126
 msgid "Comments"
 msgstr "Kommentarer"
 
-#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
-#: gitk:8322
+#: gitk:2255 gitk:4696 gitk:4701 gitk:4736 gitk:6559 gitk:7061 gitk:8505
+#: gitk:8520
 msgid "Author"
 msgstr "Författare"
 
-#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
+#: gitk:2255 gitk:4696 gitk:6559 gitk:7063
 msgid "Committer"
 msgstr "Incheckare"
 
-#: gitk:2221
+#: gitk:2286
 msgid "Search"
 msgstr "Sök"
 
-#: gitk:2229
+#: gitk:2294
 msgid "Diff"
 msgstr "Diff"
 
-#: gitk:2231
+#: gitk:2296
 msgid "Old version"
 msgstr "Gammal version"
 
-#: gitk:2233
+#: gitk:2298
 msgid "New version"
 msgstr "Ny version"
 
-#: gitk:2235
+#: gitk:2300
 msgid "Lines of context"
 msgstr "Rader sammanhang"
 
-#: gitk:2245
+#: gitk:2310
 msgid "Ignore space change"
 msgstr "Ignorera Ã¤ndringar i blanksteg"
 
-#: gitk:2304
+#: gitk:2314 gitk:2316 gitk:7646 gitk:7874
+msgid "Line diff"
+msgstr "Rad-diff"
+
+#: gitk:2379
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2306
+#: gitk:2381
 msgid "Tree"
 msgstr "Träd"
 
-#: gitk:2463 gitk:2480
+#: gitk:2540 gitk:2559
 msgid "Diff this -> selected"
 msgstr "Diff denna -> markerad"
 
-#: gitk:2464 gitk:2481
+#: gitk:2541 gitk:2560
 msgid "Diff selected -> this"
 msgstr "Diff markerad -> denna"
 
-#: gitk:2465 gitk:2482
+#: gitk:2542 gitk:2561
 msgid "Make patch"
 msgstr "Skapa patch"
 
-#: gitk:2466 gitk:8715
+#: gitk:2543 gitk:8929
 msgid "Create tag"
 msgstr "Skapa tagg"
 
-#: gitk:2467 gitk:8831
+#: gitk:2544 gitk:9045
 msgid "Write commit to file"
 msgstr "Skriv incheckning till fil"
 
-#: gitk:2468 gitk:8888
+#: gitk:2545 gitk:9102
 msgid "Create new branch"
 msgstr "Skapa ny gren"
 
-#: gitk:2469
+#: gitk:2546
 msgid "Cherry-pick this commit"
 msgstr "Plocka denna incheckning"
 
-#: gitk:2470
+#: gitk:2547
 msgid "Reset HEAD branch to here"
 msgstr "Ã…terställ HEAD-grenen hit"
 
-#: gitk:2471
+#: gitk:2548
 msgid "Mark this commit"
 msgstr "Markera denna incheckning"
 
-#: gitk:2472
+#: gitk:2549
 msgid "Return to mark"
 msgstr "Ã…tergÃ¥ till markering"
 
-#: gitk:2473
+#: gitk:2550
 msgid "Find descendant of this and mark"
 msgstr "Hitta efterföljare till denna och markera"
 
-#: gitk:2474
+#: gitk:2551
 msgid "Compare with marked commit"
 msgstr "Jämför med markerad incheckning"
 
-#: gitk:2488
+#: gitk:2552 gitk:2562
+msgid "Diff this -> marked commit"
+msgstr "Diff denna -> markerad incheckning"
+
+#: gitk:2553 gitk:2563
+msgid "Diff marked commit -> this"
+msgstr "Diff markerad incheckning -> denna"
+
+#: gitk:2569
 msgid "Check out this branch"
 msgstr "Checka ut denna gren"
 
-#: gitk:2489
+#: gitk:2570
 msgid "Remove this branch"
 msgstr "Ta bort denna gren"
 
-#: gitk:2496
+#: gitk:2577
 msgid "Highlight this too"
 msgstr "Markera Ã¤ven detta"
 
-#: gitk:2497
+#: gitk:2578
 msgid "Highlight this only"
 msgstr "Markera bara detta"
 
-#: gitk:2498
+#: gitk:2579
 msgid "External diff"
 msgstr "Extern diff"
 
-#: gitk:2499
+#: gitk:2580
 msgid "Blame parent commit"
 msgstr "Klandra föräldraincheckning"
 
-#: gitk:2506
+#: gitk:2587
 msgid "Show origin of this line"
 msgstr "Visa ursprunget för den här raden"
 
-#: gitk:2507
+#: gitk:2588
 msgid "Run git gui blame on this line"
 msgstr "Kör git gui blame pÃ¥ den här raden"
 
-#: gitk:2789
+#: gitk:2871
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright Â©9 2005-2010 Paul Mackerras\n"
+"Copyright Â©9 2005-2011 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - en incheckningsvisare för git\n"
 "\n"
-"Copyright Â©9 2005-2010 Paul Mackerras\n"
+"Copyright Â©9 2005-2011 Paul Mackerras\n"
 "\n"
 "Använd och vidareförmedla enligt villkoren i GNU General Public License"
 
-#: gitk:2797 gitk:2862 gitk:9253
+#: gitk:2879 gitk:2944 gitk:9468
 msgid "Close"
 msgstr "Stäng"
 
-#: gitk:2818
+#: gitk:2900
 msgid "Gitk key bindings"
 msgstr "Tangentbordsbindningar för Gitk"
 
-#: gitk:2821
+#: gitk:2903
 msgid "Gitk key bindings:"
 msgstr "Tangentbordsbindningar för Gitk:"
 
-#: gitk:2823
+#: gitk:2905
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tAvsluta"
 
-#: gitk:2824
+#: gitk:2906
 #, tcl-format
 msgid "<%s-W>\t\tClose window"
 msgstr "<%s-W>\t\tStäng fönster"
 
-#: gitk:2825
+#: gitk:2907
 msgid "<Home>\t\tMove to first commit"
 msgstr "<Home>\t\tGÃ¥ till första incheckning"
 
-#: gitk:2826
+#: gitk:2908
 msgid "<End>\t\tMove to last commit"
 msgstr "<End>\t\tGÃ¥ till sista incheckning"
 
-#: gitk:2827
-msgid "<Up>, p, i\tMove up one commit"
-msgstr "<Upp>, p, i\tGÃ¥ en incheckning upp"
+#: gitk:2909
+msgid "<Up>, p, k\tMove up one commit"
+msgstr "<Upp>, p, k\tGÃ¥ en incheckning upp"
 
-#: gitk:2828
-msgid "<Down>, n, k\tMove down one commit"
-msgstr "<Ned>, n, k\tGÃ¥ en incheckning ned"
+#: gitk:2910
+msgid "<Down>, n, j\tMove down one commit"
+msgstr "<Ned>, n, j\tGÃ¥ en incheckning ned"
 
-#: gitk:2829
-msgid "<Left>, z, j\tGo back in history list"
-msgstr "<Vänster>, z, j\tGÃ¥ bakÃ¥t i historiken"
+#: gitk:2911
+msgid "<Left>, z, h\tGo back in history list"
+msgstr "<Vänster>, z, h\tGÃ¥ bakÃ¥t i historiken"
 
-#: gitk:2830
+#: gitk:2912
 msgid "<Right>, x, l\tGo forward in history list"
 msgstr "<Höger>, x, l\tGÃ¥ framÃ¥t i historiken"
 
-#: gitk:2831
+#: gitk:2913
 msgid "<PageUp>\tMove up one page in commit list"
 msgstr "<PageUp>\tGÃ¥ upp en sida i incheckningslistan"
 
-#: gitk:2832
+#: gitk:2914
 msgid "<PageDown>\tMove down one page in commit list"
 msgstr "<PageDown>\tGÃ¥ ned en sida i incheckningslistan"
 
-#: gitk:2833
+#: gitk:2915
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tRulla till början av incheckningslistan"
 
-#: gitk:2834
+#: gitk:2916
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
 
-#: gitk:2835
+#: gitk:2917
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
 
-#: gitk:2836
+#: gitk:2918
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
 
-#: gitk:2837
+#: gitk:2919
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
 
-#: gitk:2838
+#: gitk:2920
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
 
-#: gitk:2839
+#: gitk:2921
 msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
 msgstr "<Skift-Upp>\tSök bakÃ¥t (uppÃ¥t, senare incheckningar)"
 
-#: gitk:2840
+#: gitk:2922
 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
 msgstr "<Skift-Ned>\tSök framÃ¥t (nedÃ¥t, tidigare incheckningar)"
 
-#: gitk:2841
+#: gitk:2923
 msgid "<Delete>, b\tScroll diff view up one page"
 msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
 
-#: gitk:2842
+#: gitk:2924
 msgid "<Backspace>\tScroll diff view up one page"
 msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
 
-#: gitk:2843
+#: gitk:2925
 msgid "<Space>\t\tScroll diff view down one page"
 msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
 
-#: gitk:2844
+#: gitk:2926
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tRulla diffvisningen upp 18 rader"
 
-#: gitk:2845
+#: gitk:2927
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tRulla diffvisningen ned 18 rader"
 
-#: gitk:2846
+#: gitk:2928
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSök"
 
-#: gitk:2847
+#: gitk:2929
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tGÃ¥ till nästa sökträff"
 
-#: gitk:2848
+#: gitk:2930
 msgid "<Return>\tMove to next find hit"
 msgstr "<Return>\t\tGÃ¥ till nästa sökträff"
 
-#: gitk:2849
+#: gitk:2931
 msgid "/\t\tFocus the search box"
 msgstr "/\t\tFokusera sökrutan"
 
-#: gitk:2850
+#: gitk:2932
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tGÃ¥ till föregÃ¥ende sökträff"
 
-#: gitk:2851
+#: gitk:2933
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tRulla diffvisningen till nästa fil"
 
-#: gitk:2852
+#: gitk:2934
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tGÃ¥ till nästa sökträff i diffvisningen"
 
-#: gitk:2853
+#: gitk:2935
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tGÃ¥ till föregÃ¥ende sökträff i diffvisningen"
 
-#: gitk:2854
+#: gitk:2936
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Num+>\tÖka teckenstorlek"
 
-#: gitk:2855
+#: gitk:2937
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tÖka teckenstorlek"
 
-#: gitk:2856
+#: gitk:2938
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Num->\tMinska teckenstorlek"
 
-#: gitk:2857
+#: gitk:2939
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tMinska teckenstorlek"
 
-#: gitk:2858
+#: gitk:2940
 msgid "<F5>\t\tUpdate"
 msgstr "<F5>\t\tUppdatera"
 
-#: gitk:3313 gitk:3322
+#: gitk:3395 gitk:3404
 #, tcl-format
 msgid "Error creating temporary directory %s:"
 msgstr "Fel vid skapande av temporär katalog %s:"
 
-#: gitk:3335
+#: gitk:3417
 #, tcl-format
 msgid "Error getting \"%s\" from %s:"
 msgstr "Fel vid hämtning av  \"%s\" frÃ¥n %s:"
 
-#: gitk:3398
+#: gitk:3480
 msgid "command failed:"
 msgstr "kommando misslyckades:"
 
-#: gitk:3547
+#: gitk:3629
 msgid "No such commit"
 msgstr "Incheckning saknas"
 
-#: gitk:3561
+#: gitk:3643
 msgid "git gui blame: command failed:"
 msgstr "git gui blame: kommando misslyckades:"
 
-#: gitk:3592
+#: gitk:3674
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Kunde inte läsa sammanslagningshuvud: %s"
 
-#: gitk:3600
+#: gitk:3682
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Fel vid läsning av index: %s"
 
-#: gitk:3625
+#: gitk:3707
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "Kunde inte starta git blame: %s"
 
-#: gitk:3628 gitk:6419
+#: gitk:3710 gitk:6527
 msgid "Searching"
 msgstr "Söker"
 
-#: gitk:3660
+#: gitk:3742
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Fel vid körning av git blame: %s"
 
-#: gitk:3688
+#: gitk:3770
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr "Raden kommer frÃ¥n incheckningen %s, som inte finns i denna vy"
 
-#: gitk:3702
+#: gitk:3784
 msgid "External diff viewer failed:"
 msgstr "Externt diff-verktyg misslyckades:"
 
-#: gitk:3820
+#: gitk:3902
 msgid "Gitk view definition"
 msgstr "Definition av Gitk-vy"
 
-#: gitk:3824
+#: gitk:3906
 msgid "Remember this view"
 msgstr "Spara denna vy"
 
-#: gitk:3825
+#: gitk:3907
 msgid "References (space separated list):"
 msgstr "Referenser (blankstegsavdelad lista):"
 
-#: gitk:3826
+#: gitk:3908
 msgid "Branches & tags:"
 msgstr "Grenar & taggar:"
 
-#: gitk:3827
+#: gitk:3909
 msgid "All refs"
 msgstr "Alla referenser"
 
-#: gitk:3828
+#: gitk:3910
 msgid "All (local) branches"
 msgstr "Alla (lokala) grenar"
 
-#: gitk:3829
+#: gitk:3911
 msgid "All tags"
 msgstr "Alla taggar"
 
-#: gitk:3830
+#: gitk:3912
 msgid "All remote-tracking branches"
 msgstr "Alla fjärrspÃ¥rande grenar"
 
-#: gitk:3831
+#: gitk:3913
 msgid "Commit Info (regular expressions):"
 msgstr "Incheckningsinfo (reguljära uttryck):"
 
-#: gitk:3832
+#: gitk:3914
 msgid "Author:"
 msgstr "Författare:"
 
-#: gitk:3833
+#: gitk:3915
 msgid "Committer:"
 msgstr "Incheckare:"
 
-#: gitk:3834
+#: gitk:3916
 msgid "Commit Message:"
 msgstr "Incheckningsmeddelande:"
 
-#: gitk:3835
+#: gitk:3917
 msgid "Matches all Commit Info criteria"
 msgstr "Motsvarar alla kriterier för incheckningsinfo"
 
-#: gitk:3836
+#: gitk:3918
 msgid "Changes to Files:"
 msgstr "Ändringar av filer:"
 
-#: gitk:3837
+#: gitk:3919
 msgid "Fixed String"
 msgstr "Fast sträng"
 
-#: gitk:3838
+#: gitk:3920
 msgid "Regular Expression"
 msgstr "Reguljärt uttryck"
 
-#: gitk:3839
+#: gitk:3921
 msgid "Search string:"
 msgstr "Söksträng:"
 
-#: gitk:3840
+#: gitk:3922
 msgid ""
 "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
@@ -648,201 +670,197 @@ msgstr ""
 "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
 
-#: gitk:3841
+#: gitk:3923
 msgid "Since:"
 msgstr "FrÃ¥n:"
 
-#: gitk:3842
+#: gitk:3924
 msgid "Until:"
 msgstr "Till:"
 
-#: gitk:3843
+#: gitk:3925
 msgid "Limit and/or skip a number of revisions (positive integer):"
 msgstr "Begränsa och/eller hoppa Ã¶ver ett antal revisioner (positivt heltal):"
 
-#: gitk:3844
+#: gitk:3926
 msgid "Number to show:"
 msgstr "Antal att visa:"
 
-#: gitk:3845
+#: gitk:3927
 msgid "Number to skip:"
 msgstr "Antal att hoppa Ã¶ver:"
 
-#: gitk:3846
+#: gitk:3928
 msgid "Miscellaneous options:"
 msgstr "Diverse alternativ:"
 
-#: gitk:3847
+#: gitk:3929
 msgid "Strictly sort by date"
 msgstr "Strikt datumsortering"
 
-#: gitk:3848
+#: gitk:3930
 msgid "Mark branch sides"
 msgstr "Markera sidogrenar"
 
-#: gitk:3849
+#: gitk:3931
 msgid "Limit to first parent"
 msgstr "Begränsa till första förälder"
 
-#: gitk:3850
+#: gitk:3932
 msgid "Simple history"
 msgstr "Enkel historik"
 
-#: gitk:3851
+#: gitk:3933
 msgid "Additional arguments to git log:"
 msgstr "Ytterligare argument till git log:"
 
-#: gitk:3852
+#: gitk:3934
 msgid "Enter files and directories to include, one per line:"
 msgstr "Ange filer och kataloger att ta med, en per rad:"
 
-#: gitk:3853
+#: gitk:3935
 msgid "Command to generate more commits to include:"
 msgstr "Kommando för att generera fler incheckningar att ta med:"
 
-#: gitk:3977
+#: gitk:4059
 msgid "Gitk: edit view"
 msgstr "Gitk: redigera vy"
 
-#: gitk:3985
+#: gitk:4067
 msgid "-- criteria for selecting revisions"
 msgstr " - kriterier för val av revisioner"
 
-#: gitk:3990
+#: gitk:4072
 msgid "View Name"
 msgstr "Namn pÃ¥ vy"
 
-#: gitk:4065
+#: gitk:4147
 msgid "Apply (F5)"
 msgstr "Använd (F5)"
 
-#: gitk:4103
+#: gitk:4185
 msgid "Error in commit selection arguments:"
 msgstr "Fel i argument för val av incheckningar:"
 
-#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
+#: gitk:4238 gitk:4290 gitk:4749 gitk:4763 gitk:6027 gitk:11849 gitk:11850
 msgid "None"
 msgstr "Inget"
 
-#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
-msgid "Date"
-msgstr "Datum"
-
-#: gitk:4604 gitk:6451
-msgid "CDate"
-msgstr "Skapat datum"
-
-#: gitk:4753 gitk:4758
+#: gitk:4846 gitk:4851
 msgid "Descendant"
 msgstr "Avkomling"
 
-#: gitk:4754
+#: gitk:4847
 msgid "Not descendant"
 msgstr "Inte avkomling"
 
-#: gitk:4761 gitk:4766
+#: gitk:4854 gitk:4859
 msgid "Ancestor"
 msgstr "Förfader"
 
-#: gitk:4762
+#: gitk:4855
 msgid "Not ancestor"
 msgstr "Inte förfader"
 
-#: gitk:5052
+#: gitk:5145
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokala Ã¤ndringar sparade i indexet men inte incheckade"
 
-#: gitk:5088
+#: gitk:5181
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokala Ã¤ndringar, ej sparade i indexet"
 
-#: gitk:6769
+#: gitk:6882
 msgid "many"
 msgstr "mÃ¥nga"
 
-#: gitk:6952
+#: gitk:7065
 msgid "Tags:"
 msgstr "Taggar:"
 
-#: gitk:6969 gitk:6975 gitk:8302
+#: gitk:7082 gitk:7088 gitk:8500
 msgid "Parent"
 msgstr "Förälder"
 
-#: gitk:6980
+#: gitk:7093
 msgid "Child"
 msgstr "Barn"
 
-#: gitk:6989
+#: gitk:7102
 msgid "Branch"
 msgstr "Gren"
 
-#: gitk:6992
+#: gitk:7105
 msgid "Follows"
 msgstr "Följer"
 
-#: gitk:6995
+#: gitk:7108
 msgid "Precedes"
 msgstr "FöregÃ¥r"
 
-#: gitk:7532
+#: gitk:7653
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Fel vid hämtning av diff: %s"
 
-#: gitk:8130
+#: gitk:8328
 msgid "Goto:"
 msgstr "GÃ¥ till:"
 
-#: gitk:8151
+#: gitk:8349
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Förkortat SHA1-id %s Ã¤r tvetydigt"
 
-#: gitk:8158
+#: gitk:8356
 #, tcl-format
 msgid "Revision %s is not known"
 msgstr "Revisionen %s Ã¤r inte känd"
 
-#: gitk:8168
+#: gitk:8366
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA-id:t %s Ã¤r inte känt"
 
-#: gitk:8170
+#: gitk:8368
 #, tcl-format
 msgid "Revision %s is not in the current view"
 msgstr "Revisionen %s finns inte i den nuvarande vyn"
 
-#: gitk:8312
+#: gitk:8507 gitk:8522
+msgid "Date"
+msgstr "Datum"
+
+#: gitk:8510
 msgid "Children"
 msgstr "Barn"
 
-#: gitk:8370
+#: gitk:8573
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Ã…terställ grenen %s hit"
 
-#: gitk:8372
+#: gitk:8575
 msgid "Detached head: can't reset"
 msgstr "FrÃ¥nkopplad head: kan inte Ã¥terställa"
 
-#: gitk:8481 gitk:8487
+#: gitk:8680 gitk:8686
 msgid "Skipping merge commit "
 msgstr "Hoppar Ã¶ver sammanslagningsincheckning "
 
-#: gitk:8496 gitk:8501
+#: gitk:8695 gitk:8700
 msgid "Error getting patch ID for "
 msgstr "Fel vid hämtning av patch-id för "
 
-#: gitk:8497 gitk:8502
+#: gitk:8696 gitk:8701
 msgid " - stopping\n"
 msgstr " - stannar\n"
 
-#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
+#: gitk:8706 gitk:8709 gitk:8717 gitk:8731 gitk:8740
 msgid "Commit "
 msgstr "Incheckning "
 
-#: gitk:8511
+#: gitk:8710
 msgid ""
 " is the same patch as\n"
 "       "
@@ -850,7 +868,7 @@ msgstr ""
 " Ã¤r samma patch som\n"
 "       "
 
-#: gitk:8519
+#: gitk:8718
 msgid ""
 " differs from\n"
 "       "
@@ -858,7 +876,7 @@ msgstr ""
 " skiljer sig frÃ¥n\n"
 "       "
 
-#: gitk:8521
+#: gitk:8720
 msgid ""
 "Diff of commits:\n"
 "\n"
@@ -866,131 +884,131 @@ msgstr ""
 "Skillnad mellan incheckningar:\n"
 "\n"
 
-#: gitk:8533 gitk:8542
+#: gitk:8732 gitk:8741
 #, tcl-format
 msgid " has %s children - stopping\n"
 msgstr " har %s barn - stannar\n"
 
-#: gitk:8561
+#: gitk:8760
 #, tcl-format
 msgid "Error writing commit to file: %s"
 msgstr "Fel vid skrivning av incheckning till fil: %s"
 
-#: gitk:8567
+#: gitk:8766
 #, tcl-format
 msgid "Error diffing commits: %s"
 msgstr "Fel vid jämförelse av incheckningar: %s"
 
-#: gitk:8598
+#: gitk:8812
 msgid "Top"
 msgstr "Topp"
 
-#: gitk:8599
+#: gitk:8813
 msgid "From"
 msgstr "FrÃ¥n"
 
-#: gitk:8604
+#: gitk:8818
 msgid "To"
 msgstr "Till"
 
-#: gitk:8628
+#: gitk:8842
 msgid "Generate patch"
 msgstr "Generera patch"
 
-#: gitk:8630
+#: gitk:8844
 msgid "From:"
 msgstr "FrÃ¥n:"
 
-#: gitk:8639
+#: gitk:8853
 msgid "To:"
 msgstr "Till:"
 
-#: gitk:8648
+#: gitk:8862
 msgid "Reverse"
 msgstr "Vänd"
 
-#: gitk:8650 gitk:8845
+#: gitk:8864 gitk:9059
 msgid "Output file:"
 msgstr "Utdatafil:"
 
-#: gitk:8656
+#: gitk:8870
 msgid "Generate"
 msgstr "Generera"
 
-#: gitk:8694
+#: gitk:8908
 msgid "Error creating patch:"
 msgstr "Fel vid generering av patch:"
 
-#: gitk:8717 gitk:8833 gitk:8890
+#: gitk:8931 gitk:9047 gitk:9104
 msgid "ID:"
 msgstr "Id:"
 
-#: gitk:8726
+#: gitk:8940
 msgid "Tag name:"
 msgstr "Taggnamn:"
 
-#: gitk:8729
+#: gitk:8943
 msgid "Tag message is optional"
 msgstr "Taggmeddelandet Ã¤r valfritt"
 
-#: gitk:8731
+#: gitk:8945
 msgid "Tag message:"
 msgstr "Taggmeddelande:"
 
-#: gitk:8735 gitk:8899
+#: gitk:8949 gitk:9113
 msgid "Create"
 msgstr "Skapa"
 
-#: gitk:8753
+#: gitk:8967
 msgid "No tag name specified"
 msgstr "Inget taggnamn angavs"
 
-#: gitk:8757
+#: gitk:8971
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Taggen \"%s\" finns redan"
 
-#: gitk:8767
+#: gitk:8981
 msgid "Error creating tag:"
 msgstr "Fel vid skapande av tagg:"
 
-#: gitk:8842
+#: gitk:9056
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:8850
+#: gitk:9064
 msgid "Write"
 msgstr "Skriv"
 
-#: gitk:8868
+#: gitk:9082
 msgid "Error writing commit:"
 msgstr "Fel vid skrivning av incheckning:"
 
-#: gitk:8895
+#: gitk:9109
 msgid "Name:"
 msgstr "Namn:"
 
-#: gitk:8918
+#: gitk:9132
 msgid "Please specify a name for the new branch"
 msgstr "Ange ett namn för den nya grenen"
 
-#: gitk:8923
+#: gitk:9137
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Grenen \"%s\" finns redan. Skriva Ã¶ver?"
 
-#: gitk:8989
+#: gitk:9204
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Incheckningen %s finns redan pÃ¥ grenen %s -- skall den verkligen appliceras "
 "pÃ¥ nytt?"
 
-#: gitk:8994
+#: gitk:9209
 msgid "Cherry-picking"
 msgstr "Plockar"
 
-#: gitk:9003
+#: gitk:9218
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -1000,7 +1018,7 @@ msgstr ""
 "Checka in, Ã¥terställ eller spara undan (stash) dina Ã¤ndringar och försök "
 "igen."
 
-#: gitk:9009
+#: gitk:9224
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
@@ -1008,32 +1026,32 @@ msgstr ""
 "Cherry-pick misslyckades pÃ¥ grund av en sammanslagningskonflikt.\n"
 "Vill du köra git citool för att lösa den?"
 
-#: gitk:9025
+#: gitk:9240
 msgid "No changes committed"
 msgstr "Inga Ã¤ndringar incheckade"
 
-#: gitk:9051
+#: gitk:9266
 msgid "Confirm reset"
 msgstr "Bekräfta Ã¥terställning"
 
-#: gitk:9053
+#: gitk:9268
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Ã…terställa grenen %s till %s?"
 
-#: gitk:9055
+#: gitk:9270
 msgid "Reset type:"
 msgstr "Typ av Ã¥terställning:"
 
-#: gitk:9058
+#: gitk:9273
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Mjuk: Rör inte utcheckning och index"
 
-#: gitk:9061
+#: gitk:9276
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Blandad: Rör inte utcheckning, Ã¥terställ index"
 
-#: gitk:9064
+#: gitk:9279
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -1041,19 +1059,19 @@ msgstr ""
 "HÃ¥rd: Ã…terställ utcheckning och index\n"
 "(förkastar ALLA lokala Ã¤ndringar)"
 
-#: gitk:9081
+#: gitk:9296
 msgid "Resetting"
 msgstr "Ã…terställer"
 
-#: gitk:9141
+#: gitk:9356
 msgid "Checking out"
 msgstr "Checkar ut"
 
-#: gitk:9194
+#: gitk:9409
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Kan inte ta bort den just nu utcheckade grenen"
 
-#: gitk:9200
+#: gitk:9415
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -1062,16 +1080,16 @@ msgstr ""
 "Incheckningarna pÃ¥ grenen %s existerar inte pÃ¥ nÃ¥gon annan gren.\n"
 "Vill du verkligen ta bort grenen %s?"
 
-#: gitk:9231
+#: gitk:9446
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Taggar och huvuden: %s"
 
-#: gitk:9246
+#: gitk:9461
 msgid "Filter"
 msgstr "Filter"
 
-#: gitk:9541
+#: gitk:9757
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -1079,206 +1097,219 @@ msgstr ""
 "Fel vid läsning av information om incheckningstopologi; information om "
 "grenar och föregÃ¥ende/senare taggar kommer inte vara komplett."
 
-#: gitk:10527
+#: gitk:10744
 msgid "Tag"
 msgstr "Tagg"
 
-#: gitk:10527
+#: gitk:10744
 msgid "Id"
 msgstr "Id"
 
-#: gitk:10576
+#: gitk:10793
 msgid "Gitk font chooser"
 msgstr "Teckensnittsväljare för Gitk"
 
-#: gitk:10593
+#: gitk:10810
 msgid "B"
 msgstr "F"
 
-#: gitk:10596
+#: gitk:10813
 msgid "I"
 msgstr "K"
 
-#: gitk:10714
-msgid "Gitk preferences"
-msgstr "Inställningar för Gitk"
-
-#: gitk:10716
+#: gitk:10931
 msgid "Commit list display options"
 msgstr "Alternativ för incheckningslistvy"
 
-#: gitk:10719
+#: gitk:10934
 msgid "Maximum graph width (lines)"
 msgstr "Maximal grafbredd (rader)"
 
-#: gitk:10722
+#: gitk:10937
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximal grafbredd (% av ruta)"
 
-#: gitk:10725
+#: gitk:10940
 msgid "Show local changes"
 msgstr "Visa lokala Ã¤ndringar"
 
-#: gitk:10728
-msgid "Auto-select SHA1"
-msgstr "Välj SHA1 automatiskt"
+#: gitk:10943
+msgid "Auto-select SHA1 (length)"
+msgstr "Välj SHA1 (längd) automatiskt"
 
-#: gitk:10731
+#: gitk:10947
 msgid "Hide remote refs"
 msgstr "Dölj fjärr-referenser"
 
-#: gitk:10735
+#: gitk:10951
 msgid "Diff display options"
 msgstr "Alternativ för diffvy"
 
-#: gitk:10737
+#: gitk:10953
 msgid "Tab spacing"
 msgstr "Blanksteg för tabulatortecken"
 
-#: gitk:10740
+#: gitk:10956
 msgid "Display nearby tags"
 msgstr "Visa närliggande taggar"
 
-#: gitk:10743
+#: gitk:10959
 msgid "Limit diffs to listed paths"
 msgstr "Begränsa diff till listade sökvägar"
 
-#: gitk:10746
+#: gitk:10962
 msgid "Support per-file encodings"
 msgstr "Stöd för filspecifika teckenkodningar"
 
-#: gitk:10752 gitk:10832
+#: gitk:10968 gitk:11115
 msgid "External diff tool"
 msgstr "Externt diff-verktyg"
 
-#: gitk:10753
+#: gitk:10969
 msgid "Choose..."
 msgstr "Välj..."
 
-#: gitk:10758
+#: gitk:10974
 msgid "General options"
 msgstr "Allmänna inställningar"
 
-#: gitk:10761
+#: gitk:10977
 msgid "Use themed widgets"
 msgstr "Använd tema pÃ¥ fönsterelement"
 
-#: gitk:10763
+#: gitk:10979
 msgid "(change requires restart)"
 msgstr "(ändringen kräver omstart)"
 
-#: gitk:10765
+#: gitk:10981
 msgid "(currently unavailable)"
 msgstr "(för närvarande inte tillgängligt)"
 
-#: gitk:10769
+#: gitk:10992
 msgid "Colors: press to choose"
 msgstr "Färger: tryck för att välja"
 
-#: gitk:10772
+#: gitk:10995
 msgid "Interface"
 msgstr "Gränssnitt"
 
-#: gitk:10773
+#: gitk:10996
 msgid "interface"
 msgstr "gränssnitt"
 
-#: gitk:10776
+#: gitk:10999
 msgid "Background"
 msgstr "Bakgrund"
 
-#: gitk:10777 gitk:10807
+#: gitk:11000 gitk:11030
 msgid "background"
 msgstr "bakgrund"
 
-#: gitk:10780
+#: gitk:11003
 msgid "Foreground"
 msgstr "Förgrund"
 
-#: gitk:10781
+#: gitk:11004
 msgid "foreground"
 msgstr "förgrund"
 
-#: gitk:10784
+#: gitk:11007
 msgid "Diff: old lines"
 msgstr "Diff: gamla rader"
 
-#: gitk:10785
+#: gitk:11008
 msgid "diff old lines"
 msgstr "diff gamla rader"
 
-#: gitk:10789
+#: gitk:11012
 msgid "Diff: new lines"
 msgstr "Diff: nya rader"
 
-#: gitk:10790
+#: gitk:11013
 msgid "diff new lines"
 msgstr "diff nya rader"
 
-#: gitk:10794
+#: gitk:11017
 msgid "Diff: hunk header"
 msgstr "Diff: delhuvud"
 
-#: gitk:10796
+#: gitk:11019
 msgid "diff hunk header"
 msgstr "diff delhuvud"
 
-#: gitk:10800
+#: gitk:11023
 msgid "Marked line bg"
 msgstr "Markerad rad bakgrund"
 
-#: gitk:10802
+#: gitk:11025
 msgid "marked line background"
 msgstr "markerad rad bakgrund"
 
-#: gitk:10806
+#: gitk:11029
 msgid "Select bg"
 msgstr "Markerad bakgrund"
 
-#: gitk:10810
+#: gitk:11038
 msgid "Fonts: press to choose"
 msgstr "Teckensnitt: tryck för att välja"
 
-#: gitk:10812
+#: gitk:11040
 msgid "Main font"
 msgstr "Huvudteckensnitt"
 
-#: gitk:10813
+#: gitk:11041
 msgid "Diff display font"
 msgstr "Teckensnitt för diffvisning"
 
-#: gitk:10814
+#: gitk:11042
 msgid "User interface font"
 msgstr "Teckensnitt för användargränssnitt"
 
-#: gitk:10842
+#: gitk:11064
+msgid "Gitk preferences"
+msgstr "Inställningar för Gitk"
+
+#: gitk:11073
+msgid "General"
+msgstr "Allmänt"
+
+#: gitk:11074
+msgid "Colors"
+msgstr "Färger"
+
+#: gitk:11075
+msgid "Fonts"
+msgstr "Teckensnitt"
+
+#: gitk:11125
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: välj färg för %s"
 
-#: gitk:11445
+#: gitk:11745
 msgid "Cannot find a git repository here."
 msgstr "Hittar inget git-arkiv här."
 
-#: gitk:11449
-#, tcl-format
-msgid "Cannot find the git directory \"%s\"."
-msgstr "Hittar inte git-katalogen \"%s\"."
-
-#: gitk:11496
+#: gitk:11792
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Tvetydigt argument \"%s\": bÃ¥de revision och filnamn"
 
-#: gitk:11508
+#: gitk:11804
 msgid "Bad arguments to gitk:"
 msgstr "Felaktiga argument till gitk:"
 
-#: gitk:11604
+#: gitk:11907
 msgid "Command line"
 msgstr "Kommandorad"
 
+#~ msgid "CDate"
+#~ msgstr "Skapat datum"
+
+#~ msgid "Cannot find the git directory \"%s\"."
+#~ msgstr "Hittar inte git-katalogen \"%s\"."
+
 #~ msgid "SHA1 ID: "
 #~ msgstr "SHA1-id: "
 
index 0863c618007a02fa20c27b62614de8a554128d0c..5f142f619855ccd664a84be366bf6877a48e6da4 100644 (file)
@@ -130,8 +130,10 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
        write_in_full(gpg.in, payload, payload_size);
        close(gpg.in);
 
-       if (gpg_output)
+       if (gpg_output) {
                strbuf_read(gpg_output, gpg.err, 0);
+               close(gpg.err);
+       }
        ret = finish_command(&gpg);
 
        unlink_or_warn(path);
diff --git a/help.c b/help.c
index 2a42ec6d1f312b573d7b1da1a15471d21efc5b4d..1dfa0b05827e6e75dc463092b2838cd1c991623c 100644 (file)
--- a/help.c
+++ b/help.c
@@ -223,6 +223,23 @@ void list_commands(unsigned int colopts,
        }
 }
 
+void list_common_cmds_help(void)
+{
+       int i, longest = 0;
+
+       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+               if (longest < strlen(common_cmds[i].name))
+                       longest = strlen(common_cmds[i].name);
+       }
+
+       puts(_("The most commonly used git commands are:"));
+       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+               printf("   %s   ", common_cmds[i].name);
+               mput_char(' ', longest - strlen(common_cmds[i].name));
+               puts(_(common_cmds[i].help));
+       }
+}
+
 int is_in_cmdlist(struct cmdnames *c, const char *s)
 {
        int i;
diff --git a/ident.c b/ident.c
index ac9672f607909111167559e3aa58e1585f05d0d8..1c123e685fbfbfcd5289958adc54a6cd48351628 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -46,6 +46,7 @@ static void copy_gecos(const struct passwd *w, struct strbuf *name)
 static int add_mailname_host(struct strbuf *buf)
 {
        FILE *mailname;
+       struct strbuf mailnamebuf = STRBUF_INIT;
 
        mailname = fopen("/etc/mailname", "r");
        if (!mailname) {
@@ -54,14 +55,17 @@ static int add_mailname_host(struct strbuf *buf)
                                strerror(errno));
                return -1;
        }
-       if (strbuf_getline(buf, mailname, '\n') == EOF) {
+       if (strbuf_getline(&mailnamebuf, mailname, '\n') == EOF) {
                if (ferror(mailname))
                        warning("cannot read /etc/mailname: %s",
                                strerror(errno));
+               strbuf_release(&mailnamebuf);
                fclose(mailname);
                return -1;
        }
        /* success! */
+       strbuf_addbuf(buf, &mailnamebuf);
+       strbuf_release(&mailnamebuf);
        fclose(mailname);
        return 0;
 }
index e521e2fd223766eee22705674dd3becbb3d52a57..21dc20b57d392c5789043968d28a3b958b2b50c7 100644 (file)
@@ -33,46 +33,6 @@ typedef void *SSL;
 #include <openssl/hmac.h>
 #endif
 
-struct store_conf {
-       char *name;
-       const char *path; /* should this be here? its interpretation is driver-specific */
-       char *map_inbox;
-       char *trash;
-       unsigned max_size; /* off_t is overkill */
-       unsigned trash_remote_new:1, trash_only_new:1;
-};
-
-/* For message->status */
-#define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
-#define M_DEAD         (1<<1) /* expunged */
-#define M_FLAGS        (1<<2) /* flags fetched */
-
-struct message {
-       struct message *next;
-       size_t size; /* zero implies "not fetched" */
-       int uid;
-       unsigned char flags, status;
-};
-
-struct store {
-       struct store_conf *conf; /* foreign */
-
-       /* currently open mailbox */
-       const char *name; /* foreign! maybe preset? */
-       char *path; /* own */
-       struct message *msgs; /* own */
-       int uidvalidity;
-       unsigned char opts; /* maybe preset? */
-       /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
-       int count; /* # of messages */
-       int recent; /* # of recent messages - don't trust this beyond the initial read */
-};
-
-struct msg_data {
-       struct strbuf data;
-       unsigned char flags;
-};
-
 static const char imap_send_usage[] = "git imap-send < <mbox>";
 
 #undef DRV_OK
@@ -90,8 +50,6 @@ static void imap_warn(const char *, ...);
 
 static char *next_arg(char **);
 
-static void free_generic_messages(struct message *);
-
 __attribute__((format (printf, 3, 4)))
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
 
@@ -135,20 +93,6 @@ static struct imap_server_conf server = {
        NULL,   /* auth_method */
 };
 
-struct imap_store_conf {
-       struct store_conf gen;
-       struct imap_server_conf *server;
-};
-
-#define NIL    (void *)0x1
-#define LIST   (void *)0x2
-
-struct imap_list {
-       struct imap_list *next, *child;
-       char *val;
-       int len;
-};
-
 struct imap_socket {
        int fd[2];
        SSL *ssl;
@@ -165,7 +109,6 @@ struct imap_cmd;
 
 struct imap {
        int uidnext; /* from SELECT responses */
-       struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
        unsigned caps, rcaps; /* CAPABILITY results */
        /* command queue */
        int nexttag, num_in_progress, literal_pending;
@@ -174,11 +117,11 @@ struct imap {
 };
 
 struct imap_store {
-       struct store gen;
+       /* currently open mailbox */
+       const char *name; /* foreign! maybe preset? */
        int uidvalidity;
        struct imap *imap;
        const char *prefix;
-       unsigned /*currentnc:1,*/ trashnc:1;
 };
 
 struct imap_cmd_cb {
@@ -225,14 +168,6 @@ static const char *cap_list[] = {
 static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
 
 
-static const char *Flags[] = {
-       "Draft",
-       "Flagged",
-       "Answered",
-       "Seen",
-       "Deleted",
-};
-
 #ifndef NO_OPENSSL
 static void ssl_socket_perror(const char *func)
 {
@@ -476,16 +411,6 @@ static char *next_arg(char **s)
        return ret;
 }
 
-static void free_generic_messages(struct message *msgs)
-{
-       struct message *tmsg;
-
-       for (; msgs; msgs = tmsg) {
-               tmsg = msgs->next;
-               free(msgs);
-       }
-}
-
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
        int ret;
@@ -613,35 +538,9 @@ static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
        }
 }
 
-static int is_atom(struct imap_list *list)
-{
-       return list && list->val && list->val != NIL && list->val != LIST;
-}
-
-static int is_list(struct imap_list *list)
-{
-       return list && list->val == LIST;
-}
-
-static void free_list(struct imap_list *list)
-{
-       struct imap_list *tmp;
-
-       for (; list; list = tmp) {
-               tmp = list->next;
-               if (is_list(list))
-                       free_list(list->child);
-               else if (is_atom(list))
-                       free(list->val);
-               free(list);
-       }
-}
-
-static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level)
+static int skip_imap_list_l(char **sp, int level)
 {
-       struct imap_list *cur;
-       char *s = *sp, *p;
-       int n, bytes;
+       char *s = *sp;
 
        for (;;) {
                while (isspace((unsigned char)*s))
@@ -650,68 +549,23 @@ static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **cu
                        s++;
                        break;
                }
-               *curp = cur = xmalloc(sizeof(*cur));
-               curp = &cur->next;
-               cur->val = NULL; /* for clean bail */
                if (*s == '(') {
                        /* sublist */
                        s++;
-                       cur->val = LIST;
-                       if (parse_imap_list_l(imap, &s, &cur->child, level + 1))
-                               goto bail;
-               } else if (imap && *s == '{') {
-                       /* literal */
-                       bytes = cur->len = strtol(s + 1, &s, 10);
-                       if (*s != '}')
-                               goto bail;
-
-                       s = cur->val = xmalloc(cur->len);
-
-                       /* dump whats left over in the input buffer */
-                       n = imap->buf.bytes - imap->buf.offset;
-
-                       if (n > bytes)
-                               /* the entire message fit in the buffer */
-                               n = bytes;
-
-                       memcpy(s, imap->buf.buf + imap->buf.offset, n);
-                       s += n;
-                       bytes -= n;
-
-                       /* mark that we used part of the buffer */
-                       imap->buf.offset += n;
-
-                       /* now read the rest of the message */
-                       while (bytes > 0) {
-                               if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0)
-                                       goto bail;
-                               s += n;
-                               bytes -= n;
-                       }
-
-                       if (buffer_gets(&imap->buf, &s))
+                       if (skip_imap_list_l(&s, level + 1))
                                goto bail;
                } else if (*s == '"') {
                        /* quoted string */
                        s++;
-                       p = s;
                        for (; *s != '"'; s++)
                                if (!*s)
                                        goto bail;
-                       cur->len = s - p;
                        s++;
-                       cur->val = xmemdupz(p, cur->len);
                } else {
                        /* atom */
-                       p = s;
                        for (; *s && !isspace((unsigned char)*s); s++)
                                if (level && *s == ')')
                                        break;
-                       cur->len = s - p;
-                       if (cur->len == 3 && !memcmp("NIL", p, 3))
-                               cur->val = NIL;
-                       else
-                               cur->val = xmemdupz(p, cur->len);
                }
 
                if (!level)
@@ -720,27 +574,15 @@ static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **cu
                        goto bail;
        }
        *sp = s;
-       *curp = NULL;
        return 0;
 
 bail:
-       *curp = NULL;
        return -1;
 }
 
-static struct imap_list *parse_imap_list(struct imap *imap, char **sp)
+static void skip_list(char **sp)
 {
-       struct imap_list *head;
-
-       if (!parse_imap_list_l(imap, sp, &head, 0))
-               return head;
-       free_list(head);
-       return NULL;
-}
-
-static struct imap_list *parse_list(char **sp)
-{
-       return parse_imap_list(NULL, sp);
+       skip_imap_list_l(sp, 0);
 }
 
 static void parse_capability(struct imap *imap, char *cmd)
@@ -772,7 +614,7 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
        *p++ = 0;
        arg = next_arg(&s);
        if (!strcmp("UIDVALIDITY", arg)) {
-               if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) {
+               if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg))) {
                        fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
                        return RESP_BAD;
                }
@@ -790,7 +632,7 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
                for (; isspace((unsigned char)*p); p++);
                fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
        } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
-               if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) ||
+               if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg)) ||
                    !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) {
                        fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
                        return RESP_BAD;
@@ -819,20 +661,28 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
                        }
 
                        if (!strcmp("NAMESPACE", arg)) {
-                               imap->ns_personal = parse_list(&cmd);
-                               imap->ns_other = parse_list(&cmd);
-                               imap->ns_shared = parse_list(&cmd);
+                               /* rfc2342 NAMESPACE response. */
+                               skip_list(&cmd); /* Personal mailboxes */
+                               skip_list(&cmd); /* Others' mailboxes */
+                               skip_list(&cmd); /* Shared mailboxes */
                        } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
                                   !strcmp("NO", arg) || !strcmp("BYE", arg)) {
                                if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
                                        return resp;
-                       } else if (!strcmp("CAPABILITY", arg))
+                       } else if (!strcmp("CAPABILITY", arg)) {
                                parse_capability(imap, cmd);
-                       else if ((arg1 = next_arg(&cmd))) {
-                               if (!strcmp("EXISTS", arg1))
-                                       ctx->gen.count = atoi(arg);
-                               else if (!strcmp("RECENT", arg1))
-                                       ctx->gen.recent = atoi(arg);
+                       } else if ((arg1 = next_arg(&cmd))) {
+                               ; /*
+                                  * Unhandled response-data with at least two words.
+                                  * Ignore it.
+                                  *
+                                  * NEEDSWORK: Previously this case handled '<num> EXISTS'
+                                  * and '<num> RECENT' but as a probably-unintended side
+                                  * effect it ignores other unrecognized two-word
+                                  * responses.  imap-send doesn't ever try to read
+                                  * messages or mailboxes these days, so consider
+                                  * eliminating this case.
+                                  */
                        } else {
                                fprintf(stderr, "IMAP error: unable to parse untagged response\n");
                                return RESP_BAD;
@@ -934,16 +784,12 @@ static void imap_close_server(struct imap_store *ictx)
                imap_exec(ictx, NULL, "LOGOUT");
                socket_shutdown(&imap->buf.sock);
        }
-       free_list(imap->ns_personal);
-       free_list(imap->ns_other);
-       free_list(imap->ns_shared);
        free(imap);
 }
 
-static void imap_close_store(struct store *ctx)
+static void imap_close_store(struct imap_store *ctx)
 {
-       imap_close_server((struct imap_store *)ctx);
-       free_generic_messages(ctx->msgs);
+       imap_close_server(ctx);
        free(ctx);
 }
 
@@ -1028,7 +874,7 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha
        return 0;
 }
 
-static struct store *imap_open_store(struct imap_server_conf *srvc)
+static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
 {
        struct imap_store *ctx;
        struct imap *imap;
@@ -1238,103 +1084,69 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
        } /* !preauth */
 
        ctx->prefix = "";
-       ctx->trashnc = 1;
-       return (struct store *)ctx;
+       return ctx;
 
 bail:
-       imap_close_store(&ctx->gen);
+       imap_close_store(ctx);
        return NULL;
 }
 
-static int imap_make_flags(int flags, char *buf)
-{
-       const char *s;
-       unsigned i, d;
-
-       for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
-               if (flags & (1 << i)) {
-                       buf[d++] = ' ';
-                       buf[d++] = '\\';
-                       for (s = Flags[i]; *s; s++)
-                               buf[d++] = *s;
-               }
-       buf[0] = '(';
-       buf[d++] = ')';
-       return d;
-}
-
+/*
+ * Insert CR characters as necessary in *msg to ensure that every LF
+ * character in *msg is preceded by a CR.
+ */
 static void lf_to_crlf(struct strbuf *msg)
 {
-       size_t new_len;
        char *new;
-       int i, j, lfnum = 0;
-
-       if (msg->buf[0] == '\n')
-               lfnum++;
-       for (i = 1; i < msg->len; i++) {
-               if (msg->buf[i - 1] != '\r' && msg->buf[i] == '\n')
-                       lfnum++;
+       size_t i, j;
+       char lastc;
+
+       /* First pass: tally, in j, the size of the new string: */
+       for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
+               if (msg->buf[i] == '\n' && lastc != '\r')
+                       j++; /* a CR will need to be added here */
+               lastc = msg->buf[i];
+               j++;
        }
 
-       new_len = msg->len + lfnum;
-       new = xmalloc(new_len + 1);
-       if (msg->buf[0] == '\n') {
-               new[0] = '\r';
-               new[1] = '\n';
-               i = 1;
-               j = 2;
-       } else {
-               new[0] = msg->buf[0];
-               i = 1;
-               j = 1;
-       }
-       for ( ; i < msg->len; i++) {
-               if (msg->buf[i] != '\n') {
-                       new[j++] = msg->buf[i];
-                       continue;
-               }
-               if (msg->buf[i - 1] != '\r')
+       new = xmalloc(j + 1);
+
+       /*
+        * Second pass: write the new string.  Note that this loop is
+        * otherwise identical to the first pass.
+        */
+       for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
+               if (msg->buf[i] == '\n' && lastc != '\r')
                        new[j++] = '\r';
-               /* otherwise it already had CR before */
-               new[j++] = '\n';
+               lastc = new[j++] = msg->buf[i];
        }
-       strbuf_attach(msg, new, new_len, new_len + 1);
+       strbuf_attach(msg, new, j, j + 1);
 }
 
 /*
  * Store msg to IMAP.  Also detach and free the data from msg->data,
  * leaving msg->data empty.
  */
-static int imap_store_msg(struct store *gctx, struct msg_data *msg)
+static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 {
-       struct imap_store *ctx = (struct imap_store *)gctx;
        struct imap *imap = ctx->imap;
        struct imap_cmd_cb cb;
        const char *prefix, *box;
-       int ret, d;
-       char flagstr[128];
+       int ret;
 
-       lf_to_crlf(&msg->data);
+       lf_to_crlf(msg);
        memset(&cb, 0, sizeof(cb));
 
-       cb.dlen = msg->data.len;
-       cb.data = strbuf_detach(&msg->data, NULL);
-
-       d = 0;
-       if (msg->flags) {
-               d = imap_make_flags(msg->flags, flagstr);
-               flagstr[d++] = ' ';
-       }
-       flagstr[d] = 0;
+       cb.dlen = msg->len;
+       cb.data = strbuf_detach(msg, NULL);
 
-       box = gctx->name;
+       box = ctx->name;
        prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
        cb.create = 0;
-       ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
+       ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
        imap->caps = imap->rcaps;
        if (ret != DRV_OK)
                return ret;
-       gctx->count++;
 
        return DRV_OK;
 }
@@ -1483,8 +1295,8 @@ static int git_imap_config(const char *key, const char *val, void *cb)
 int main(int argc, char **argv)
 {
        struct strbuf all_msgs = STRBUF_INIT;
-       struct msg_data msg = {STRBUF_INIT, 0};
-       struct store *ctx = NULL;
+       struct strbuf msg = STRBUF_INIT;
+       struct imap_store *ctx = NULL;
        int ofs = 0;
        int r;
        int total, n = 0;
@@ -1545,10 +1357,10 @@ int main(int argc, char **argv)
                unsigned percent = n * 100 / total;
 
                fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
-               if (!split_msg(&all_msgs, &msg.data, &ofs))
+               if (!split_msg(&all_msgs, &msg, &ofs))
                        break;
                if (server.use_html)
-                       wrap_in_html(&msg.data);
+                       wrap_in_html(&msg);
                r = imap_store_msg(ctx, &msg);
                if (r != DRV_OK)
                        break;
index acea33bf1babfe541c319081f14625ac779bb582..fb61ea66a13eba3a8e91a05dbe1c37de98cec853 100644 (file)
@@ -222,7 +222,7 @@ static const char *default_ll_merge;
 static int read_merge_config(const char *var, const char *value, void *cb)
 {
        struct ll_merge_driver *fn;
-       const char *ep, *name;
+       const char *key, *name;
        int namelen;
 
        if (!strcmp(var, "merge.default")) {
@@ -236,15 +236,13 @@ static int read_merge_config(const char *var, const char *value, void *cb)
         * especially, we do not want to look at variables such as
         * "merge.summary", "merge.tool", and "merge.verbosity".
         */
-       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
+       if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name)
                return 0;
 
        /*
         * Find existing one as we might be processing merge.<name>.var2
         * after seeing merge.<name>.var1.
         */
-       name = var + 6;
-       namelen = ep - name;
        for (fn = ll_user_merge; fn; fn = fn->next)
                if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
                        break;
@@ -256,16 +254,14 @@ static int read_merge_config(const char *var, const char *value, void *cb)
                ll_user_merge_tail = &(fn->next);
        }
 
-       ep++;
-
-       if (!strcmp("name", ep)) {
+       if (!strcmp("name", key)) {
                if (!value)
                        return error("%s: lacks value", var);
                fn->description = xstrdup(value);
                return 0;
        }
 
-       if (!strcmp("driver", ep)) {
+       if (!strcmp("driver", key)) {
                if (!value)
                        return error("%s: lacks value", var);
                /*
@@ -289,7 +285,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (!strcmp("recursive", ep)) {
+       if (!strcmp("recursive", key)) {
                if (!value)
                        return error("%s: lacks value", var);
                fn->recursive = xstrdup(value);
index e703853749f0949c58cde0a27ff1d01991609400..1c8bd8d5a0894d9deda1e849165ff38e8d594a4d 100644 (file)
@@ -177,7 +177,7 @@ extern NORETURN void usage_msg_opt(const char *msg,
 
 extern int optbug(const struct option *opt, const char *reason);
 extern int opterror(const struct option *opt, const char *reason, int flags);
-#ifdef __GNUC__
+#if defined(__GNUC__) && ! defined(clang)
 #define opterror(o,r,f) (opterror((o),(r),(f)), -1)
 #endif
 
diff --git a/pathspec.c b/pathspec.c
new file mode 100644 (file)
index 0000000..284f397
--- /dev/null
@@ -0,0 +1,101 @@
+#include "cache.h"
+#include "dir.h"
+#include "pathspec.h"
+
+/*
+ * Finds which of the given pathspecs match items in the index.
+ *
+ * For each pathspec, sets the corresponding entry in the seen[] array
+ * (which should be specs items long, i.e. the same size as pathspec)
+ * to the nature of the "closest" (i.e. most specific) match found for
+ * that pathspec in the index, if it was a closer type of match than
+ * the existing entry.  As an optimization, matching is skipped
+ * altogether if seen[] already only contains non-zero entries.
+ *
+ * If seen[] has not already been written to, it may make sense
+ * to use find_pathspecs_matching_against_index() instead.
+ */
+void add_pathspec_matches_against_index(const char **pathspec,
+                                       char *seen, int specs)
+{
+       int num_unmatched = 0, i;
+
+       /*
+        * Since we are walking the index as if we were walking the directory,
+        * we have to mark the matched pathspec as seen; otherwise we will
+        * mistakenly think that the user gave a pathspec that did not match
+        * anything.
+        */
+       for (i = 0; i < specs; i++)
+               if (!seen[i])
+                       num_unmatched++;
+       if (!num_unmatched)
+               return;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+       }
+}
+
+/*
+ * Finds which of the given pathspecs match items in the index.
+ *
+ * This is a one-shot wrapper around add_pathspec_matches_against_index()
+ * which allocates, populates, and returns a seen[] array indicating the
+ * nature of the "closest" (i.e. most specific) matches which each of the
+ * given pathspecs achieves against all items in the index.
+ */
+char *find_pathspecs_matching_against_index(const char **pathspec)
+{
+       char *seen;
+       int i;
+
+       for (i = 0; pathspec[i];  i++)
+               ; /* just counting */
+       seen = xcalloc(i, 1);
+       add_pathspec_matches_against_index(pathspec, seen, i);
+       return seen;
+}
+
+/*
+ * Check the index to see whether path refers to a submodule, or
+ * something inside a submodule.  If the former, returns the path with
+ * any trailing slash stripped.  If the latter, dies with an error
+ * message.
+ */
+const char *check_path_for_gitlink(const char *path)
+{
+       int i, path_len = strlen(path);
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (S_ISGITLINK(ce->ce_mode)) {
+                       int ce_len = ce_namelen(ce);
+                       if (path_len <= ce_len || path[ce_len] != '/' ||
+                           memcmp(ce->name, path, ce_len))
+                               /* path does not refer to this
+                                * submodule or anything inside it */
+                               continue;
+                       if (path_len == ce_len + 1) {
+                               /* path refers to submodule;
+                                * strip trailing slash */
+                               return xstrndup(ce->name, ce_len);
+                       } else {
+                               die (_("Path '%s' is in submodule '%.*s'"),
+                                    path, ce_len, ce->name);
+                       }
+               }
+       }
+       return path;
+}
+
+/*
+ * Dies if the given path refers to a file inside a symlinked
+ * directory in the index.
+ */
+void die_if_path_beyond_symlink(const char *path, const char *prefix)
+{
+       if (has_symlink_leading_path(path, strlen(path))) {
+               int len = prefix ? strlen(prefix) : 0;
+               die(_("'%s' is beyond a symbolic link"), path + len);
+       }
+}
diff --git a/pathspec.h b/pathspec.h
new file mode 100644 (file)
index 0000000..db0184a
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef PATHSPEC_H
+#define PATHSPEC_H
+
+extern char *find_pathspecs_matching_against_index(const char **pathspec);
+extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs);
+extern const char *check_path_for_gitlink(const char *path);
+extern void die_if_path_beyond_symlink(const char *path, const char *prefix);
+
+#endif /* PATHSPEC_H */
index 59215fa86e2c2902ec97b12bea70736f9c7c02c6..490e3303804e834497fa1bd3f62500a7b07a31f1 100644 (file)
@@ -490,7 +490,7 @@ sub refname {
        #
        # Additionally, % must be escaped because it is used for escaping
        # and we want our escaped refname to be reversible
-       $refname =~ s{([ \%~\^:\?\*\[\t])}{uc sprintf('%%%02x',ord($1))}eg;
+       $refname =~ s{([ \%~\^:\?\*\[\t])}{sprintf('%%%02X',ord($1))}eg;
 
        # no slash-separated component can begin with a dot .
        # /.* becomes /%2E*
@@ -2377,7 +2377,7 @@ sub map_path {
 
 sub uri_encode {
        my ($f) = @_;
-       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
+       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#sprintf("%%%02X",ord($1))#eg;
        $f
 }
 
index 3db152155c62fb43b6243c10ce25cc193895afe3..fa0d3c6cddfa7a36745c576ba69e484675238593 100644 (file)
@@ -146,7 +146,7 @@ sub url_path {
        my ($self, $path) = @_;
        if ($self->{url} =~ m#^https?://#) {
                # characters are taken from subversion/libsvn_subr/path.c
-               $path =~ s#([^~a-zA-Z0-9_./!$&'()*+,-])#uc sprintf("%%%02x",ord($1))#eg;
+               $path =~ s#([^~a-zA-Z0-9_./!$&'()*+,-])#sprintf("%%%02X",ord($1))#eg;
        }
        $self->{url} . '/' . $self->repo_path($path);
 }
index 07fc0628656c5e37eaf04c969a7273455a391f8d..eae57ad9d7f3b06a5a76f9d934825f9824def477 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -524,10 +524,11 @@ static void add_merge_info(const struct pretty_print_context *pp,
        strbuf_addch(sb, '\n');
 }
 
-static char *get_header(const struct commit *commit, const char *key)
+static char *get_header(const struct commit *commit, const char *msg,
+                       const char *key)
 {
        int key_len = strlen(key);
-       const char *line = commit->buffer;
+       const char *line = msg;
 
        while (line) {
                const char *eol = strchr(line, '\n'), *next;
@@ -588,25 +589,77 @@ char *logmsg_reencode(const struct commit *commit,
        static const char *utf8 = "UTF-8";
        const char *use_encoding;
        char *encoding;
+       char *msg = commit->buffer;
        char *out;
 
+       if (!msg) {
+               enum object_type type;
+               unsigned long size;
+
+               msg = read_sha1_file(commit->object.sha1, &type, &size);
+               if (!msg)
+                       die("Cannot read commit object %s",
+                           sha1_to_hex(commit->object.sha1));
+               if (type != OBJ_COMMIT)
+                       die("Expected commit for '%s', got %s",
+                           sha1_to_hex(commit->object.sha1), typename(type));
+       }
+
        if (!output_encoding || !*output_encoding)
-               return NULL;
-       encoding = get_header(commit, "encoding");
+               return msg;
+       encoding = get_header(commit, msg, "encoding");
        use_encoding = encoding ? encoding : utf8;
-       if (same_encoding(use_encoding, output_encoding))
-               if (encoding) /* we'll strip encoding header later */
-                       out = xstrdup(commit->buffer);
-               else
-                       return NULL; /* nothing to do */
-       else
-               out = reencode_string(commit->buffer,
-                                     output_encoding, use_encoding);
+       if (same_encoding(use_encoding, output_encoding)) {
+               /*
+                * No encoding work to be done. If we have no encoding header
+                * at all, then there's nothing to do, and we can return the
+                * message verbatim (whether newly allocated or not).
+                */
+               if (!encoding)
+                       return msg;
+
+               /*
+                * Otherwise, we still want to munge the encoding header in the
+                * result, which will be done by modifying the buffer. If we
+                * are using a fresh copy, we can reuse it. But if we are using
+                * the cached copy from commit->buffer, we need to duplicate it
+                * to avoid munging commit->buffer.
+                */
+               out = msg;
+               if (out == commit->buffer)
+                       out = xstrdup(out);
+       }
+       else {
+               /*
+                * There's actual encoding work to do. Do the reencoding, which
+                * still leaves the header to be replaced in the next step. At
+                * this point, we are done with msg. If we allocated a fresh
+                * copy, we can free it.
+                */
+               out = reencode_string(msg, output_encoding, use_encoding);
+               if (out && msg != commit->buffer)
+                       free(msg);
+       }
+
+       /*
+        * This replacement actually consumes the buffer we hand it, so we do
+        * not have to worry about freeing the old "out" here.
+        */
        if (out)
                out = replace_encoding_header(out, output_encoding);
 
        free(encoding);
-       return out;
+       /*
+        * If the re-encoding failed, out might be NULL here; in that
+        * case we just return the commit message verbatim.
+        */
+       return out ? out : msg;
+}
+
+void logmsg_free(char *msg, const struct commit *commit)
+{
+       if (msg != commit->buffer)
+               free(msg);
 }
 
 static int mailmap_name(const char **email, size_t *email_len,
@@ -1278,14 +1331,11 @@ void format_commit_message(const struct commit *commit,
        context.pretty_ctx = pretty_ctx;
        context.wrap_start = sb->len;
        context.message = logmsg_reencode(commit, output_enc);
-       if (!context.message)
-               context.message = commit->buffer;
 
        strbuf_expand(sb, format, format_commit_item, &context);
        rewrap_message_tail(sb, &context, 0, 0, 0);
 
-       if (context.message != commit->buffer)
-               free(context.message);
+       logmsg_free(context.message, commit);
        free(context.signature.gpg_output);
        free(context.signature.signer);
 }
@@ -1432,7 +1482,7 @@ void pretty_print_commit(const struct pretty_print_context *pp,
 {
        unsigned long beginning_of_body;
        int indent = 4;
-       const char *msg = commit->buffer;
+       const char *msg;
        char *reencoded;
        const char *encoding;
        int need_8bit_cte = pp->need_8bit_cte;
@@ -1443,10 +1493,7 @@ void pretty_print_commit(const struct pretty_print_context *pp,
        }
 
        encoding = get_log_output_encoding();
-       reencoded = logmsg_reencode(commit, encoding);
-       if (reencoded) {
-               msg = reencoded;
-       }
+       msg = reencoded = logmsg_reencode(commit, encoding);
 
        if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
                indent = 0;
@@ -1503,7 +1550,7 @@ void pretty_print_commit(const struct pretty_print_context *pp,
        if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
 
-       free(reencoded);
+       logmsg_free(reencoded, commit);
 }
 
 void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
index fda78bc353afcfd4d01864f2a13158d9c9c55173..827ae55c508addf5c058872e554a3d914389e324 100644 (file)
@@ -197,21 +197,25 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
        }
        if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
                changed |= MTIME_CHANGED;
-       if (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime)
+       if (trust_ctime && check_stat &&
+           ce->ce_ctime.sec != (unsigned int)st->st_ctime)
                changed |= CTIME_CHANGED;
 
 #ifdef USE_NSEC
-       if (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
+       if (check_stat && ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
                changed |= MTIME_CHANGED;
-       if (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
+       if (trust_ctime && check_stat &&
+           ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
                changed |= CTIME_CHANGED;
 #endif
 
-       if (ce->ce_uid != (unsigned int) st->st_uid ||
-           ce->ce_gid != (unsigned int) st->st_gid)
-               changed |= OWNER_CHANGED;
-       if (ce->ce_ino != (unsigned int) st->st_ino)
-               changed |= INODE_CHANGED;
+       if (check_stat) {
+               if (ce->ce_uid != (unsigned int) st->st_uid ||
+                       ce->ce_gid != (unsigned int) st->st_gid)
+                       changed |= OWNER_CHANGED;
+               if (ce->ce_ino != (unsigned int) st->st_ino)
+                       changed |= INODE_CHANGED;
+       }
 
 #ifdef USE_STDEV
        /*
@@ -219,8 +223,8 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
         * clients will have different views of what "device"
         * the filesystem is on
         */
-       if (ce->ce_dev != (unsigned int) st->st_dev)
-               changed |= INODE_CHANGED;
+       if (check_stat && ce->ce_dev != (unsigned int) st->st_dev)
+                       changed |= INODE_CHANGED;
 #endif
 
        if (ce->ce_size != (unsigned int) st->st_size)
diff --git a/refs.c b/refs.c
index 541fec20658082f13ef4b73b621787512b300ba6..29628253087fb0d54a8f7a45938624527f35d4a8 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -333,14 +333,12 @@ struct string_slice {
 
 static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
 {
-       struct string_slice *key = (struct string_slice *)key_;
-       struct ref_entry *ent = *(struct ref_entry **)ent_;
-       int entlen = strlen(ent->name);
-       int cmplen = key->len < entlen ? key->len : entlen;
-       int cmp = memcmp(key->str, ent->name, cmplen);
+       const struct string_slice *key = key_;
+       const struct ref_entry *ent = *(const struct ref_entry * const *)ent_;
+       int cmp = strncmp(key->str, ent->name, key->len);
        if (cmp)
                return cmp;
-       return key->len - entlen;
+       return '\0' - (unsigned char)ent->name[key->len];
 }
 
 /*
index 4b1153f02c715ac0c441110bf5aa83aceb081aa0..e53a6eb7769e2884f77819c73423c31e49b0114e 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1279,26 +1279,6 @@ int match_push_refs(struct ref *src, struct ref **dst,
        return 0;
 }
 
-static inline int is_forwardable(struct ref* ref)
-{
-       struct object *o;
-
-       if (!prefixcmp(ref->name, "refs/tags/"))
-               return 0;
-
-       /* old object must be a commit */
-       o = parse_object(ref->old_sha1);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-
-       /* new object must be commit-ish */
-       o = deref_tag(parse_object(ref->new_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-
-       return 1;
-}
-
 void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        int force_update)
 {
@@ -1320,54 +1300,40 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
                }
 
                /*
-                * The below logic determines whether an individual
-                * refspec A:B can be pushed.  The push will succeed
-                * if any of the following are true:
+                * Decide whether an individual refspec A:B can be
+                * pushed.  The push will succeed if any of the
+                * following are true:
                 *
                 * (1) the remote reference B does not exist
                 *
                 * (2) the remote reference B is being removed (i.e.,
                 *     pushing :B where no source is specified)
                 *
-                * (3) the update meets all fast-forwarding criteria:
-                *
-                *     (a) the destination is not under refs/tags/
-                *     (b) the old is a commit
-                *     (c) the new is a descendant of the old
-                *
-                *     NOTE: We must actually have the old object in
-                *     order to overwrite it in the remote reference,
-                *     and the new object must be commit-ish.  These are
-                *     implied by (b) and (c) respectively.
+                * (3) the destination is not under refs/tags/, and
+                *     if the old and new value is a commit, the new
+                *     is a descendant of the old.
                 *
                 * (4) it is forced using the +A:B notation, or by
                 *     passing the --force argument
                 */
 
-               ref->not_forwardable = !is_forwardable(ref);
-
-               ref->update =
-                       !ref->deletion &&
-                       !is_null_sha1(ref->old_sha1);
-
-               if (ref->update) {
-                       ref->nonfastforward =
-                               !has_sha1_file(ref->old_sha1)
-                                 || !ref_newer(ref->new_sha1, ref->old_sha1);
-
-                       if (ref->not_forwardable) {
-                               ref->requires_force = 1;
-                               if (!force_ref_update) {
-                                       ref->status = REF_STATUS_REJECT_ALREADY_EXISTS;
-                                       continue;
-                               }
-                       } else if (ref->nonfastforward) {
-                               ref->requires_force = 1;
-                               if (!force_ref_update) {
-                                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
-                                       continue;
-                               }
-                       }
+               if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
+                       int why = 0; /* why would this push require --force? */
+
+                       if (!prefixcmp(ref->name, "refs/tags/"))
+                               why = REF_STATUS_REJECT_ALREADY_EXISTS;
+                       else if (!has_sha1_file(ref->old_sha1))
+                               why = REF_STATUS_REJECT_FETCH_FIRST;
+                       else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
+                                !lookup_commit_reference_gently(ref->new_sha1, 1))
+                               why = REF_STATUS_REJECT_NEEDS_FORCE;
+                       else if (!ref_newer(ref->new_sha1, ref->old_sha1))
+                               why = REF_STATUS_REJECT_NONFASTFORWARD;
+
+                       if (!force_ref_update)
+                               ref->status = why;
+                       else if (why)
+                               ref->forced_update = 1;
                }
        }
 }
@@ -1561,7 +1527,8 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
        struct commit_list *list, *used;
        int found = 0;
 
-       /* Both new and old must be commit-ish and new is descendant of
+       /*
+        * Both new and old must be commit-ish and new is descendant of
         * old.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_sha1), NULL, 0);
index 04712191e8acfbf000c526a5b1b0a80541e8e174..12d4ddb552204ba010ac90f24429d81a74e78a4e 100644 (file)
@@ -735,6 +735,15 @@ int finish_async(struct async *async)
 #endif
 }
 
+char *find_hook(const char *name)
+{
+       char *path = git_path("hooks/%s", name);
+       if (access(path, X_OK) < 0)
+               path = NULL;
+
+       return path;
+}
+
 int run_hook(const char *index_file, const char *name, ...)
 {
        struct child_process hook;
@@ -744,11 +753,13 @@ int run_hook(const char *index_file, const char *name, ...)
        va_list args;
        int ret;
 
-       if (access(git_path("hooks/%s", name), X_OK) < 0)
+       p = find_hook(name);
+       if (!p)
                return 0;
 
+       argv_array_push(&argv, p);
+
        va_start(args, name);
-       argv_array_push(&argv, git_path("hooks/%s", name));
        while ((p = va_arg(args, const char *)))
                argv_array_push(&argv, p);
        va_end(args);
index 850c638f19a2b6776c0311db28371b18438f207a..221ce331405933f6f097b7e2b51401b70097e4e2 100644 (file)
@@ -45,6 +45,7 @@ int start_command(struct child_process *);
 int finish_command(struct child_process *);
 int run_command(struct child_process *);
 
+extern char *find_hook(const char *name);
 extern int run_hook(const char *index_file, const char *name, ...);
 
 #define RUN_COMMAND_NO_STDIN 1
index 1c375f0a28c6417f1f6cb48afbde681f458c544d..97ab336097bfb45c63aec739a6eef2df26cc4790 100644 (file)
@@ -230,6 +230,8 @@ int send_pack(struct send_pack_args *args,
                switch (ref->status) {
                case REF_STATUS_REJECT_NONFASTFORWARD:
                case REF_STATUS_REJECT_ALREADY_EXISTS:
+               case REF_STATUS_REJECT_FETCH_FIRST:
+               case REF_STATUS_REJECT_NEEDS_FORCE:
                case REF_STATUS_UPTODATE:
                        continue;
                default:
diff --git a/setup.c b/setup.c
index f108c4b990c0e4f6a98c1fe34d3031040c84195a..2e1521b09e5e6a0de585225293805b1d30a03cd4 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -66,7 +66,14 @@ int check_filename(const char *prefix, const char *arg)
        const char *name;
        struct stat st;
 
-       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!prefixcmp(arg, ":/")) {
+               if (arg[2] == '\0') /* ":/" is root dir, always exists */
+                       return 1;
+               name = arg + 2;
+       } else if (prefix)
+               name = prefix_filename(prefix, strlen(prefix), arg);
+       else
+               name = arg;
        if (!lstat(name, &st))
                return 1; /* file exists */
        if (errno == ENOENT || errno == ENOTDIR)
@@ -246,6 +253,25 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char
                return prefix_path(prefix, prefixlen, copyfrom);
 }
 
+/*
+ * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
+ * based interface - see pathspec_magic above.
+ *
+ * Arguments:
+ *  - prefix - a path relative to the root of the working tree
+ *  - pathspec - a list of paths underneath the prefix path
+ *
+ * Iterates over pathspec, prepending each path with prefix,
+ * and return the resulting list.
+ *
+ * If pathspec is empty, return a singleton list containing prefix.
+ *
+ * If pathspec and prefix are both empty, return an empty list.
+ *
+ * This is typically used by built-in commands such as add.c, in order
+ * to normalize argv arguments provided to the built-in into a list of
+ * paths to process, all relative to the root of the working tree.
+ */
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
        const char *entry = *pathspec;
index a0363dea203d2a06e985bb4e140ffc675428cbf8..6be915f38f1fe8dbe0a22c4cd8ae2569331f483f 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -72,8 +72,14 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
                }
                if (parse_commit(commit))
                        die("invalid commit");
-               commit->object.flags |= not_shallow_flag;
                cur_depth++;
+               if (cur_depth >= depth) {
+                       commit_list_insert(commit, &result);
+                       commit->object.flags |= shallow_flag;
+                       commit = NULL;
+                       continue;
+               }
+               commit->object.flags |= not_shallow_flag;
                for (p = commit->parents, commit = NULL; p; p = p->next) {
                        if (!p->item->util) {
                                int *pointer = xmalloc(sizeof(int));
index 9a373bef70b7fdd434c02d1ca753fcc8f799ecc2..48e9abb5c2069bfc02a32747004bf51799dda0f1 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -204,6 +204,54 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
        va_end(ap);
 }
 
+static void add_lines(struct strbuf *out,
+                       const char *prefix1,
+                       const char *prefix2,
+                       const char *buf, size_t size)
+{
+       while (size) {
+               const char *prefix;
+               const char *next = memchr(buf, '\n', size);
+               next = next ? (next + 1) : (buf + size);
+
+               prefix = (prefix2 && buf[0] == '\n') ? prefix2 : prefix1;
+               strbuf_addstr(out, prefix);
+               strbuf_add(out, buf, next - buf);
+               size -= next - buf;
+               buf = next;
+       }
+       strbuf_complete_line(out);
+}
+
+void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size)
+{
+       static char prefix1[3];
+       static char prefix2[2];
+
+       if (prefix1[0] != comment_line_char) {
+               sprintf(prefix1, "%c ", comment_line_char);
+               sprintf(prefix2, "%c", comment_line_char);
+       }
+       add_lines(out, prefix1, prefix2, buf, size);
+}
+
+void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...)
+{
+       va_list params;
+       struct strbuf buf = STRBUF_INIT;
+       int incomplete_line = sb->len && sb->buf[sb->len - 1] != '\n';
+
+       va_start(params, fmt);
+       strbuf_vaddf(&buf, fmt, params);
+       va_end(params);
+
+       strbuf_add_commented_lines(sb, buf.buf, buf.len);
+       if (incomplete_line)
+               sb->buf[--sb->len] = '\0';
+
+       strbuf_release(&buf);
+}
+
 void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap)
 {
        int len;
@@ -414,15 +462,7 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 void strbuf_add_lines(struct strbuf *out, const char *prefix,
                      const char *buf, size_t size)
 {
-       while (size) {
-               const char *next = memchr(buf, '\n', size);
-               next = next ? (next + 1) : (buf + size);
-               strbuf_addstr(out, prefix);
-               strbuf_add(out, buf, next - buf);
-               size -= next - buf;
-               buf = next;
-       }
-       strbuf_complete_line(out);
+       add_lines(out, prefix, NULL, buf, size);
 }
 
 void strbuf_addstr_xml_quoted(struct strbuf *buf, const char *s)
index ecae4e215f6377702c2b33e78ca1a0e454ebd819..958822c2dd4ebf2275f294258c265884cfbf3ee8 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -110,6 +110,8 @@ extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
 extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
                           const void *, size_t);
 
+extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);
+
 extern void strbuf_add(struct strbuf *, const void *, size_t);
 static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
        strbuf_add(sb, s, strlen(s));
@@ -131,6 +133,8 @@ extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *
 
 __attribute__((format (printf,2,3)))
 extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
+__attribute__((format (printf, 2, 3)))
+extern void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...);
 __attribute__((format (printf,2,0)))
 extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
 
index 2f554362347674df1a5ee9904d83a0de530304a6..9ba149654322840323cf2d8f4980fa09e56f4068 100644 (file)
@@ -126,45 +126,44 @@ void gitmodules_config(void)
 
 int parse_submodule_config_option(const char *var, const char *value)
 {
-       int len;
        struct string_list_item *config;
-       struct strbuf submodname = STRBUF_INIT;
+       const char *name, *key;
+       int namelen;
 
-       var += 10;              /* Skip "submodule." */
+       if (parse_config_key(var, "submodule", &name, &namelen, &key) < 0 || !name)
+               return 0;
 
-       len = strlen(var);
-       if ((len > 5) && !strcmp(var + len - 5, ".path")) {
-               strbuf_add(&submodname, var, len - 5);
+       if (!strcmp(key, "path")) {
                config = unsorted_string_list_lookup(&config_name_for_path, value);
                if (config)
                        free(config->util);
                else
                        config = string_list_append(&config_name_for_path, xstrdup(value));
-               config->util = strbuf_detach(&submodname, NULL);
-               strbuf_release(&submodname);
-       } else if ((len > 23) && !strcmp(var + len - 23, ".fetchrecursesubmodules")) {
-               strbuf_add(&submodname, var, len - 23);
-               config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, submodname.buf);
+               config->util = xmemdupz(name, namelen);
+       } else if (!strcmp(key, "fetchrecursesubmodules")) {
+               char *name_cstr = xmemdupz(name, namelen);
+               config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name_cstr);
                if (!config)
-                       config = string_list_append(&config_fetch_recurse_submodules_for_name,
-                                                   strbuf_detach(&submodname, NULL));
+                       config = string_list_append(&config_fetch_recurse_submodules_for_name, name_cstr);
+               else
+                       free(name_cstr);
                config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value);
-               strbuf_release(&submodname);
-       } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
+       } else if (!strcmp(key, "ignore")) {
+               char *name_cstr;
+
                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);
                        return 0;
                }
 
-               strbuf_add(&submodname, var, len - 7);
-               config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf);
-               if (config)
+               name_cstr = xmemdupz(name, namelen);
+               config = unsorted_string_list_lookup(&config_ignore_for_name, name_cstr);
+               if (config) {
                        free(config->util);
-               else
-                       config = string_list_append(&config_ignore_for_name,
-                                                   strbuf_detach(&submodname, NULL));
-               strbuf_release(&submodname);
+                       free(name_cstr);
+               } else
+                       config = string_list_append(&config_ignore_for_name, name_cstr);
                config->util = xstrdup(value);
                return 0;
        }
index 7061dce7e5de2f67e60fe6c42c40519f29cad4a1..2098b9ba053b09b00ed4382aa55ea0802d749d89 100644 (file)
@@ -8,7 +8,8 @@ TEST_NO_CREATE_REPO=NoThanks
 
 . ./test-lib.sh
 
-if ! test_have_prereq PYTHON; then
+if ! test_have_prereq PYTHON
+then
        skip_all='skipping git p4 tests; python not available'
        test_done
 fi
@@ -17,6 +18,24 @@ fi
        test_done
 }
 
+# On cygwin, the NT version of Perforce can be used.  When giving
+# it paths, either on the command-line or in client specifications,
+# be sure to use the native windows form.
+#
+# Older versions of perforce were available compiled natively for
+# cygwin.  Those do not accept native windows paths, so make sure
+# not to convert for them.
+native_path() {
+       path="$1" &&
+       if test_have_prereq CYGWIN && ! p4 -V | grep -q CYGWIN
+       then
+               path=$(cygpath --windows "$path")
+       else
+               path=$(test-path-utils real_path "$path")
+       fi &&
+       echo "$path"
+}
+
 # Try to pick a unique port: guess a large number, then hope
 # no more than one of each test is running.
 #
@@ -32,7 +51,7 @@ P4EDITOR=:
 export P4PORT P4CLIENT P4EDITOR
 
 db="$TRASH_DIRECTORY/db"
-cli=$(test-path-utils real_path "$TRASH_DIRECTORY/cli")
+cli="$TRASH_DIRECTORY/cli"
 git="$TRASH_DIRECTORY/git"
 pidfile="$TRASH_DIRECTORY/p4d.pid"
 
@@ -40,8 +59,11 @@ start_p4d() {
        mkdir -p "$db" "$cli" "$git" &&
        rm -f "$pidfile" &&
        (
-               p4d -q -r "$db" -p $P4DPORT &
-               echo $! >"$pidfile"
+               cd "$db" &&
+               {
+                       p4d -q -p $P4DPORT &
+                       echo $! >"$pidfile"
+               }
        ) &&
 
        # This gives p4d a long time to start up, as it can be
@@ -74,15 +96,8 @@ start_p4d() {
        fi
 
        # build a client
-       (
-               cd "$cli" &&
-               p4 client -i <<-EOF
-               Client: client
-               Description: client
-               Root: $cli
-               View: //depot/... //client/...
-               EOF
-       )
+       client_view "//depot/... //client/..." &&
+
        return 0
 }
 
@@ -123,13 +138,26 @@ marshal_dump() {
 client_view() {
        (
                cat <<-EOF &&
-               Client: client
-               Description: client
+               Client: $P4CLIENT
+               Description: $P4CLIENT
                Root: $cli
+               AltRoots: $(native_path "$cli")
+               LineEnd: unix
                View:
                EOF
-               for arg ; do
-                       printf "\t$arg\n"
-               done
+               printf "\t%s\n" "$@"
        ) | p4 client -i
 }
+
+is_cli_file_writeable() {
+       # cygwin version of p4 does not set read-only attr,
+       # will be marked 444 but -w is true
+       file="$1" &&
+       if test_have_prereq CYGWIN && p4 -V | grep -q CYGWIN
+       then
+               stat=$(stat --format=%a "$file") &&
+               test $stat = 644
+       else
+               test -w "$file"
+       fi
+}
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
new file mode 100755 (executable)
index 0000000..d7df719
--- /dev/null
@@ -0,0 +1,637 @@
+#!/bin/sh
+
+test_description=check-ignore
+
+. ./test-lib.sh
+
+init_vars () {
+       global_excludes="$(pwd)/global-excludes"
+}
+
+enable_global_excludes () {
+       init_vars &&
+       git config core.excludesfile "$global_excludes"
+}
+
+expect_in () {
+       dest="$HOME/expected-$1" text="$2"
+       if test -z "$text"
+       then
+               >"$dest" # avoid newline
+       else
+               echo "$text" >"$dest"
+       fi
+}
+
+expect () {
+       expect_in stdout "$1"
+}
+
+expect_from_stdin () {
+       cat >"$HOME/expected-stdout"
+}
+
+test_stderr () {
+       expected="$1"
+       expect_in stderr "$1" &&
+       test_cmp "$HOME/expected-stderr" "$HOME/stderr"
+}
+
+stderr_contains () {
+       regexp="$1"
+       if grep "$regexp" "$HOME/stderr"
+       then
+               return 0
+       else
+               echo "didn't find /$regexp/ in $HOME/stderr"
+               cat "$HOME/stderr"
+               return 1
+       fi
+}
+
+stderr_empty_on_success () {
+       expect_code="$1"
+       if test $expect_code = 0
+       then
+               test_stderr ""
+       else
+               # If we expect failure then stderr might or might not be empty
+               # due to --quiet - the caller can check its contents
+               return 0
+       fi
+}
+
+test_check_ignore () {
+       args="$1" expect_code="${2:-0}" global_args="$3"
+
+       init_vars &&
+       rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" &&
+       echo git $global_args check-ignore $quiet_opt $verbose_opt $args \
+               >"$HOME/cmd" &&
+       test_expect_code "$expect_code" \
+               git $global_args check-ignore $quiet_opt $verbose_opt $args \
+               >"$HOME/stdout" 2>"$HOME/stderr" &&
+       test_cmp "$HOME/expected-stdout" "$HOME/stdout" &&
+       stderr_empty_on_success "$expect_code"
+}
+
+test_expect_success_multi () {
+       prereq=
+       if test $# -eq 4
+       then
+               prereq=$1
+               shift
+       fi
+       testname="$1" expect_verbose="$2" code="$3"
+
+       expect=$( echo "$expect_verbose" | sed -e 's/.* //' )
+
+       test_expect_success $prereq "$testname" '
+               expect "$expect" &&
+               eval "$code"
+       '
+
+       for quiet_opt in '-q' '--quiet'
+       do
+               test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
+                       expect '' &&
+                       $code
+               "
+       done
+       quiet_opt=
+
+       for verbose_opt in '-v' '--verbose'
+       do
+               test_expect_success $prereq "$testname${verbose_opt:+ with $verbose_opt}" "
+                       expect '$expect_verbose' &&
+                       $code
+               "
+       done
+       verbose_opt=
+}
+
+test_expect_success 'setup' '
+       init_vars &&
+       mkdir -p a/b/ignored-dir a/submodule b &&
+       if test_have_prereq SYMLINKS
+       then
+               ln -s b a/symlink
+       fi &&
+       (
+               cd a/submodule &&
+               git init &&
+               echo a >a &&
+               git add a &&
+               git commit -m"commit in submodule"
+       ) &&
+       git add a/submodule &&
+       cat <<-\EOF >.gitignore &&
+               one
+               ignored-*
+       EOF
+       for dir in . a
+       do
+               : >$dir/not-ignored &&
+               : >$dir/ignored-and-untracked &&
+               : >$dir/ignored-but-in-index
+       done &&
+       git add -f ignored-but-in-index a/ignored-but-in-index &&
+       cat <<-\EOF >a/.gitignore &&
+               two*
+               *three
+       EOF
+       cat <<-\EOF >a/b/.gitignore &&
+               four
+               five
+               # this comment should affect the line numbers
+               six
+               ignored-dir/
+               # and so should this blank line:
+
+               !on*
+               !two
+       EOF
+       echo "seven" >a/b/ignored-dir/.gitignore &&
+       test -n "$HOME" &&
+       cat <<-\EOF >"$global_excludes" &&
+               globalone
+               !globaltwo
+               globalthree
+       EOF
+       cat <<-\EOF >>.git/info/exclude
+               per-repo
+       EOF
+'
+
+############################################################################
+#
+# test invalid inputs
+
+test_expect_success_multi 'empty command line' '' '
+       test_check_ignore "" 128 &&
+       stderr_contains "fatal: no path specified"
+'
+
+test_expect_success_multi '--stdin with empty STDIN' '' '
+       test_check_ignore "--stdin" 1 </dev/null &&
+       if test -n "$quiet_opt"; then
+               test_stderr ""
+       else
+               test_stderr "no pathspec given."
+       fi
+'
+
+test_expect_success '-q with multiple args' '
+       expect "" &&
+       test_check_ignore "-q one two" 128 &&
+       stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+test_expect_success '--quiet with multiple args' '
+       expect "" &&
+       test_check_ignore "--quiet one two" 128 &&
+       stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+for verbose_opt in '-v' '--verbose'
+do
+       for quiet_opt in '-q' '--quiet'
+       do
+               test_expect_success "$quiet_opt $verbose_opt" "
+                       expect '' &&
+                       test_check_ignore '$quiet_opt $verbose_opt foo' 128 &&
+                       stderr_contains 'fatal: cannot have both --quiet and --verbose'
+               "
+       done
+done
+
+test_expect_success '--quiet with multiple args' '
+       expect "" &&
+       test_check_ignore "--quiet one two" 128 &&
+       stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+test_expect_success_multi 'erroneous use of --' '' '
+       test_check_ignore "--" 128 &&
+       stderr_contains "fatal: no path specified"
+'
+
+test_expect_success_multi '--stdin with superfluous arg' '' '
+       test_check_ignore "--stdin foo" 128 &&
+       stderr_contains "fatal: cannot specify pathnames with --stdin"
+'
+
+test_expect_success_multi '--stdin -z with superfluous arg' '' '
+       test_check_ignore "--stdin -z foo" 128 &&
+       stderr_contains "fatal: cannot specify pathnames with --stdin"
+'
+
+test_expect_success_multi '-z without --stdin' '' '
+       test_check_ignore "-z" 128 &&
+       stderr_contains "fatal: -z only makes sense with --stdin"
+'
+
+test_expect_success_multi '-z without --stdin and superfluous arg' '' '
+       test_check_ignore "-z foo" 128 &&
+       stderr_contains "fatal: -z only makes sense with --stdin"
+'
+
+test_expect_success_multi 'needs work tree' '' '
+       (
+               cd .git &&
+               test_check_ignore "foo" 128
+       ) &&
+       stderr_contains "fatal: This operation must be run in a work tree"
+'
+
+############################################################################
+#
+# test standard ignores
+
+# First make sure that the presence of a file in the working tree
+# does not impact results, but that the presence of a file in the
+# index does.
+
+for subdir in '' 'a/'
+do
+       if test -z "$subdir"
+       then
+               where="at top-level"
+       else
+               where="in subdir $subdir"
+       fi
+
+       test_expect_success_multi "non-existent file $where not ignored" '' "
+               test_check_ignore '${subdir}non-existent' 1
+       "
+
+       test_expect_success_multi "non-existent file $where ignored" \
+               ".gitignore:1:one       ${subdir}one" "
+               test_check_ignore '${subdir}one'
+       "
+
+       test_expect_success_multi "existing untracked file $where not ignored" '' "
+               test_check_ignore '${subdir}not-ignored' 1
+       "
+
+       test_expect_success_multi "existing tracked file $where not ignored" '' "
+               test_check_ignore '${subdir}ignored-but-in-index' 1
+       "
+
+       test_expect_success_multi "existing untracked file $where ignored" \
+               ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" "
+               test_check_ignore '${subdir}ignored-and-untracked'
+       "
+done
+
+# Having established the above, from now on we mostly test against
+# files which do not exist in the working tree or index.
+
+test_expect_success 'sub-directory local ignore' '
+       expect "a/3-three" &&
+       test_check_ignore "a/3-three a/three-not-this-one"
+'
+
+test_expect_success 'sub-directory local ignore with --verbose'  '
+       expect "a/.gitignore:2:*three   a/3-three" &&
+       test_check_ignore "--verbose a/3-three a/three-not-this-one"
+'
+
+test_expect_success 'local ignore inside a sub-directory' '
+       expect "3-three" &&
+       (
+               cd a &&
+               test_check_ignore "3-three three-not-this-one"
+       )
+'
+test_expect_success 'local ignore inside a sub-directory with --verbose' '
+       expect "a/.gitignore:2:*three   3-three" &&
+       (
+               cd a &&
+               test_check_ignore "--verbose 3-three three-not-this-one"
+       )
+'
+
+test_expect_success_multi 'nested include' \
+       'a/b/.gitignore:8:!on*  a/b/one' '
+       test_check_ignore "a/b/one"
+'
+
+############################################################################
+#
+# test ignored sub-directories
+
+test_expect_success_multi 'ignored sub-directory' \
+       'a/b/.gitignore:5:ignored-dir/  a/b/ignored-dir' '
+       test_check_ignore "a/b/ignored-dir"
+'
+
+test_expect_success 'multiple files inside ignored sub-directory' '
+       expect_from_stdin <<-\EOF &&
+               a/b/ignored-dir/foo
+               a/b/ignored-dir/twoooo
+               a/b/ignored-dir/seven
+       EOF
+       test_check_ignore "a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"
+'
+
+test_expect_success 'multiple files inside ignored sub-directory with -v' '
+       expect_from_stdin <<-\EOF &&
+               a/b/.gitignore:5:ignored-dir/   a/b/ignored-dir/foo
+               a/b/.gitignore:5:ignored-dir/   a/b/ignored-dir/twoooo
+               a/b/.gitignore:5:ignored-dir/   a/b/ignored-dir/seven
+       EOF
+       test_check_ignore "-v a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"
+'
+
+test_expect_success 'cd to ignored sub-directory' '
+       expect_from_stdin <<-\EOF &&
+               foo
+               twoooo
+               ../one
+               seven
+               ../../one
+       EOF
+       (
+               cd a/b/ignored-dir &&
+               test_check_ignore "foo twoooo ../one seven ../../one"
+       )
+'
+
+test_expect_success 'cd to ignored sub-directory with -v' '
+       expect_from_stdin <<-\EOF &&
+               a/b/.gitignore:5:ignored-dir/   foo
+               a/b/.gitignore:5:ignored-dir/   twoooo
+               a/b/.gitignore:8:!on*   ../one
+               a/b/.gitignore:5:ignored-dir/   seven
+               .gitignore:1:one        ../../one
+       EOF
+       (
+               cd a/b/ignored-dir &&
+               test_check_ignore "-v foo twoooo ../one seven ../../one"
+       )
+'
+
+############################################################################
+#
+# test handling of symlinks
+
+test_expect_success_multi SYMLINKS 'symlink' '' '
+       test_check_ignore "a/symlink" 1
+'
+
+test_expect_success_multi SYMLINKS 'beyond a symlink' '' '
+       test_check_ignore "a/symlink/foo" 128 &&
+       test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"
+'
+
+test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
+       (
+               cd a &&
+               test_check_ignore "symlink/foo" 128
+       ) &&
+       test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link"
+'
+
+############################################################################
+#
+# test handling of submodules
+
+test_expect_success_multi 'submodule' '' '
+       test_check_ignore "a/submodule/one" 128 &&
+       test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+'
+
+test_expect_success_multi 'submodule from subdirectory' '' '
+       (
+               cd a &&
+               test_check_ignore "submodule/one" 128
+       ) &&
+       test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+'
+
+############################################################################
+#
+# test handling of global ignore files
+
+test_expect_success 'global ignore not yet enabled' '
+       expect_from_stdin <<-\EOF &&
+               .git/info/exclude:7:per-repo    per-repo
+               a/.gitignore:2:*three   a/globalthree
+               .git/info/exclude:7:per-repo    a/per-repo
+       EOF
+       test_check_ignore "-v globalone per-repo a/globalthree a/per-repo not-ignored a/globaltwo"
+'
+
+test_expect_success 'global ignore' '
+       enable_global_excludes &&
+       expect_from_stdin <<-\EOF &&
+               globalone
+               per-repo
+               globalthree
+               a/globalthree
+               a/per-repo
+               globaltwo
+       EOF
+       test_check_ignore "globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
+'
+
+test_expect_success 'global ignore with -v' '
+       enable_global_excludes &&
+       expect_from_stdin <<-EOF &&
+               $global_excludes:1:globalone    globalone
+               .git/info/exclude:7:per-repo    per-repo
+               $global_excludes:3:globalthree  globalthree
+               a/.gitignore:2:*three   a/globalthree
+               .git/info/exclude:7:per-repo    a/per-repo
+               $global_excludes:2:!globaltwo   globaltwo
+       EOF
+       test_check_ignore "-v globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
+'
+
+############################################################################
+#
+# test --stdin
+
+cat <<-\EOF >stdin
+       one
+       not-ignored
+       a/one
+       a/not-ignored
+       a/b/on
+       a/b/one
+       a/b/one one
+       "a/b/one two"
+       "a/b/one\"three"
+       a/b/not-ignored
+       a/b/two
+       a/b/twooo
+       globaltwo
+       a/globaltwo
+       a/b/globaltwo
+       b/globaltwo
+EOF
+cat <<-\EOF >expected-default
+       one
+       a/one
+       a/b/on
+       a/b/one
+       a/b/one one
+       a/b/one two
+       "a/b/one\"three"
+       a/b/two
+       a/b/twooo
+       globaltwo
+       a/globaltwo
+       a/b/globaltwo
+       b/globaltwo
+EOF
+cat <<-EOF >expected-verbose
+       .gitignore:1:one        one
+       .gitignore:1:one        a/one
+       a/b/.gitignore:8:!on*   a/b/on
+       a/b/.gitignore:8:!on*   a/b/one
+       a/b/.gitignore:8:!on*   a/b/one one
+       a/b/.gitignore:8:!on*   a/b/one two
+       a/b/.gitignore:8:!on*   "a/b/one\"three"
+       a/b/.gitignore:9:!two   a/b/two
+       a/.gitignore:1:two*     a/b/twooo
+       $global_excludes:2:!globaltwo   globaltwo
+       $global_excludes:2:!globaltwo   a/globaltwo
+       $global_excludes:2:!globaltwo   a/b/globaltwo
+       $global_excludes:2:!globaltwo   b/globaltwo
+EOF
+
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
+       tr "\n" "\0" >stdin0
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' expected-default | \
+       tr "\n" "\0" >expected-default0
+sed -e 's/     "/      /' -e 's/\\//' -e 's/"$//' expected-verbose | \
+       tr ":\t\n" "\0" >expected-verbose0
+
+test_expect_success '--stdin' '
+       expect_from_stdin <expected-default &&
+       test_check_ignore "--stdin" <stdin
+'
+
+test_expect_success '--stdin -q' '
+       expect "" &&
+       test_check_ignore "-q --stdin" <stdin
+'
+
+test_expect_success '--stdin -v' '
+       expect_from_stdin <expected-verbose &&
+       test_check_ignore "-v --stdin" <stdin
+'
+
+for opts in '--stdin -z' '-z --stdin'
+do
+       test_expect_success "$opts" "
+               expect_from_stdin <expected-default0 &&
+               test_check_ignore '$opts' <stdin0
+       "
+
+       test_expect_success "$opts -q" "
+               expect "" &&
+               test_check_ignore '-q $opts' <stdin0
+       "
+
+       test_expect_success "$opts -v" "
+               expect_from_stdin <expected-verbose0 &&
+               test_check_ignore '-v $opts' <stdin0
+       "
+done
+
+cat <<-\EOF >stdin
+       ../one
+       ../not-ignored
+       one
+       not-ignored
+       b/on
+       b/one
+       b/one one
+       "b/one two"
+       "b/one\"three"
+       b/two
+       b/not-ignored
+       b/twooo
+       ../globaltwo
+       globaltwo
+       b/globaltwo
+       ../b/globaltwo
+EOF
+cat <<-\EOF >expected-default
+       ../one
+       one
+       b/on
+       b/one
+       b/one one
+       b/one two
+       "b/one\"three"
+       b/two
+       b/twooo
+       ../globaltwo
+       globaltwo
+       b/globaltwo
+       ../b/globaltwo
+EOF
+cat <<-EOF >expected-verbose
+       .gitignore:1:one        ../one
+       .gitignore:1:one        one
+       a/b/.gitignore:8:!on*   b/on
+       a/b/.gitignore:8:!on*   b/one
+       a/b/.gitignore:8:!on*   b/one one
+       a/b/.gitignore:8:!on*   b/one two
+       a/b/.gitignore:8:!on*   "b/one\"three"
+       a/b/.gitignore:9:!two   b/two
+       a/.gitignore:1:two*     b/twooo
+       $global_excludes:2:!globaltwo   ../globaltwo
+       $global_excludes:2:!globaltwo   globaltwo
+       $global_excludes:2:!globaltwo   b/globaltwo
+       $global_excludes:2:!globaltwo   ../b/globaltwo
+EOF
+
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
+       tr "\n" "\0" >stdin0
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' expected-default | \
+       tr "\n" "\0" >expected-default0
+sed -e 's/     "/      /' -e 's/\\//' -e 's/"$//' expected-verbose | \
+       tr ":\t\n" "\0" >expected-verbose0
+
+test_expect_success '--stdin from subdirectory' '
+       expect_from_stdin <expected-default &&
+       (
+               cd a &&
+               test_check_ignore "--stdin" <../stdin
+       )
+'
+
+test_expect_success '--stdin from subdirectory with -v' '
+       expect_from_stdin <expected-verbose &&
+       (
+               cd a &&
+               test_check_ignore "--stdin -v" <../stdin
+       )
+'
+
+for opts in '--stdin -z' '-z --stdin'
+do
+       test_expect_success "$opts from subdirectory" '
+               expect_from_stdin <expected-default0 &&
+               (
+                       cd a &&
+                       test_check_ignore "'"$opts"'" <../stdin0
+               )
+       '
+
+       test_expect_success "$opts from subdirectory with -v" '
+               expect_from_stdin <expected-verbose0 &&
+               (
+                       cd a &&
+                       test_check_ignore "'"$opts"' -v" <../stdin0
+               )
+       '
+done
+
+
+test_done
index ccb0a3cb61be3bb591033564b221726a4cd3968d..a8e84d854636178224b1bbd52272d47fc686ece9 100755 (executable)
@@ -397,4 +397,39 @@ test_expect_success 'strip comments, too' '
        test -z "$(echo "# comment" | git stripspace -s)"
 '
 
+test_expect_success 'strip comments with changed comment char' '
+       test ! -z "$(echo "; comment" | git -c core.commentchar=";" stripspace)" &&
+       test -z "$(echo "; comment" | git -c core.commentchar=";" stripspace -s)"
+'
+
+test_expect_success '-c with single line' '
+       printf "# foo\n" >expect &&
+       printf "foo" | git stripspace -c >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c with single line followed by empty line' '
+       printf "# foo\n#\n" >expect &&
+       printf "foo\n\n" | git stripspace -c >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c with newline only' '
+       printf "#\n" >expect &&
+       printf "\n" | git stripspace -c >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--comment-lines with single line' '
+       printf "# foo\n" >expect &&
+       printf "foo" | git stripspace -c >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c with changed comment char' '
+       printf "; foo\n" >expect &&
+       printf "foo" | git -c core.commentchar=";" stripspace -c >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 78816d9d935df478f5cdf93e06fae26cc5488910..05d78d22a6d0fa5534ae3f37cb3c289595b6c1c5 100755 (executable)
@@ -29,12 +29,10 @@ test_have_prereq SYMLINKS ||
 if test_have_prereq CASE_INSENSITIVE_FS
 then
 test_expect_success "detection of case insensitive filesystem during repo init" '
-
        test $(git config --bool core.ignorecase) = true
 '
 else
 test_expect_success "detection of case insensitive filesystem during repo init" '
-
        test_must_fail git config --bool core.ignorecase >/dev/null ||
        test $(git config --bool core.ignorecase) = false
 '
@@ -43,20 +41,17 @@ fi
 if test_have_prereq SYMLINKS
 then
 test_expect_success "detection of filesystem w/o symlink support during repo init" '
-
        test_must_fail git config --bool core.symlinks ||
        test "$(git config --bool core.symlinks)" = true
 '
 else
 test_expect_success "detection of filesystem w/o symlink support during repo init" '
-
        v=$(git config --bool core.symlinks) &&
        test "$v" = false
 '
 fi
 
 test_expect_success "setup case tests" '
-
        git config core.ignorecase true &&
        touch camelcase &&
        git add camelcase &&
@@ -67,29 +62,23 @@ test_expect_success "setup case tests" '
        git mv tmp CamelCase &&
        git commit -m "rename" &&
        git checkout -f master
-
 '
 
 $test_case 'rename (case change)' '
-
        git mv camelcase CamelCase &&
        git commit -m "rename"
-
 '
 
-$test_case 'merge (case change)' '
-
+test_expect_success 'merge (case change)' '
        rm -f CamelCase &&
        rm -f camelcase &&
        git reset --hard initial &&
        git merge topic
-
 '
 
 
 
-test_expect_failure 'add (with different case)' '
-
+test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' '
        git reset --hard initial &&
        rm camelcase &&
        echo 1 >CamelCase &&
@@ -97,37 +86,30 @@ test_expect_failure 'add (with different case)' '
        camel=$(git ls-files | grep -i camelcase) &&
        test $(echo "$camel" | wc -l) = 1 &&
        test "z$(git cat-file blob :$camel)" = z1
-
 '
 
 test_expect_success "setup unicode normalization tests" '
-
-  test_create_repo unicode &&
-  cd unicode &&
-  touch "$aumlcdiar" &&
-  git add "$aumlcdiar" &&
-  git commit -m initial &&
-  git tag initial &&
-  git checkout -b topic &&
-  git mv $aumlcdiar tmp &&
-  git mv tmp "$auml" &&
-  git commit -m rename &&
-  git checkout -f master
-
+       test_create_repo unicode &&
+       cd unicode &&
+       touch "$aumlcdiar" &&
+       git add "$aumlcdiar" &&
+       git commit -m initial &&
+       git tag initial &&
+       git checkout -b topic &&
+       git mv $aumlcdiar tmp &&
+       git mv tmp "$auml" &&
+       git commit -m rename &&
+       git checkout -f master
 '
 
 $test_unicode 'rename (silent unicode normalization)' '
-
- git mv "$aumlcdiar" "$auml" &&
- git commit -m rename
-
+       git mv "$aumlcdiar" "$auml" &&
+       git commit -m rename
 '
 
 $test_unicode 'merge (silent unicode normalization)' '
-
- git reset --hard initial &&
- git merge topic
-
+       git reset --hard initial &&
+       git merge topic
 '
 
 test_done
index 70edbb33e26c0ee6d5cec81d2417c8a830d6fab1..06b18f8bc1e2991170736254bf3c558ab4673d13 100755 (executable)
@@ -23,7 +23,7 @@ test_expect_success '"reset <submodule>" updates the index' '
        git update-index --refresh &&
        git diff-files --quiet &&
        git diff-index --quiet --cached HEAD &&
-       test_must_fail git reset HEAD^ submodule &&
+       git reset HEAD^ submodule &&
        test_must_fail git diff-files --quiet &&
        git reset submodule &&
        git diff-files --quiet
index af54c831111e6b74f892fefde220921eb5e83fa8..4c37057ddf4a6796c88dba45f0123da1a44fb4af 100755 (executable)
@@ -29,6 +29,18 @@ match() {
     fi
 }
 
+pathmatch() {
+    if [ $1 = 1 ]; then
+       test_expect_success "pathmatch:    match '$2' '$3'" "
+           test-wildmatch pathmatch '$2' '$3'
+       "
+    else
+       test_expect_success "pathmatch: no match '$2' '$3'" "
+           ! test-wildmatch pathmatch '$2' '$3'
+       "
+    fi
+}
+
 # Basic wildmat features
 match 1 1 foo foo
 match 0 0 foo bar
@@ -191,5 +203,36 @@ match 1 1 'XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1' 'XXX/*/
 match 0 0 'XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*'
 match 1 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt' '**/*a*b*g*n*t'
 match 0 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz' '**/*a*b*g*n*t'
+match 0 x foo '*/*/*'
+match 0 x foo/bar '*/*/*'
+match 1 x foo/bba/arr '*/*/*'
+match 0 x foo/bb/aa/rr '*/*/*'
+match 1 x foo/bb/aa/rr '**/**/**'
+match 1 x abcXdefXghi '*X*i'
+match 0 x ab/cXd/efXg/hi '*X*i'
+match 1 x ab/cXd/efXg/hi '*/*X*/*/*i'
+match 1 x ab/cXd/efXg/hi '**/*X*/**/*i'
+
+pathmatch 1 foo foo
+pathmatch 0 foo fo
+pathmatch 1 foo/bar foo/bar
+pathmatch 1 foo/bar 'foo/*'
+pathmatch 1 foo/bba/arr 'foo/*'
+pathmatch 1 foo/bba/arr 'foo/**'
+pathmatch 1 foo/bba/arr 'foo*'
+pathmatch 1 foo/bba/arr 'foo**'
+pathmatch 1 foo/bba/arr 'foo/*arr'
+pathmatch 1 foo/bba/arr 'foo/**arr'
+pathmatch 0 foo/bba/arr 'foo/*z'
+pathmatch 0 foo/bba/arr 'foo/**z'
+pathmatch 1 foo/bar 'foo?bar'
+pathmatch 1 foo/bar 'foo[/]bar'
+pathmatch 0 foo '*/*/*'
+pathmatch 0 foo/bar '*/*/*'
+pathmatch 1 foo/bba/arr '*/*/*'
+pathmatch 1 foo/bb/aa/rr '*/*/*'
+pathmatch 1 abcXdefXghi '*X*i'
+pathmatch 1 ab/cXd/efXg/hi '*/*X*/*/*i'
+pathmatch 1 ab/cXd/efXg/hi '*Xg*i'
 
 test_done
index 91f8198f058df5e2ee93cda8086793ce6a9186d5..04a44d5c61495c4c87db6ada952f5652b8e17642 100755 (executable)
@@ -106,4 +106,12 @@ test_expect_success 'switching diff driver produces correct results' '
        test_cmp expect actual
 '
 
+# The point here is to test that we can log the notes cache and still use it to
+# produce a diff later (older versions of git would segfault on this). It's
+# much more likely to come up in the real world with "log --all -p", but using
+# --no-walk lets us reliably reproduce the order of traversal.
+test_expect_success 'log notes cache and still use cache for -p' '
+       git log --no-walk -p refs/notes/textconv/magic HEAD
+'
+
 test_done
index 2c482b622b4a9e68bb0614dec8e156d5dfc8fa06..72300b5f244504540a551854cb82f81cf5391a55 100755 (executable)
@@ -11,11 +11,24 @@ test_expect_success 'setup' '
        mkdir sub
 '
 
-test_expect_success '"git log :/" should be ambiguous' '
-       test_must_fail git log :/ 2>error &&
+test_expect_success '"git log :/" should not be ambiguous' '
+       git log :/
+'
+
+test_expect_success '"git log :/a" should be ambiguous (applied both rev and worktree)' '
+       : >a &&
+       test_must_fail git log :/a 2>error &&
        grep ambiguous error
 '
 
+test_expect_success '"git log :/a -- " should not be ambiguous' '
+       git log :/a --
+'
+
+test_expect_success '"git log -- :/a" should not be ambiguous' '
+       git log -- :/a
+'
+
 test_expect_success '"git log :" should be ambiguous' '
        test_must_fail git log : 2>error &&
        grep ambiguous error
index e7c240fc1f8333c933f22bc1af6bf57eb9ae27cb..3fbd366ec3bf95020959041462721291d6b92cf2 100755 (executable)
@@ -212,7 +212,8 @@ test_expect_success 'git-archive --prefix=olde-' '
 test_expect_success 'setup tar filters' '
        git config tar.tar.foo.command "tr ab ba" &&
        git config tar.bar.command "tr ab ba" &&
-       git config tar.bar.remote true
+       git config tar.bar.remote true &&
+       git config tar.invalid baz
 '
 
 test_expect_success 'archive --list mentions user filter' '
index 6322e8ade8436dc4e66b9874638e7ca6b7d222b3..354d32c584741daffa03891c5bee7fdc469a20da 100755 (executable)
@@ -130,16 +130,25 @@ test_expect_success 'single given branch clone' '
        test_must_fail git --git-dir=branch-a/.git rev-parse origin/B
 '
 
+test_expect_success 'clone shallow depth 1' '
+       git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0 &&
+       test "`git --git-dir=shallow0/.git rev-list --count HEAD`" = 1
+'
+
 test_expect_success 'clone shallow' '
        git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
 '
 
+test_expect_success 'clone shallow depth count' '
+       test "`git --git-dir=shallow/.git rev-list --count HEAD`" = 2
+'
+
 test_expect_success 'clone shallow object count' '
        (
                cd shallow &&
                git count-objects -v
        ) > count.shallow &&
-       grep "^in-pack: 18" count.shallow
+       grep "^in-pack: 12" count.shallow
 '
 
 test_expect_success 'clone shallow object count (part 2)' '
@@ -256,12 +265,36 @@ test_expect_success 'additional simple shallow deepenings' '
        )
 '
 
+test_expect_success 'clone shallow depth count' '
+       test "`git --git-dir=shallow/.git rev-list --count HEAD`" = 11
+'
+
 test_expect_success 'clone shallow object count' '
        (
                cd shallow &&
                git count-objects -v
        ) > count.shallow &&
-       grep "^count: 52" count.shallow
+       grep "^count: 55" count.shallow
+'
+
+test_expect_success 'fetch --no-shallow on full repo' '
+       test_must_fail git fetch --noshallow
+'
+
+test_expect_success 'fetch --depth --no-shallow' '
+       (
+               cd shallow &&
+               test_must_fail git fetch --depth=1 --noshallow
+       )
+'
+
+test_expect_success 'turn shallow to complete repository' '
+       (
+               cd shallow &&
+               git fetch --unshallow &&
+               ! test -f .git/shallow &&
+               git fsck --full
+       )
 '
 
 test_expect_success 'clone shallow without --no-single-branch' '
@@ -273,7 +306,7 @@ test_expect_success 'clone shallow object count' '
                cd shallow2 &&
                git count-objects -v
        ) > count.shallow2 &&
-       grep "^in-pack: 6" count.shallow2
+       grep "^in-pack: 3" count.shallow2
 '
 
 test_expect_success 'clone shallow with --branch' '
@@ -281,7 +314,7 @@ test_expect_success 'clone shallow with --branch' '
 '
 
 test_expect_success 'clone shallow object count' '
-       echo "in-pack: 6" > count3.expected &&
+       echo "in-pack: 3" > count3.expected &&
        GIT_DIR=shallow3/.git git count-objects -v |
                grep "^in-pack" > count3.actual &&
        test_cmp count3.expected count3.actual
@@ -310,7 +343,7 @@ EOF
        GIT_DIR=shallow6/.git git tag -l >taglist.actual &&
        test_cmp taglist.expected taglist.actual &&
 
-       echo "in-pack: 7" > count6.expected &&
+       echo "in-pack: 4" > count6.expected &&
        GIT_DIR=shallow6/.git git count-objects -v |
                grep "^in-pack" > count6.actual &&
        test_cmp count6.expected count6.actual
@@ -325,7 +358,7 @@ EOF
        GIT_DIR=shallow7/.git git tag -l >taglist.actual &&
        test_cmp taglist.expected taglist.actual &&
 
-       echo "in-pack: 7" > count7.expected &&
+       echo "in-pack: 4" > count7.expected &&
        GIT_DIR=shallow7/.git git count-objects -v |
                grep "^in-pack" > count7.actual &&
        test_cmp count7.expected count7.actual
index 60093728fecd626ae5e005c4d41cee330ac1112c..8f024a08f0b175c12747c33ac0a07cacb73937d9 100755 (executable)
@@ -950,27 +950,6 @@ test_expect_success 'push requires --force to update lightweight tag' '
        )
 '
 
-test_expect_success 'push requires --force to update annotated tag' '
-       mk_test heads/master &&
-       mk_child child1 &&
-       mk_child child2 &&
-       (
-               cd child1 &&
-               git tag -a -m "message 1" Tag &&
-               git push ../child2 Tag:refs/tmp/Tag &&
-               git push ../child2 Tag:refs/tmp/Tag &&
-               >file1 &&
-               git add file1 &&
-               git commit -m "file1" &&
-               git tag -f -a -m "message 2" Tag &&
-               test_must_fail git push ../child2 Tag:refs/tmp/Tag &&
-               git push --force ../child2 Tag:refs/tmp/Tag &&
-               git tag -f -a -m "message 3" Tag HEAD~ &&
-               test_must_fail git push ../child2 Tag:refs/tmp/Tag &&
-               git push --force ../child2 Tag:refs/tmp/Tag
-       )
-'
-
 test_expect_success 'push --porcelain' '
        mk_empty &&
        echo >.git/foo  "To testrepo" &&
diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
new file mode 100755 (executable)
index 0000000..6f9916a
--- /dev/null
@@ -0,0 +1,131 @@
+#!/bin/sh
+
+test_description='check pre-push hooks'
+. ./test-lib.sh
+
+# Setup hook that always succeeds
+HOOKDIR="$(git rev-parse --git-dir)/hooks"
+HOOK="$HOOKDIR/pre-push"
+mkdir -p "$HOOKDIR"
+write_script "$HOOK" <<EOF
+cat >/dev/null
+exit 0
+EOF
+
+test_expect_success 'setup' '
+       git config push.default upstream &&
+       git init --bare repo1 &&
+       git remote add parent1 repo1 &&
+       test_commit one &&
+       git push parent1 HEAD:foreign
+'
+write_script "$HOOK" <<EOF
+cat >/dev/null
+exit 1
+EOF
+
+COMMIT1="$(git rev-parse HEAD)"
+export COMMIT1
+
+test_expect_success 'push with failing hook' '
+       test_commit two &&
+       test_must_fail git push parent1 HEAD
+'
+
+test_expect_success '--no-verify bypasses hook' '
+       git push --no-verify parent1 HEAD
+'
+
+COMMIT2="$(git rev-parse HEAD)"
+export COMMIT2
+
+write_script "$HOOK" <<'EOF'
+echo "$1" >actual
+echo "$2" >>actual
+cat >>actual
+EOF
+
+cat >expected <<EOF
+parent1
+repo1
+refs/heads/master $COMMIT2 refs/heads/foreign $COMMIT1
+EOF
+
+test_expect_success 'push with hook' '
+       git push parent1 master:foreign &&
+       diff expected actual
+'
+
+test_expect_success 'add a branch' '
+       git checkout -b other parent1/foreign &&
+       test_commit three
+'
+
+COMMIT3="$(git rev-parse HEAD)"
+export COMMIT3
+
+cat >expected <<EOF
+parent1
+repo1
+refs/heads/other $COMMIT3 refs/heads/foreign $COMMIT2
+EOF
+
+test_expect_success 'push to default' '
+       git push &&
+       diff expected actual
+'
+
+cat >expected <<EOF
+parent1
+repo1
+refs/tags/one $COMMIT1 refs/tags/tag1 $_z40
+HEAD~ $COMMIT2 refs/heads/prev $_z40
+EOF
+
+test_expect_success 'push non-branches' '
+       git push parent1 one:tag1 HEAD~:refs/heads/prev &&
+       diff expected actual
+'
+
+cat >expected <<EOF
+parent1
+repo1
+(delete) $_z40 refs/heads/prev $COMMIT2
+EOF
+
+test_expect_success 'push delete' '
+       git push parent1 :prev &&
+       diff expected actual
+'
+
+cat >expected <<EOF
+repo1
+repo1
+HEAD $COMMIT3 refs/heads/other $_z40
+EOF
+
+test_expect_success 'push to URL' '
+       git push repo1 HEAD &&
+       diff expected actual
+'
+
+# Test that filling pipe buffers doesn't cause failure
+# Too slow to leave enabled for general use
+if false
+then
+       printf 'parent1\nrepo1\n' >expected
+       nr=1000
+       while test $nr -lt 2000
+       do
+               nr=$(( $nr + 1 ))
+               git branch b/$nr $COMMIT3
+               echo "refs/heads/b/$nr $COMMIT3 refs/heads/b/$nr $_z40" >>expected
+       done
+
+       test_expect_success 'push many refs' '
+               git push parent1 "refs/heads/b/*:refs/heads/b/*" &&
+               diff expected actual
+       '
+fi
+
+test_done
index b096dc88c2f4caf28887cf8fea5d700f0132f5a3..df82ec9ddae30acc5cad23886cbf6ff2de287e7f 100755 (executable)
@@ -28,7 +28,8 @@ test_expect_success 'creating initial files and commits' '
 
        echo "1st line 2nd file" >secondfile &&
        echo "2nd line 2nd file" >>secondfile &&
-       git commit -a -m "modify 2nd file"
+       git commit -a -m "modify 2nd file" &&
+       head5=$(git rev-parse --verify HEAD)
 '
 # git log --pretty=oneline # to see those SHA1 involved
 
@@ -56,7 +57,7 @@ test_expect_success 'giving a non existing revision should fail' '
        test_must_fail git reset --mixed aaaaaa &&
        test_must_fail git reset --soft aaaaaa &&
        test_must_fail git reset --hard aaaaaa &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 test_expect_success 'reset --soft with unmerged index should fail' '
@@ -74,7 +75,7 @@ test_expect_success \
        test_must_fail git reset --hard -- first &&
        test_must_fail git reset --soft HEAD^ -- first &&
        test_must_fail git reset --hard HEAD^ -- first &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 test_expect_success 'giving unrecognized options should fail' '
@@ -86,7 +87,7 @@ test_expect_success 'giving unrecognized options should fail' '
        test_must_fail git reset --soft -o &&
        test_must_fail git reset --hard --other &&
        test_must_fail git reset --hard -o &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 test_expect_success \
@@ -110,7 +111,7 @@ test_expect_success \
 
        git checkout master &&
        git branch -D branch1 branch2 &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 test_expect_success \
@@ -133,27 +134,27 @@ test_expect_success \
 
        git checkout master &&
        git branch -D branch3 branch4 &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 test_expect_success \
        'resetting to HEAD with no changes should succeed and do nothing' '
        git reset --hard &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset --hard HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset --soft &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset --soft HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset --mixed &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset --mixed HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+               check_changes $head5 &&
        git reset HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes $head5
 '
 
 >.diff_expect
@@ -176,7 +177,7 @@ test_expect_success '--soft reset only should show changes in diff --cached' '
        git reset --soft HEAD^ &&
        check_changes d1a4bc3abce4829628ae2dcb0d60ef3d1a78b1c4 &&
        test "$(git rev-parse ORIG_HEAD)" = \
-                       3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+                       $head5
 '
 
 >.diff_expect
@@ -193,7 +194,7 @@ test_expect_success \
        git commit -a -C ORIG_HEAD &&
        check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d &&
        test "$(git rev-parse ORIG_HEAD)" = \
-                       3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+                       $head5
 '
 
 >.diff_expect
@@ -303,7 +304,7 @@ test_expect_success 'redoing the last two commits should succeed' '
        echo "1st line 2nd file" >secondfile &&
        echo "2nd line 2nd file" >>secondfile &&
        git commit -a -m "modify 2nd file" &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 >.diff_expect
@@ -341,15 +342,15 @@ EOF
 test_expect_success \
        '--hard reset to ORIG_HEAD should clear a fast-forward merge' '
        git reset --hard HEAD^ &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+       check_changes $head5 &&
 
        git pull . branch1 &&
        git reset --hard ORIG_HEAD &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+       check_changes $head5 &&
 
        git checkout master &&
        git branch -D branch1 branch2 &&
-       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       check_changes $head5
 '
 
 cat > expect << EOF
@@ -388,7 +389,8 @@ test_expect_success 'test --mixed <paths>' '
        echo 4 > file4 &&
        echo 5 > file1 &&
        git add file1 file3 file4 &&
-       test_must_fail git reset HEAD -- file1 file2 file3 &&
+       git reset HEAD -- file1 file2 file3 &&
+       test_must_fail git diff --quiet &&
        git diff > output &&
        test_cmp output expect &&
        git diff --cached > output &&
@@ -402,7 +404,8 @@ test_expect_success 'test resetting the index at give paths' '
        >sub/file2 &&
        git update-index --add sub/file1 sub/file2 &&
        T=$(git write-tree) &&
-       test_must_fail git reset HEAD sub/file2 &&
+       git reset HEAD sub/file2 &&
+       test_must_fail git diff --quiet &&
        U=$(git write-tree) &&
        echo "$T" &&
        echo "$U" &&
@@ -440,7 +443,8 @@ test_expect_success 'resetting specific path that is unmerged' '
                echo "100644 $F3 3      file2"
        } | git update-index --index-info &&
        git ls-files -u &&
-       test_must_fail git reset HEAD file2 &&
+       git reset HEAD file2 &&
+       test_must_fail git diff --quiet &&
        git diff-index --exit-code --cached HEAD
 '
 
@@ -449,7 +453,8 @@ test_expect_success 'disambiguation (1)' '
        git reset --hard &&
        >secondfile &&
        git add secondfile &&
-       test_must_fail git reset secondfile &&
+       git reset secondfile &&
+       test_must_fail git diff --quiet -- secondfile &&
        test -z "$(git diff --cached --name-only)" &&
        test -f secondfile &&
        test ! -s secondfile
@@ -474,7 +479,8 @@ test_expect_success 'disambiguation (3)' '
        >secondfile &&
        git add secondfile &&
        rm -f secondfile &&
-       test_must_fail git reset HEAD secondfile &&
+       git reset HEAD secondfile &&
+       test_must_fail git diff --quiet &&
        test -z "$(git diff --cached --name-only)" &&
        test ! -f secondfile
 
@@ -486,9 +492,18 @@ test_expect_success 'disambiguation (4)' '
        >secondfile &&
        git add secondfile &&
        rm -f secondfile &&
-       test_must_fail git reset -- secondfile &&
+       git reset -- secondfile &&
+       test_must_fail git diff --quiet &&
        test -z "$(git diff --cached --name-only)" &&
        test ! -f secondfile
 '
 
+test_expect_success 'reset with paths accepts tree' '
+       # for simpler tests, drop last commit containing added files
+       git reset --hard HEAD^ &&
+       git reset HEAD^^{tree} -- . &&
+       git diff --cached HEAD^ --exit-code &&
+       git diff HEAD --exit-code
+'
+
 test_done
diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh
new file mode 100755 (executable)
index 0000000..8062cf5
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git reset should work on unborn branch'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo a >a &&
+       echo b >b
+'
+
+test_expect_success 'reset' '
+       git add a b &&
+       git reset &&
+       test "$(git ls-files)" = ""
+'
+
+test_expect_success 'reset HEAD' '
+       rm .git/index &&
+       git add a b &&
+       test_must_fail git reset HEAD
+'
+
+test_expect_success 'reset $file' '
+       rm .git/index &&
+       git add a b &&
+       git reset a &&
+       test "$(git ls-files)" = "b"
+'
+
+test_expect_success 'reset -p' '
+       rm .git/index &&
+       git add a &&
+       echo y | git reset -p &&
+       test "$(git ls-files)" = ""
+'
+
+test_expect_success 'reset --soft is a no-op' '
+       rm .git/index &&
+       git add a &&
+       git reset --soft
+       test "$(git ls-files)" = "a"
+'
+
+test_expect_success 'reset --hard' '
+       rm .git/index &&
+       git add a &&
+       git reset --hard &&
+       test "$(git ls-files)" = "" &&
+       test_path_is_missing a
+'
+
+test_done
index f919c8d34de41b2ec3fe08c217dd2c6276cf8472..8e32f190077474274dc5046df5a64f837ee696f3 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2008 Johannes Schindelin
 #
 
-test_description='Test rebasing and stashing with dirty submodules'
+test_description='Test rebasing, stashing, etc. with submodules'
 
 . ./test-lib.sh
 
@@ -20,7 +20,8 @@ test_expect_success setup '
        echo second line >> file &&
        (cd submodule && git pull) &&
        test_tick &&
-       git commit -m file-and-submodule -a
+       git commit -m file-and-submodule -a &&
+       git branch added-submodule
 
 '
 
@@ -89,4 +90,29 @@ test_expect_success 'stash with a dirty submodule' '
 
 '
 
+test_expect_success 'rebasing submodule that should conflict' '
+       git reset --hard &&
+       git checkout added-submodule &&
+       git add submodule &&
+       test_tick &&
+       git commit -m third &&
+       (
+               cd submodule &&
+               git commit --allow-empty -m extra
+       ) &&
+       git add submodule &&
+       test_tick &&
+       git commit -m fourth &&
+
+       test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 &&
+       git ls-files -s submodule >actual &&
+       (
+               cd submodule &&
+               echo "160000 $(git rev-parse HEAD^) 1   submodule" &&
+               echo "160000 $(git rev-parse HEAD^^) 2  submodule" &&
+               echo "160000 $(git rev-parse HEAD) 3    submodule"
+       ) >expect &&
+       test_cmp expect actual
+'
+
 test_done
index b1c76483866f55b4429fc76e7035e65984e7b4c2..cbd7a45927eb2d4b04649f21f82192091782ce90 100755 (executable)
@@ -517,4 +517,11 @@ use_template="-t template"
 
 try_commit_status_combo
 
+test_expect_success 'commit --status with custom comment character' '
+       test_when_finished "git config --unset core.commentchar" &&
+       git config core.commentchar ";" &&
+       try_commit --status &&
+       test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
+'
+
 test_done
index e313ef196ed91a6ad12c70b147e2309af8fb4bfd..a79c032ffd941a68e737b844ea5b28fffe58686c 100755 (executable)
@@ -1253,6 +1253,56 @@ test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary
        git config -f .gitmodules  --remove-section submodule.subname
 '
 
+cat > expect << EOF
+; On branch master
+; Changes to be committed:
+;   (use "git reset HEAD <file>..." to unstage)
+;
+;      modified:   sm
+;
+; Changes not staged for commit:
+;   (use "git add <file>..." to update what will be committed)
+;   (use "git checkout -- <file>..." to discard changes in working directory)
+;
+;      modified:   dir1/modified
+;      modified:   sm (new commits)
+;
+; Submodule changes to be committed:
+;
+; * sm $head...$new_head (1):
+;   > Add bar
+;
+; Submodules changed but not updated:
+;
+; * sm $new_head...$head2 (1):
+;   > 2nd commit
+;
+; Untracked files:
+;   (use "git add <file>..." to include in what will be committed)
+;
+;      .gitmodules
+;      dir1/untracked
+;      dir2/modified
+;      dir2/untracked
+;      expect
+;      output
+;      untracked
+EOF
+
+test_expect_success "status (core.commentchar with submodule summary)" '
+       test_when_finished "git config --unset core.commentchar" &&
+       git config core.commentchar ";" &&
+       git status >output &&
+       test_i18ncmp expect output
+'
+
+test_expect_success "status (core.commentchar with two chars with submodule summary)" '
+       test_when_finished "git config --unset core.commentchar" &&
+       git config core.commentchar ";;" &&
+       git status >output &&
+       test_i18ncmp expect output
+'
+
 cat > expect << EOF
 # On branch master
 # Changes not staged for commit:
index 749b75e8d4fba546b22a0280b72891c38b5ea00a..4fea8d901bbc0f713a1500adbbdd2e5886720f47 100755 (executable)
@@ -306,5 +306,13 @@ test_expect_success 'git-svn works in a bare repository' '
        git svn fetch ) &&
        rm -rf bare-repo
        '
+test_expect_success 'git-svn works in in a repository with a gitdir: link' '
+       mkdir worktree gitdir &&
+       ( cd worktree &&
+       git svn init "$svnrepo" &&
+       git init --separate-git-dir ../gitdir &&
+       git svn fetch ) &&
+       rm -rf worktree gitdir
+       '
 
 test_done
diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh
new file mode 100755 (executable)
index 0000000..735a018
--- /dev/null
@@ -0,0 +1,551 @@
+#!/bin/sh
+
+test_description='git-cvsserver and git refspecs
+
+tests ability for git-cvsserver to switch between and compare
+tags, branches and other git refspecs'
+
+. ./test-lib.sh
+
+#########
+
+check_start_tree() {
+       rm -f "$WORKDIR/list.expected"
+       echo "start $1" >>"${WORKDIR}/check.log"
+}
+
+check_file() {
+       sandbox="$1"
+       file="$2"
+       ver="$3"
+       GIT_DIR=$SERVERDIR git show "${ver}:${file}" \
+               >"$WORKDIR/check.got" 2>"$WORKDIR/check.stderr"
+       test_cmp "$WORKDIR/check.got" "$sandbox/$file"
+       stat=$?
+       echo "check_file $sandbox $file $ver : $stat" >>"$WORKDIR/check.log"
+       echo "$file" >>"$WORKDIR/list.expected"
+       return $stat
+}
+
+check_end_tree() {
+       sandbox="$1" &&
+       find "$sandbox" -name CVS -prune -o -type f -print >"$WORKDIR/list.actual" &&
+       sort <"$WORKDIR/list.expected" >expected &&
+       sort <"$WORKDIR/list.actual" | sed -e "s%cvswork/%%" >actual &&
+       test_cmp expected actual &&
+       rm expected actual
+}
+
+check_end_full_tree() {
+       sandbox="$1" &&
+       sort <"$WORKDIR/list.expected" >expected &&
+       find "$sandbox" -name CVS -prune -o -type f -print |
+       sed -e "s%$sandbox/%%" | sort >act1 &&
+       test_cmp expected act1 &&
+       git ls-tree --name-only -r "$2" | sort >act2 &&
+       test_cmp expected act2 &&
+       rm expected act1 act2
+}
+
+#########
+
+check_diff() {
+       diffFile="$1"
+       vOld="$2"
+       vNew="$3"
+       rm -rf diffSandbox
+       git clone -q -n . diffSandbox &&
+       (
+               cd diffSandbox &&
+               git checkout "$vOld" &&
+               git apply -p0 --index <"../$diffFile" &&
+               git diff --exit-code "$vNew"
+       ) >check_diff_apply.out 2>&1
+}
+
+#########
+
+cvs >/dev/null 2>&1
+if test $? -ne 1
+then
+       skip_all='skipping git-cvsserver tests, cvs not found'
+       test_done
+fi
+if ! test_have_prereq PERL
+then
+       skip_all='skipping git-cvsserver tests, perl not available'
+       test_done
+fi
+"$PERL_PATH" -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+       skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable'
+       test_done
+}
+
+unset GIT_DIR GIT_CONFIG
+WORKDIR=$(pwd)
+SERVERDIR=$(pwd)/gitcvs.git
+git_config="$SERVERDIR/config"
+CVSROOT=":fork:$SERVERDIR"
+CVSWORK="$(pwd)/cvswork"
+CVS_SERVER=git-cvsserver
+export CVSROOT CVS_SERVER
+
+rm -rf "$CVSWORK" "$SERVERDIR"
+test_expect_success 'setup v1, b1' '
+       echo "Simple text file" >textfile.c &&
+       echo "t2" >t2 &&
+       mkdir adir &&
+       echo "adir/afile line1" >adir/afile &&
+       echo "adir/afile line2" >>adir/afile &&
+       echo "adir/afile line3" >>adir/afile &&
+       echo "adir/afile line4" >>adir/afile &&
+       echo "adir/a2file" >>adir/a2file &&
+       mkdir adir/bdir &&
+       echo "adir/bdir/bfile line 1" >adir/bdir/bfile &&
+       echo "adir/bdir/bfile line 2" >>adir/bdir/bfile &&
+       echo "adir/bdir/b2file" >adir/bdir/b2file &&
+       git add textfile.c t2 adir &&
+       git commit -q -m "First Commit (v1)" &&
+       git tag v1 &&
+       git branch b1 &&
+       git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+       GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+       GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co v1' '
+       cvs -f -Q co -r v1 -d cvswork master >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_tree cvswork
+'
+
+rm -rf cvswork
+test_expect_success 'cvs co b1' '
+       cvs -f co -r b1 -d cvswork master >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_tree cvswork
+'
+
+test_expect_success 'cvs co b1 [cvswork3]' '
+       cvs -f co -r b1 -d cvswork3 master >cvs.log 2>&1 &&
+       check_start_tree cvswork3 &&
+       check_file cvswork3 textfile.c v1 &&
+       check_file cvswork3 t2 v1 &&
+       check_file cvswork3 adir/afile v1 &&
+       check_file cvswork3 adir/a2file v1 &&
+       check_file cvswork3 adir/bdir/bfile v1 &&
+       check_file cvswork3 adir/bdir/b2file v1 &&
+       check_end_full_tree cvswork3 v1
+'
+
+test_expect_success 'edit cvswork3 and save diff' '
+       (
+               cd cvswork3 &&
+               sed -e "s/line1/line1 - data/" adir/afile >adir/afileNEW &&
+               mv -f adir/afileNEW adir/afile &&
+               echo "afile5" >adir/afile5 &&
+               rm t2 &&
+               cvs -f add adir/afile5 &&
+               cvs -f rm t2 &&
+               ! cvs -f diff -N -u >"$WORKDIR/cvswork3edit.diff"
+       )
+'
+
+test_expect_success 'setup v1.2 on b1' '
+       git checkout b1 &&
+       echo "new v1.2" >t3 &&
+       rm t2 &&
+       sed -e "s/line3/line3 - more data/" adir/afile >adir/afileNEW &&
+       mv -f adir/afileNEW adir/afile &&
+       rm adir/a2file &&
+       echo "a3file" >>adir/a3file &&
+       echo "bfile line 3" >>adir/bdir/bfile &&
+       rm adir/bdir/b2file &&
+       echo "b3file" >adir/bdir/b3file &&
+       mkdir cdir &&
+       echo "cdir/cfile" >cdir/cfile &&
+       git add -A cdir adir t3 t2 &&
+       git commit -q -m 'v1.2' &&
+       git tag v1.2 &&
+       git push --tags gitcvs.git b1:b1
+'
+
+test_expect_success 'cvs -f up (on b1 adir)' '
+       ( cd cvswork/adir && cvs -f up -d ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1.2 &&
+       check_file cvswork adir/a3file v1.2 &&
+       check_file cvswork adir/bdir/bfile v1.2 &&
+       check_file cvswork adir/bdir/b3file v1.2 &&
+       check_end_tree cvswork
+'
+
+test_expect_success 'cvs up (on b1 /)' '
+       ( cd cvswork && cvs -f up -d ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1.2 &&
+       check_file cvswork t3 v1.2 &&
+       check_file cvswork adir/afile v1.2 &&
+       check_file cvswork adir/a3file v1.2 &&
+       check_file cvswork adir/bdir/bfile v1.2 &&
+       check_file cvswork adir/bdir/b3file v1.2 &&
+       check_file cvswork cdir/cfile v1.2 &&
+       check_end_tree cvswork
+'
+
+# Make sure "CVS/Tag" files didn't get messed up:
+test_expect_success 'cvs up (on b1 /) (again; check CVS/Tag files)' '
+       ( cd cvswork && cvs -f up -d ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1.2 &&
+       check_file cvswork t3 v1.2 &&
+       check_file cvswork adir/afile v1.2 &&
+       check_file cvswork adir/a3file v1.2 &&
+       check_file cvswork adir/bdir/bfile v1.2 &&
+       check_file cvswork adir/bdir/b3file v1.2 &&
+       check_file cvswork cdir/cfile v1.2 &&
+       check_end_tree cvswork
+'
+
+# update to another version:
+test_expect_success 'cvs up -r v1' '
+       ( cd cvswork && cvs -f up -r v1 ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_tree cvswork
+'
+
+test_expect_success 'cvs up' '
+       ( cd cvswork && cvs -f up ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_tree cvswork
+'
+
+test_expect_success 'cvs up (again; check CVS/Tag files)' '
+       ( cd cvswork && cvs -f up -d ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_tree cvswork
+'
+
+test_expect_success 'setup simple b2' '
+       git branch b2 v1 &&
+       git push --tags gitcvs.git b2:b2
+'
+
+test_expect_success 'cvs co b2 [into cvswork2]' '
+       cvs -f co -r b2 -d cvswork2 master >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_tree cvswork
+'
+
+test_expect_success 'root dir edit [cvswork2]' '
+       (
+               cd cvswork2 && echo "Line 2" >>textfile.c &&
+               ! cvs -f diff -u >"$WORKDIR/cvsEdit1.diff" &&
+               cvs -f commit -m "edit textfile.c" textfile.c
+       ) >cvsEdit1.log 2>&1
+'
+
+test_expect_success 'root dir rm file [cvswork2]' '
+       (
+               cd cvswork2 &&
+               cvs -f rm -f t2 &&
+               cvs -f diff -u >../cvsEdit2-empty.diff &&
+               ! cvs -f diff -N -u >"$WORKDIR/cvsEdit2-N.diff" &&
+               cvs -f commit -m "rm t2"
+       ) >cvsEdit2.log 2>&1
+'
+
+test_expect_success 'subdir edit/add/rm files [cvswork2]' '
+       (
+               cd cvswork2 &&
+               sed -e "s/line 1/line 1 (v2)/" adir/bdir/bfile >adir/bdir/bfileNEW &&
+               mv -f adir/bdir/bfileNEW adir/bdir/bfile &&
+               rm adir/bdir/b2file &&
+               cd adir &&
+               cvs -f rm bdir/b2file &&
+               echo "4th file" >bdir/b4file &&
+               cvs -f add bdir/b4file &&
+               ! cvs -f diff -N -u >"$WORKDIR/cvsEdit3.diff" &&
+               git fetch gitcvs.git b2:b2 &&
+               (
+                 cd .. &&
+                 ! cvs -f diff -u -N -r v1.2 >"$WORKDIR/cvsEdit3-v1.2.diff" &&
+                 ! cvs -f diff -u -N -r v1.2 -r v1 >"$WORKDIR/cvsEdit3-v1.2-v1.diff"
+               ) &&
+               cvs -f commit -m "various add/rm/edit"
+       ) >cvs.log 2>&1
+'
+
+test_expect_success 'validate result of edits [cvswork2]' '
+       git fetch gitcvs.git b2:b2 &&
+       git tag v2 b2 &&
+       git push --tags gitcvs.git b2:b2 &&
+       check_start_tree cvswork2 &&
+       check_file cvswork2 textfile.c v2 &&
+       check_file cvswork2 adir/afile v2 &&
+       check_file cvswork2 adir/a2file v2 &&
+       check_file cvswork2 adir/bdir/bfile v2 &&
+       check_file cvswork2 adir/bdir/b4file v2 &&
+       check_end_full_tree cvswork2 v2
+'
+
+test_expect_success 'validate basic diffs saved during above cvswork2 edits' '
+       test $(grep Index: cvsEdit1.diff | wc -l) = 1 &&
+       test ! -s cvsEdit2-empty.diff &&
+       test $(grep Index: cvsEdit2-N.diff | wc -l) = 1 &&
+       test $(grep Index: cvsEdit3.diff | wc -l) = 3 &&
+       rm -rf diffSandbox &&
+       git clone -q -n . diffSandbox &&
+       (
+               cd diffSandbox &&
+               git checkout v1 &&
+               git apply -p0 --index <"$WORKDIR/cvsEdit1.diff" &&
+               git apply -p0 --index <"$WORKDIR/cvsEdit2-N.diff" &&
+               git apply -p0 --directory=adir --index <"$WORKDIR/cvsEdit3.diff" &&
+               git diff --exit-code v2
+       ) >"check_diff_apply.out" 2>&1
+'
+
+test_expect_success 'validate v1.2 diff saved during last cvswork2 edit' '
+       test $(grep Index: cvsEdit3-v1.2.diff | wc -l) = 9 &&
+       check_diff cvsEdit3-v1.2.diff v1.2 v2
+'
+
+test_expect_success 'validate v1.2 v1 diff saved during last cvswork2 edit' '
+       test $(grep Index: cvsEdit3-v1.2-v1.diff | wc -l) = 9 &&
+       check_diff cvsEdit3-v1.2-v1.diff v1.2 v1
+'
+
+test_expect_success 'cvs up [cvswork2]' '
+       ( cd cvswork2 && cvs -f up ) >cvs.log 2>&1 &&
+       check_start_tree cvswork2 &&
+       check_file cvswork2 textfile.c v2 &&
+       check_file cvswork2 adir/afile v2 &&
+       check_file cvswork2 adir/a2file v2 &&
+       check_file cvswork2 adir/bdir/bfile v2 &&
+       check_file cvswork2 adir/bdir/b4file v2 &&
+       check_end_full_tree cvswork2 v2
+'
+
+test_expect_success 'cvs up -r b2 [back to cvswork]' '
+       ( cd cvswork && cvs -f up -r b2 ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v2 &&
+       check_file cvswork adir/afile v2 &&
+       check_file cvswork adir/a2file v2 &&
+       check_file cvswork adir/bdir/bfile v2 &&
+       check_file cvswork adir/bdir/b4file v2 &&
+       check_end_full_tree cvswork v2
+'
+
+test_expect_success 'cvs up -r b1' '
+       ( cd cvswork && cvs -f up -r b1 ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1.2 &&
+       check_file cvswork t3 v1.2 &&
+       check_file cvswork adir/afile v1.2 &&
+       check_file cvswork adir/a3file v1.2 &&
+       check_file cvswork adir/bdir/bfile v1.2 &&
+       check_file cvswork adir/bdir/b3file v1.2 &&
+       check_file cvswork cdir/cfile v1.2 &&
+       check_end_full_tree cvswork v1.2
+'
+
+test_expect_success 'cvs up -A' '
+       ( cd cvswork && cvs -f up -A ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_full_tree cvswork v1
+'
+
+test_expect_success 'cvs up (check CVS/Tag files)' '
+       ( cd cvswork && cvs -f up ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_full_tree cvswork v1
+'
+
+# This is not really legal CVS, but it seems to work anyway:
+test_expect_success 'cvs up -r heads/b1' '
+       ( cd cvswork && cvs -f up -r heads/b1 ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1.2 &&
+       check_file cvswork t3 v1.2 &&
+       check_file cvswork adir/afile v1.2 &&
+       check_file cvswork adir/a3file v1.2 &&
+       check_file cvswork adir/bdir/bfile v1.2 &&
+       check_file cvswork adir/bdir/b3file v1.2 &&
+       check_file cvswork cdir/cfile v1.2 &&
+       check_end_full_tree cvswork v1.2
+'
+
+# But this should work even if CVS client checks -r more carefully:
+test_expect_success 'cvs up -r heads_-s-b2 (cvsserver escape mechanism)' '
+       ( cd cvswork && cvs -f up -r heads_-s-b2 ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v2 &&
+       check_file cvswork adir/afile v2 &&
+       check_file cvswork adir/a2file v2 &&
+       check_file cvswork adir/bdir/bfile v2 &&
+       check_file cvswork adir/bdir/b4file v2 &&
+       check_end_full_tree cvswork v2
+'
+
+v1hash=$(git rev-parse v1)
+test_expect_success 'cvs up -r $(git rev-parse v1)' '
+       test -n "$v1hash" &&
+       ( cd cvswork && cvs -f up -r "$v1hash" ) >cvs.log 2>&1 &&
+       check_start_tree cvswork &&
+       check_file cvswork textfile.c v1 &&
+       check_file cvswork t2 v1 &&
+       check_file cvswork adir/afile v1 &&
+       check_file cvswork adir/a2file v1 &&
+       check_file cvswork adir/bdir/bfile v1 &&
+       check_file cvswork adir/bdir/b2file v1 &&
+       check_end_full_tree cvswork v1
+'
+
+test_expect_success 'cvs diff -r v1 -u' '
+       ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log &&
+       test ! -s cvsDiff.out &&
+       test ! -s cvs.log
+'
+
+test_expect_success 'cvs diff -N -r v2 -u' '
+       ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log &&
+       test ! -s cvs.log &&
+       test -s cvsDiff.out &&
+       check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1
+'
+
+test_expect_success 'cvs diff -N -r v2 -r v1.2' '
+       ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log &&
+       test ! -s cvs.log &&
+       test -s cvsDiff.out &&
+       check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1
+'
+
+test_expect_success 'apply early [cvswork3] diff to b3' '
+       git clone -q . gitwork3 &&
+       (
+               cd gitwork3 &&
+               git checkout -b b3 v1 &&
+               git apply -p0 --index <"$WORKDIR/cvswork3edit.diff" &&
+               git commit -m "cvswork3 edits applied"
+       ) &&
+       git fetch gitwork3 b3:b3 &&
+       git tag v3 b3
+'
+
+test_expect_success 'check [cvswork3] diff' '
+       ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log &&
+       test ! -s cvs.log &&
+       test -s cvsDiff.out &&
+       test $(grep Index: cvsDiff.out | wc -l) = 3 &&
+       test_cmp cvsDiff.out cvswork3edit.diff &&
+       check_diff cvsDiff.out v1 v3 >check_diff.out 2>&1
+'
+
+test_expect_success 'merge early [cvswork3] b3 with b1' '
+       ( cd gitwork3 && git merge "message" HEAD b1 ) &&
+       git fetch gitwork3 b3:b3 &&
+       git tag v3merged b3 &&
+       git push --tags gitcvs.git b3:b3
+'
+
+# This test would fail if cvsserver properly created a ".#afile"* file
+# for the merge.
+# TODO: Validate that the .# file was saved properly, and then
+#   delete/ignore it when checking the tree.
+test_expect_success 'cvs up dirty [cvswork3]' '
+       (
+               cd cvswork3 &&
+               cvs -f up &&
+               ! cvs -f diff -N -u >"$WORKDIR/cvsDiff.out"
+       ) >cvs.log 2>&1 &&
+       test -s cvsDiff.out &&
+       test $(grep Index: cvsDiff.out | wc -l) = 2 &&
+       check_start_tree cvswork3 &&
+       check_file cvswork3 textfile.c v3merged &&
+       check_file cvswork3 t3 v3merged &&
+       check_file cvswork3 adir/afile v3merged &&
+       check_file cvswork3 adir/a3file v3merged &&
+       check_file cvswork3 adir/afile5 v3merged &&
+       check_file cvswork3 adir/bdir/bfile v3merged &&
+       check_file cvswork3 adir/bdir/b3file v3merged &&
+       check_file cvswork3 cdir/cfile v3merged &&
+       check_end_full_tree cvswork3 v3merged
+'
+
+# TODO: test cvs status
+
+test_expect_success 'cvs commit [cvswork3]' '
+       (
+               cd cvswork3 &&
+               cvs -f commit -m "dirty sandbox after auto-merge"
+       ) >cvs.log 2>&1 &&
+       check_start_tree cvswork3 &&
+       check_file cvswork3 textfile.c v3merged &&
+       check_file cvswork3 t3 v3merged &&
+       check_file cvswork3 adir/afile v3merged &&
+       check_file cvswork3 adir/a3file v3merged &&
+       check_file cvswork3 adir/afile5 v3merged &&
+       check_file cvswork3 adir/bdir/bfile v3merged &&
+       check_file cvswork3 adir/bdir/b3file v3merged &&
+       check_file cvswork3 cdir/cfile v3merged &&
+       check_end_full_tree cvswork3 v3merged &&
+       git fetch gitcvs.git b3:b4 &&
+       git tag v4.1 b4 &&
+       git diff --exit-code v4.1 v3merged >check_diff_apply.out 2>&1
+'
+
+test_done
index 8c5979647ffceeb6614f0cdb58ae06170afac7c4..665607c9cbe5a07f83d4ae4c769060108bddbd9c 100755 (executable)
@@ -30,6 +30,11 @@ test_expect_success 'basic git p4 clone' '
        )
 '
 
+test_expect_success 'depot typo error' '
+       test_must_fail git p4 clone --dest="$git" /depot 2>errs &&
+       grep "Depot paths must start with" errs
+'
+
 test_expect_success 'git p4 clone @all' '
        git p4 clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
@@ -160,9 +165,12 @@ test_expect_success 'clone --bare should make a bare repository' '
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               test ! -d .git &&
-               bare=`git config --get core.bare` &&
-               test "$bare" = true
+               test_path_is_missing .git &&
+               git config --get --bool core.bare true &&
+               git rev-parse --verify refs/remotes/p4/master &&
+               git rev-parse --verify refs/remotes/p4/HEAD &&
+               git rev-parse --verify refs/heads/master &&
+               git rev-parse --verify HEAD
        )
 '
 
index 21924dfd7db4fd5b8c0eb2c2823580ae33e73cc1..eeefa6790454d83bfb4ce740f7b1843c6a71c79c 100755 (executable)
@@ -8,6 +8,123 @@ test_expect_success 'start p4d' '
        start_p4d
 '
 
+#
+# This series of tests checks newline handling  Both p4 and
+# git store newlines as \n, and have options to choose how
+# newlines appear in checked-out files.
+#
+test_expect_success 'p4 client newlines, unix' '
+       (
+               cd "$cli" &&
+               p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i &&
+               printf "unix\ncrlf\n" >f-unix &&
+               printf "unix\r\ncrlf\r\n" >f-unix-as-crlf &&
+               p4 add -t text f-unix &&
+               p4 submit -d f-unix &&
+
+               # LineEnd: unix; should be no change after sync
+               cp f-unix f-unix-orig &&
+               p4 sync -f &&
+               test_cmp f-unix-orig f-unix &&
+
+               # make sure stored in repo as unix newlines
+               # use sed to eat python-appened newline
+               p4 -G print //depot/f-unix | marshal_dump data 2 |\
+                   sed \$d >f-unix-p4-print &&
+               test_cmp f-unix-orig f-unix-p4-print &&
+
+               # switch to win, make sure lf -> crlf
+               p4 client -o | sed "/LineEnd/s/:.*/:win/" | p4 client -i &&
+               p4 sync -f &&
+               test_cmp f-unix-as-crlf f-unix
+       )
+'
+
+test_expect_success 'p4 client newlines, win' '
+       (
+               cd "$cli" &&
+               p4 client -o | sed "/LineEnd/s/:.*/:win/" | p4 client -i &&
+               printf "win\r\ncrlf\r\n" >f-win &&
+               printf "win\ncrlf\n" >f-win-as-lf &&
+               p4 add -t text f-win &&
+               p4 submit -d f-win &&
+
+               # LineEnd: win; should be no change after sync
+               cp f-win f-win-orig &&
+               p4 sync -f &&
+               test_cmp f-win-orig f-win &&
+
+               # make sure stored in repo as unix newlines
+               # use sed to eat python-appened newline
+               p4 -G print //depot/f-win | marshal_dump data 2 |\
+                   sed \$d >f-win-p4-print &&
+               test_cmp f-win-as-lf f-win-p4-print &&
+
+               # switch to unix, make sure lf -> crlf
+               p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i &&
+               p4 sync -f &&
+               test_cmp f-win-as-lf f-win
+       )
+'
+
+test_expect_success 'ensure blobs store only lf newlines' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init &&
+               git p4 sync //depot@all &&
+
+               # verify the files in .git are stored only with newlines
+               o=$(git ls-tree p4/master -- f-unix | cut -f1 | cut -d\  -f3) &&
+               git cat-file blob $o >f-unix-blob &&
+               test_cmp "$cli"/f-unix-orig f-unix-blob &&
+
+               o=$(git ls-tree p4/master -- f-win | cut -f1 | cut -d\  -f3) &&
+               git cat-file blob $o >f-win-blob &&
+               test_cmp "$cli"/f-win-as-lf f-win-blob &&
+
+               rm f-unix-blob f-win-blob
+       )
+'
+
+test_expect_success 'gitattributes setting eol=lf produces lf newlines' '
+       test_when_finished cleanup_git &&
+       (
+               # checkout the files and make sure core.eol works as planned
+               cd "$git" &&
+               git init &&
+               echo "* eol=lf" >.gitattributes &&
+               git p4 sync //depot@all &&
+               git checkout master &&
+               test_cmp "$cli"/f-unix-orig f-unix &&
+               test_cmp "$cli"/f-win-as-lf f-win
+       )
+'
+
+test_expect_success 'gitattributes setting eol=crlf produces crlf newlines' '
+       test_when_finished cleanup_git &&
+       (
+               # checkout the files and make sure core.eol works as planned
+               cd "$git" &&
+               git init &&
+               echo "* eol=crlf" >.gitattributes &&
+               git p4 sync //depot@all &&
+               git checkout master &&
+               test_cmp "$cli"/f-unix-as-crlf f-unix &&
+               test_cmp "$cli"/f-win-orig f-win
+       )
+'
+
+test_expect_success 'crlf cleanup' '
+       (
+               cd "$cli" &&
+               rm f-unix-orig f-unix-as-crlf &&
+               rm f-win-orig f-win-as-lf &&
+               p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i &&
+               p4 sync -f
+       )
+'
+
 test_expect_success 'utf-16 file create' '
        (
                cd "$cli" &&
@@ -105,12 +222,13 @@ build_gendouble() {
        cat >gendouble.py <<-\EOF
        import sys
        import struct
-       import array
 
-       s = array.array("c", '\0' * 26)
-       struct.pack_into(">L", s,  0, 0x00051607)  # AppleDouble
-       struct.pack_into(">L", s,  4, 0x00020000)  # version 2
-       s.tofile(sys.stdout)
+       s = struct.pack(">LL18s",
+                       0x00051607,  # AppleDouble
+                       0x00020000,  # version 2
+                       ""           # pad to 26 bytes
+       )
+       sys.stdout.write(s)
        EOF
 }
 
index fa40cc8bb59a4e785681013e1dc676709d808410..254d428b73fe4983298f57e0d87405002abdfbc2 100755 (executable)
@@ -27,14 +27,102 @@ test_expect_success 'clone no --git-dir' '
        test_must_fail git p4 clone --git-dir=xx //depot
 '
 
-test_expect_success 'clone --branch' '
+test_expect_success 'clone --branch should checkout master' '
        git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               git ls-files >files &&
-               test_line_count = 0 files &&
-               test_path_is_file .git/refs/remotes/p4/sb
+               git rev-parse refs/remotes/p4/sb >sb &&
+               git rev-parse refs/heads/master >master &&
+               test_cmp sb master &&
+               git rev-parse HEAD >head &&
+               test_cmp sb head
+       )
+'
+
+test_expect_success 'sync when no master branch prints a nice error' '
+       test_when_finished cleanup_git &&
+       git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot@2 &&
+       (
+               cd "$git" &&
+               test_must_fail git p4 sync 2>err &&
+               grep "Error: no branch refs/remotes/p4/master" err
+       )
+'
+
+test_expect_success 'sync --branch builds the full ref name correctly' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init &&
+
+               git p4 sync --branch=b1 //depot &&
+               git rev-parse --verify refs/remotes/p4/b1 &&
+               git p4 sync --branch=p4/b2 //depot &&
+               git rev-parse --verify refs/remotes/p4/b2 &&
+
+               git p4 sync --import-local --branch=h1 //depot &&
+               git rev-parse --verify refs/heads/p4/h1 &&
+               git p4 sync --import-local --branch=p4/h2 //depot &&
+               git rev-parse --verify refs/heads/p4/h2 &&
+
+               git p4 sync --branch=refs/stuff //depot &&
+               git rev-parse --verify refs/stuff
+       )
+'
+
+# engages --detect-branches code, which will do filename filtering so
+# no sync to either b1 or b2
+test_expect_success 'sync when two branches but no master should noop' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init &&
+               git p4 sync --branch=refs/remotes/p4/b1 //depot@2 &&
+               git p4 sync --branch=refs/remotes/p4/b2 //depot@2 &&
+               git p4 sync &&
+               git show -s --format=%s refs/remotes/p4/b1 >show &&
+               grep "Initial import" show &&
+               git show -s --format=%s refs/remotes/p4/b2 >show &&
+               grep "Initial import" show
+       )
+'
+
+test_expect_success 'sync --branch updates specific branch, no detection' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init &&
+               git p4 sync --branch=b1 //depot@2 &&
+               git p4 sync --branch=b2 //depot@2 &&
+               git p4 sync --branch=b2 &&
+               git show -s --format=%s refs/remotes/p4/b1 >show &&
+               grep "Initial import" show &&
+               git show -s --format=%s refs/remotes/p4/b2 >show &&
+               grep "change 3" show
+       )
+'
+
+# allows using the refname "p4" as a short name for p4/master
+test_expect_success 'clone creates HEAD symbolic reference' '
+       git p4 clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git rev-parse --verify refs/remotes/p4/master >master &&
+               git rev-parse --verify p4 >p4 &&
+               test_cmp master p4
+       )
+'
+
+test_expect_success 'clone --branch creates HEAD symbolic reference' '
+       git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git rev-parse --verify refs/remotes/p4/sb >sb &&
+               git rev-parse --verify p4 >p4 &&
+               test_cmp sb p4
        )
 '
 
@@ -126,37 +214,58 @@ test_expect_success 'clone --use-client-spec' '
                exec >/dev/null &&
                test_must_fail git p4 clone --dest="$git" --use-client-spec
        ) &&
-       cli2=$(test-path-utils real_path "$TRASH_DIRECTORY/cli2") &&
+       # build a different client
+       cli2="$TRASH_DIRECTORY/cli2" &&
        mkdir -p "$cli2" &&
        test_when_finished "rmdir \"$cli2\"" &&
+       test_when_finished cleanup_git &&
        (
-               cd "$cli2" &&
-               p4 client -i <<-EOF
-               Client: client2
-               Description: client2
-               Root: $cli2
-               View: //depot/sub/... //client2/bus/...
-               EOF
-       ) &&
-       P4CLIENT=client2 &&
+               # group P4CLIENT and cli changes in a sub-shell
+               P4CLIENT=client2 &&
+               cli="$cli2" &&
+               client_view "//depot/sub/... //client2/bus/..." &&
+               git p4 clone --dest="$git" --use-client-spec //depot/... &&
+               (
+                       cd "$git" &&
+                       test_path_is_file bus/dir/f4 &&
+                       test_path_is_missing file1
+               ) &&
+               cleanup_git &&
+               # same thing again, this time with variable instead of option
+               (
+                       cd "$git" &&
+                       git init &&
+                       git config git-p4.useClientSpec true &&
+                       git p4 sync //depot/... &&
+                       git checkout -b master p4/master &&
+                       test_path_is_file bus/dir/f4 &&
+                       test_path_is_missing file1
+               )
+       )
+'
+
+test_expect_success 'submit works with no p4/master' '
        test_when_finished cleanup_git &&
-       git p4 clone --dest="$git" --use-client-spec //depot/... &&
+       git p4 clone --branch=b1 //depot@1,2 --destination="$git" &&
        (
                cd "$git" &&
-               test_path_is_file bus/dir/f4 &&
-               test_path_is_missing file1
-       ) &&
-       cleanup_git &&
+               test_commit submit-1-branch &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit --branch=b1
+       )
+'
 
-       # same thing again, this time with variable instead of option
+# The sync/rebase part post-submit will engage detect-branches
+# machinery which will not do anything in this particular test.
+test_expect_success 'submit works with two branches' '
+       test_when_finished cleanup_git &&
+       git p4 clone --branch=b1 //depot@1,2 --destination="$git" &&
        (
                cd "$git" &&
-               git init &&
-               git config git-p4.useClientSpec true &&
-               git p4 sync //depot/... &&
-               git checkout -b master p4/master &&
-               test_path_is_file bus/dir/f4 &&
-               test_path_is_missing file1
+               git p4 sync --branch=b2 //depot@1,3 &&
+               test_commit submit-2-branches &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
        )
 '
 
index 0ae048f29f548eac6cfa37fbabcd9b7f80440515..1fb7bc7cf98bdbdf89491c687e6347cda673d720 100755 (executable)
@@ -17,6 +17,16 @@ test_expect_success 'init depot' '
        )
 '
 
+test_expect_failure 'is_cli_file_writeable function' '
+       (
+               cd "$cli" &&
+               echo a >a &&
+               is_cli_file_writeable a &&
+               ! is_cli_file_writeable file1 &&
+               rm a
+       )
+'
+
 test_expect_success 'submit with no client dir' '
        test_when_finished cleanup_git &&
        git p4 clone --dest="$git" //depot &&
@@ -200,7 +210,7 @@ test_expect_success 'submit copy' '
        (
                cd "$cli" &&
                test_path_is_file file5.ta &&
-               test ! -w file5.ta
+               ! is_cli_file_writeable file5.ta
        )
 '
 
@@ -219,7 +229,7 @@ test_expect_success 'submit rename' '
                cd "$cli" &&
                test_path_is_missing file6.t &&
                test_path_is_file file6.ta &&
-               test ! -w file6.ta
+               ! is_cli_file_writeable file6.ta
        )
 '
 
index 281be29174b8b4fea854d72c663eaa2d46b00259..77f63492d90e61500e6665ba447fbbad7a8c7b3b 100755 (executable)
@@ -196,7 +196,7 @@ test_expect_success 'exclusion single file' '
 
 test_expect_success 'overlay wildcard' '
        client_view "//depot/dir1/... //client/cli/..." \
-                   "+//depot/dir2/... //client/cli/...\n" &&
+                   "+//depot/dir2/... //client/cli/..." &&
        files="cli/file11 cli/file12 cli/file21 cli/file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
@@ -333,7 +333,7 @@ test_expect_success 'subdir clone, submit copy' '
        (
                cd "$cli" &&
                test_path_is_file dir1/file11a &&
-               test ! -w dir1/file11a
+               ! is_cli_file_writeable dir1/file11a
        )
 '
 
@@ -353,7 +353,7 @@ test_expect_success 'subdir clone, submit rename' '
                cd "$cli" &&
                test_path_is_missing dir1/file13 &&
                test_path_is_file dir1/file13a &&
-               test ! -w dir1/file13a
+               ! is_cli_file_writeable dir1/file13a
        )
 '
 
@@ -365,7 +365,10 @@ test_expect_success 'wildcard files submit back to p4, client-spec case' '
        (
                cd "$git" &&
                echo git-wild-hash >dir1/git-wild#hash &&
-               echo git-wild-star >dir1/git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       echo git-wild-star >dir1/git-wild\*star
+               fi &&
                echo git-wild-at >dir1/git-wild@at &&
                echo git-wild-percent >dir1/git-wild%percent &&
                git add dir1/git-wild* &&
@@ -376,7 +379,10 @@ test_expect_success 'wildcard files submit back to p4, client-spec case' '
        (
                cd "$cli" &&
                test_path_is_file dir1/git-wild#hash &&
-               test_path_is_file dir1/git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       test_path_is_file dir1/git-wild\*star
+               fi &&
                test_path_is_file dir1/git-wild@at &&
                test_path_is_file dir1/git-wild%percent
        ) &&
index 143d41305785090837bcd233cf67f0d57dfcff44..67633257f3fccb2408d678759bce87f7a58acd0a 100755 (executable)
@@ -14,7 +14,10 @@ test_expect_success 'add p4 files with wildcards in the names' '
                printf "file2\nhas\nsome\nrandom\ntext\n" >file2 &&
                p4 add file2 &&
                echo file-wild-hash >file-wild#hash &&
-               echo file-wild-star >file-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       echo file-wild-star >file-wild\*star
+               fi &&
                echo file-wild-at >file-wild@at &&
                echo file-wild-percent >file-wild%percent &&
                p4 add -f file-wild* &&
@@ -28,7 +31,10 @@ test_expect_success 'wildcard files git p4 clone' '
        (
                cd "$git" &&
                test -f file-wild#hash &&
-               test -f file-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       test -f file-wild\*star
+               fi &&
                test -f file-wild@at &&
                test -f file-wild%percent
        )
@@ -40,7 +46,10 @@ test_expect_success 'wildcard files submit back to p4, add' '
        (
                cd "$git" &&
                echo git-wild-hash >git-wild#hash &&
-               echo git-wild-star >git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       echo git-wild-star >git-wild\*star
+               fi &&
                echo git-wild-at >git-wild@at &&
                echo git-wild-percent >git-wild%percent &&
                git add git-wild* &&
@@ -51,7 +60,10 @@ test_expect_success 'wildcard files submit back to p4, add' '
        (
                cd "$cli" &&
                test_path_is_file git-wild#hash &&
-               test_path_is_file git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       test_path_is_file git-wild\*star
+               fi &&
                test_path_is_file git-wild@at &&
                test_path_is_file git-wild%percent
        )
@@ -63,7 +75,10 @@ test_expect_success 'wildcard files submit back to p4, modify' '
        (
                cd "$git" &&
                echo new-line >>git-wild#hash &&
-               echo new-line >>git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       echo new-line >>git-wild\*star
+               fi &&
                echo new-line >>git-wild@at &&
                echo new-line >>git-wild%percent &&
                git add git-wild* &&
@@ -74,7 +89,10 @@ test_expect_success 'wildcard files submit back to p4, modify' '
        (
                cd "$cli" &&
                test_line_count = 2 git-wild#hash &&
-               test_line_count = 2 git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       test_line_count = 2 git-wild\*star
+               fi &&
                test_line_count = 2 git-wild@at &&
                test_line_count = 2 git-wild%percent
        )
@@ -87,7 +105,7 @@ test_expect_success 'wildcard files submit back to p4, copy' '
                cd "$git" &&
                cp file2 git-wild-cp#hash &&
                git add git-wild-cp#hash &&
-               cp git-wild\*star file-wild-3 &&
+               cp git-wild#hash file-wild-3 &&
                git add file-wild-3 &&
                git commit -m "wildcard copies" &&
                git config git-p4.detectCopies true &&
@@ -134,7 +152,10 @@ test_expect_success 'wildcard files submit back to p4, delete' '
        (
                cd "$cli" &&
                test_path_is_missing git-wild#hash &&
-               test_path_is_missing git-wild\*star &&
+               if test_have_prereq NOT_MINGW NOT_CYGWIN
+               then
+                       test_path_is_missing git-wild\*star
+               fi &&
                test_path_is_missing git-wild@at &&
                test_path_is_missing git-wild%percent
        )
index d2b7b3d98d77a18657f536af54b159230a1ea057..1243d96092ad5c6a66d66ee9debc399649ffa0a2 100755 (executable)
@@ -405,8 +405,8 @@ test_expect_success 'cleanup chmod after submit cancel' '
        git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
-               chmod u+x text &&
-               chmod u-x text+x &&
+               test_chmod +x text &&
+               test_chmod -x text+x &&
                git add text text+x &&
                git commit -m "chmod texts" &&
                echo n | test_expect_code 1 git p4 submit
@@ -415,10 +415,13 @@ test_expect_success 'cleanup chmod after submit cancel' '
                cd "$cli" &&
                test_path_is_file text &&
                ! p4 fstat -T action text &&
-               stat --format=%A text | egrep ^-r-- &&
                test_path_is_file text+x &&
                ! p4 fstat -T action text+x &&
-               stat --format=%A text+x | egrep ^-r-x
+               if test_have_prereq NOT_CYGWIN
+               then
+                       stat --format=%A text | egrep ^-r-- &&
+                       stat --format=%A text+x | egrep ^-r-x
+               fi
        )
 '
 
index 3cd53f87fb70443bc51bfec13d92a82d7f73ec7c..adc1372b3c334b4305761f6c3922493c85a0822a 100755 (executable)
@@ -13,6 +13,25 @@ complete ()
        return 0
 }
 
+# Be careful when updating this list:
+#
+# (1) The build tree may have build artifact from different branch, or
+#     the user's $PATH may have a random executable that may begin
+#     with "git-check" that are not part of the subcommands this build
+#     will ship, e.g.  "check-ignore".  The tests for completion for
+#     subcommand names tests how "check" is expanded; we limit the
+#     possible candidates to "checkout" and "check-attr" to make sure
+#     "check-attr", which is known by the filter function as a
+#     subcommand to be thrown out, while excluding other random files
+#     that happen to begin with "check" to avoid letting them get in
+#     the way.
+#
+# (2) A test makes sure that common subcommands are included in the
+#     completion for "git <TAB>", and a plumbing is excluded.  "add",
+#     "filter-branch" and "ls-files" are listed for this.
+
+GIT_TESTING_COMMAND_COMPLETION='add checkout check-attr filter-branch ls-files'
+
 . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
 
 # We don't need this function to actually join words or do anything special.
@@ -196,7 +215,6 @@ test_expect_success 'general options plus command' '
        test_completion "git --paginate check" "checkout " &&
        test_completion "git --git-dir=foo check" "checkout " &&
        test_completion "git --bare check" "checkout " &&
-       test_completion "git --help des" "describe " &&
        test_completion "git --exec-path=foo check" "checkout " &&
        test_completion "git --html-path check" "checkout " &&
        test_completion "git --no-pager check" "checkout " &&
@@ -207,6 +225,11 @@ test_expect_success 'general options plus command' '
        test_completion "git --no-replace-objects check" "checkout "
 '
 
+test_expect_success 'git --help completion' '
+       test_completion "git --help ad" "add " &&
+       test_completion "git --help core" "core-tutorial "
+'
+
 test_expect_success 'setup for ref completion' '
        echo content >file1 &&
        echo more >file2 &&
index 1a6c4ab08cd5adb56869855913272ec42eceb45f..9e7f6b424d77ee88c3cd36507cd421a437ec7bfa 100644 (file)
@@ -666,12 +666,14 @@ case $(uname -s) in
        # backslashes in pathspec are converted to '/'
        # exec does not inherit the PID
        test_set_prereq MINGW
+       test_set_prereq NOT_CYGWIN
        test_set_prereq SED_STRIPS_CR
        ;;
 *CYGWIN*)
        test_set_prereq POSIXPERM
        test_set_prereq EXECKEEPSPID
        test_set_prereq NOT_MINGW
+       test_set_prereq CYGWIN
        test_set_prereq SED_STRIPS_CR
        ;;
 *)
@@ -679,6 +681,7 @@ case $(uname -s) in
        test_set_prereq BSLASHPSPEC
        test_set_prereq EXECKEEPSPID
        test_set_prereq NOT_MINGW
+       test_set_prereq NOT_CYGWIN
        ;;
 esac
 
diff --git a/templates/hooks--pre-push.sample b/templates/hooks--pre-push.sample
new file mode 100644 (file)
index 0000000..15ab6d8
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+# An example hook script to verify what is about to be pushed.  Called by "git
+# push" after it has checked the remote status, but before anything has been
+# pushed.  If this script exits with a non-zero status nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+# If pushing without using a named remote those arguments will be equal.
+#
+# Information about the commits which are being pushed is supplied as lines to
+# the standard input in the form:
+#
+#   <local ref> <local sha1> <remote ref> <remote sha1>
+#
+# This sample shows how to prevent push of commits where the log message starts
+# with "WIP" (work in progress).
+
+remote="$1"
+url="$2"
+
+z40=0000000000000000000000000000000000000000
+
+IFS=' '
+while read local_ref local_sha remote_ref remote_sha
+do
+       if [ "$local_sha" = $z40 ]
+       then
+               # Handle delete
+       else
+               if [ "$remote_sha" = $z40 ]
+               then
+                       # New branch, examine all commits
+                       range="$local_sha"
+               else
+                       # Update to existing branch, examine new commits
+                       range="$remote_sha..$local_sha"
+               fi
+
+               # Check for WIP commit
+               commit=`git rev-list -n 1 --grep '^WIP' "$range"`
+               if [ -n "$commit" ]
+               then
+                       echo "Found WIP commit in $local_ref, not pushing"
+                       exit 1
+               fi
+       fi
+done
+
+exit 0
index e384c8edb104c46c6cfe29677eccc8199fafe080..a3e2643fbc5022cabb671054db7950bc991652b9 100644 (file)
@@ -1,9 +1,85 @@
+#ifdef USE_WILDMATCH
+#undef USE_WILDMATCH  /* We need real fnmatch implementation here */
+#endif
 #include "cache.h"
 #include "wildmatch.h"
 
+static int perf(int ac, char **av)
+{
+       struct timeval tv1, tv2;
+       struct stat st;
+       int fd, i, n, flags1 = 0, flags2 = 0;
+       char *buffer, *p;
+       uint32_t usec1, usec2;
+       const char *lang;
+       const char *file = av[0];
+       const char *pattern = av[1];
+
+       lang = getenv("LANG");
+       if (lang && strcmp(lang, "C"))
+               die("Please test it on C locale.");
+
+       if ((fd = open(file, O_RDONLY)) == -1 || fstat(fd, &st))
+               die_errno("file open");
+
+       buffer = xmalloc(st.st_size + 2);
+       if (read(fd, buffer, st.st_size) != st.st_size)
+               die_errno("read");
+
+       buffer[st.st_size] = '\0';
+       buffer[st.st_size + 1] = '\0';
+       for (i = 0; i < st.st_size; i++)
+               if (buffer[i] == '\n')
+                       buffer[i] = '\0';
+
+       n = atoi(av[2]);
+       if (av[3] && !strcmp(av[3], "pathname")) {
+               flags1 = WM_PATHNAME;
+               flags2 = FNM_PATHNAME;
+       }
+
+       gettimeofday(&tv1, NULL);
+       for (i = 0; i < n; i++) {
+               for (p = buffer; *p; p += strlen(p) + 1)
+                       wildmatch(pattern, p, flags1, NULL);
+       }
+       gettimeofday(&tv2, NULL);
+
+       usec1 = (uint32_t)tv2.tv_sec * 1000000 + tv2.tv_usec;
+       usec1 -= (uint32_t)tv1.tv_sec * 1000000 + tv1.tv_usec;
+       printf("wildmatch %ds %dus\n",
+              (int)(usec1 / 1000000),
+              (int)(usec1 % 1000000));
+
+       gettimeofday(&tv1, NULL);
+       for (i = 0; i < n; i++) {
+               for (p = buffer; *p; p += strlen(p) + 1)
+                       fnmatch(pattern, p, flags2);
+       }
+       gettimeofday(&tv2, NULL);
+
+       usec2 = (uint32_t)tv2.tv_sec * 1000000 + tv2.tv_usec;
+       usec2 -= (uint32_t)tv1.tv_sec * 1000000 + tv1.tv_usec;
+       if (usec2 > usec1)
+               printf("fnmatch   %ds %dus or %.2f%% slower\n",
+                      (int)((usec2 - usec1) / 1000000),
+                      (int)((usec2 - usec1) % 1000000),
+                      (float)(usec2 - usec1) / usec1 * 100);
+       else
+               printf("fnmatch   %ds %dus or %.2f%% faster\n",
+                      (int)((usec1 - usec2) / 1000000),
+                      (int)((usec1 - usec2) % 1000000),
+                      (float)(usec1 - usec2) / usec1 * 100);
+       return 0;
+}
+
 int main(int argc, char **argv)
 {
        int i;
+
+       if (!strcmp(argv[1], "perf"))
+               return perf(argc - 2, argv + 2);
+
        for (i = 2; i < argc; i++) {
                if (argv[i][0] == '/')
                        die("Forward slash is not allowed at the beginning of the\n"
@@ -12,9 +88,11 @@ int main(int argc, char **argv)
                        argv[i] += 3;
        }
        if (!strcmp(argv[1], "wildmatch"))
-               return !!wildmatch(argv[3], argv[2], 0);
+               return !!wildmatch(argv[3], argv[2], WM_PATHNAME, NULL);
        else if (!strcmp(argv[1], "iwildmatch"))
-               return !!wildmatch(argv[3], argv[2], FNM_CASEFOLD);
+               return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD, NULL);
+       else if (!strcmp(argv[1], "pathmatch"))
+               return !!wildmatch(argv[3], argv[2], 0, NULL);
        else if (!strcmp(argv[1], "fnmatch"))
                return !!fnmatch(argv[3], argv[2], FNM_PATHNAME);
        else
index 965b778cb3bb6fdd8068dbc7f9603cf9815c028d..cb3ef7d38e475480a5d81ce6077c4c87ea09a318 100644 (file)
@@ -666,6 +666,16 @@ static void push_update_ref_status(struct strbuf *buf,
                        free(msg);
                        msg = NULL;
                }
+               else if (!strcmp(msg, "fetch first")) {
+                       status = REF_STATUS_REJECT_FETCH_FIRST;
+                       free(msg);
+                       msg = NULL;
+               }
+               else if (!strcmp(msg, "needs force")) {
+                       status = REF_STATUS_REJECT_NEEDS_FORCE;
+                       free(msg);
+                       msg = NULL;
+               }
        }
 
        if (*ref)
index 2673d273ff3aa5530ee57e8a3916bbbbc802466d..886ffd8b1e1c9dbb31f3156844790ff98d5ecde3 100644 (file)
@@ -659,7 +659,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
                const char *msg;
 
                strcpy(quickref, status_abbrev(ref->old_sha1));
-               if (ref->requires_force) {
+               if (ref->forced_update) {
                        strcat(quickref, "...");
                        type = '+';
                        msg = "forced update";
@@ -699,6 +699,14 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
                                                 "already exists", porcelain);
                break;
+       case REF_STATUS_REJECT_FETCH_FIRST:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "fetch first", porcelain);
+               break;
+       case REF_STATUS_REJECT_NEEDS_FORCE:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "needs force", porcelain);
+               break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
                                                 ref->deletion ? NULL : ref->peer_ref,
@@ -744,12 +752,16 @@ void transport_print_push_status(const char *dest, struct ref *refs,
                    ref->status != REF_STATUS_OK)
                        n += print_one_push_status(ref, dest, n, porcelain);
                if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {
-                       if (!strcmp(head, ref->name))
+                       if (head != NULL && !strcmp(head, ref->name))
                                *reject_reasons |= REJECT_NON_FF_HEAD;
                        else
                                *reject_reasons |= REJECT_NON_FF_OTHER;
                } else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {
                        *reject_reasons |= REJECT_ALREADY_EXISTS;
+               } else if (ref->status == REF_STATUS_REJECT_FETCH_FIRST) {
+                       *reject_reasons |= REJECT_FETCH_FIRST;
+               } else if (ref->status == REF_STATUS_REJECT_NEEDS_FORCE) {
+                       *reject_reasons |= REJECT_NEEDS_FORCE;
                }
        }
 }
@@ -1034,6 +1046,62 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)
        die("Aborting.");
 }
 
+static int run_pre_push_hook(struct transport *transport,
+                            struct ref *remote_refs)
+{
+       int ret = 0, x;
+       struct ref *r;
+       struct child_process proc;
+       struct strbuf buf;
+       const char *argv[4];
+
+       if (!(argv[0] = find_hook("pre-push")))
+               return 0;
+
+       argv[1] = transport->remote->name;
+       argv[2] = transport->url;
+       argv[3] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.argv = argv;
+       proc.in = -1;
+
+       if (start_command(&proc)) {
+               finish_command(&proc);
+               return -1;
+       }
+
+       strbuf_init(&buf, 256);
+
+       for (r = remote_refs; r; r = r->next) {
+               if (!r->peer_ref) continue;
+               if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
+               if (r->status == REF_STATUS_UPTODATE) continue;
+
+               strbuf_reset(&buf);
+               strbuf_addf( &buf, "%s %s %s %s\n",
+                        r->peer_ref->name, sha1_to_hex(r->new_sha1),
+                        r->name, sha1_to_hex(r->old_sha1));
+
+               if (write_in_full(proc.in, buf.buf, buf.len) != buf.len) {
+                       ret = -1;
+                       break;
+               }
+       }
+
+       strbuf_release(&buf);
+
+       x = close(proc.in);
+       if (!ret)
+               ret = x;
+
+       x = finish_command(&proc);
+       if (!ret)
+               ret = x;
+
+       return ret;
+}
+
 int transport_push(struct transport *transport,
                   int refspec_nr, const char **refspec, int flags,
                   unsigned int *reject_reasons)
@@ -1074,6 +1142,10 @@ int transport_push(struct transport *transport,
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
 
+               if (!(flags & TRANSPORT_PUSH_NO_HOOK))
+                       if (run_pre_push_hook(transport, remote_refs))
+                               return -1;
+
                if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        for (; ref; ref = ref->next)
index bfd2df5823aac55e4ce8674b7980cccabf0fed5f..a3450e97c0d0d94778a4b635efdd53b724eb70c2 100644 (file)
@@ -104,6 +104,7 @@ struct transport {
 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 #define TRANSPORT_PUSH_PRUNE 128
 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
+#define TRANSPORT_PUSH_NO_HOOK 512
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
@@ -143,6 +144,8 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
 #define REJECT_NON_FF_HEAD     0x01
 #define REJECT_NON_FF_OTHER    0x02
 #define REJECT_ALREADY_EXISTS  0x04
+#define REJECT_FETCH_FIRST     0x08
+#define REJECT_NEEDS_FORCE     0x10
 
 int transport_push(struct transport *connection,
                   int refspec_nr, const char **refspec, int flags,
index 0e1a196ace33110e2bb74cde90d6f62358413ead..09e53df3b2a39e7ec8c7d601c5b8b8ae4de39b9a 100644 (file)
@@ -1020,7 +1020,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        if (!core_apply_sparse_checkout || !o->update)
                o->skip_sparse_checkout = 1;
        if (!o->skip_sparse_checkout) {
-               if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0)
+               if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, &el, 0) < 0)
                        o->skip_sparse_checkout = 1;
                else
                        o->el = &el;
index 95d83135ae95b2fa7980c69cbd7b49e3a6ff2d0a..7c05b15e68d6deed45a172df397e814fcf2032b0 100644 (file)
@@ -672,10 +672,17 @@ static void receive_needs(void)
        if (depth == 0 && shallows.nr == 0)
                return;
        if (depth > 0) {
-               struct commit_list *result, *backup;
+               struct commit_list *result = NULL, *backup = NULL;
                int i;
-               backup = result = get_shallow_commits(&want_obj, depth,
-                       SHALLOW, NOT_SHALLOW);
+               if (depth == INFINITE_DEPTH)
+                       for (i = 0; i < shallows.nr; i++) {
+                               struct object *object = shallows.objects[i].item;
+                               object->flags |= NOT_SHALLOW;
+                       }
+               else
+                       backup = result =
+                               get_shallow_commits(&want_obj, depth,
+                                                   SHALLOW, NOT_SHALLOW);
                while (result) {
                        struct object *object = &result->item->object;
                        if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) {
index ed958ef6b8b912436ad0145fe650adea083412e3..ea43a0306f1a017f719e0d6b7c1de8b063760184 100644 (file)
@@ -184,35 +184,6 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *k, int len)
        return NULL;
 }
 
-static struct userdiff_driver *parse_driver(const char *var,
-               const char *value, const char *type)
-{
-       struct userdiff_driver *drv;
-       const char *dot;
-       const char *name;
-       int namelen;
-
-       if (prefixcmp(var, "diff."))
-               return NULL;
-       dot = strrchr(var, '.');
-       if (dot == var + 4)
-               return NULL;
-       if (strcmp(type, dot+1))
-               return NULL;
-
-       name = var + 5;
-       namelen = dot - name;
-       drv = userdiff_find_by_namelen(name, namelen);
-       if (!drv) {
-               ALLOC_GROW(drivers, ndrivers+1, drivers_alloc);
-               drv = &drivers[ndrivers++];
-               memset(drv, 0, sizeof(*drv));
-               drv->name = xmemdupz(name, namelen);
-               drv->binary = -1;
-       }
-       return drv;
-}
-
 static int parse_funcname(struct userdiff_funcname *f, const char *k,
                const char *v, int cflags)
 {
@@ -240,20 +211,34 @@ static int parse_bool(int *b, const char *k, const char *v)
 int userdiff_config(const char *k, const char *v)
 {
        struct userdiff_driver *drv;
+       const char *name, *type;
+       int namelen;
+
+       if (parse_config_key(k, "diff", &name, &namelen, &type) || !name)
+               return 0;
+
+       drv = userdiff_find_by_namelen(name, namelen);
+       if (!drv) {
+               ALLOC_GROW(drivers, ndrivers+1, drivers_alloc);
+               drv = &drivers[ndrivers++];
+               memset(drv, 0, sizeof(*drv));
+               drv->name = xmemdupz(name, namelen);
+               drv->binary = -1;
+       }
 
-       if ((drv = parse_driver(k, v, "funcname")))
+       if (!strcmp(type, "funcname"))
                return parse_funcname(&drv->funcname, k, v, 0);
-       if ((drv = parse_driver(k, v, "xfuncname")))
+       if (!strcmp(type, "xfuncname"))
                return parse_funcname(&drv->funcname, k, v, REG_EXTENDED);
-       if ((drv = parse_driver(k, v, "binary")))
+       if (!strcmp(type, "binary"))
                return parse_tristate(&drv->binary, k, v);
-       if ((drv = parse_driver(k, v, "command")))
+       if (!strcmp(type, "command"))
                return git_config_string(&drv->external, k, v);
-       if ((drv = parse_driver(k, v, "textconv")))
+       if (!strcmp(type, "textconv"))
                return git_config_string(&drv->textconv, k, v);
-       if ((drv = parse_driver(k, v, "cachetextconv")))
+       if (!strcmp(type, "cachetextconv"))
                return parse_bool(&drv->textconv_want_cache, k, v);
-       if ((drv = parse_driver(k, v, "wordregex")))
+       if (!strcmp(type, "wordregex"))
                return git_config_string(&drv->word_regex, k, v);
 
        return 0;
index 2d3ed84364147d6d61252094566e7275d4c6aeb3..7192bdc1b880728a81b33a38091cca8de6a30445 100644 (file)
@@ -18,9 +18,6 @@ typedef unsigned char uchar;
 #define NEGATE_CLASS   '!'
 #define NEGATE_CLASS2  '^'
 
-#define FALSE 0
-#define TRUE 1
-
 #define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
                                    && *(class) == *(litmatch) \
                                    && strncmp((char*)class, litmatch, len) == 0)
@@ -55,7 +52,7 @@ typedef unsigned char uchar;
 #define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
 
 /* Match pattern "p" against "text" */
-static int dowild(const uchar *p, const uchar *text, int force_lower_case)
+static int dowild(const uchar *p, const uchar *text, unsigned int flags)
 {
        uchar p_ch;
        const uchar *pattern = p;
@@ -64,10 +61,10 @@ static int dowild(const uchar *p, const uchar *text, int force_lower_case)
                int matched, match_slash, negated;
                uchar t_ch, prev_ch;
                if ((t_ch = *text) == '\0' && p_ch != '*')
-                       return ABORT_ALL;
-               if (force_lower_case && ISUPPER(t_ch))
+                       return WM_ABORT_ALL;
+               if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))
                        t_ch = tolower(t_ch);
-               if (force_lower_case && ISUPPER(p_ch))
+               if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))
                        p_ch = tolower(p_ch);
                switch (p_ch) {
                case '\\':
@@ -77,18 +74,21 @@ static int dowild(const uchar *p, const uchar *text, int force_lower_case)
                        /* FALLTHROUGH */
                default:
                        if (t_ch != p_ch)
-                               return NOMATCH;
+                               return WM_NOMATCH;
                        continue;
                case '?':
                        /* Match anything but '/'. */
-                       if (t_ch == '/')
-                               return NOMATCH;
+                       if ((flags & WM_PATHNAME) && t_ch == '/')
+                               return WM_NOMATCH;
                        continue;
                case '*':
                        if (*++p == '*') {
                                const uchar *prev_p = p - 2;
                                while (*++p == '*') {}
-                               if ((prev_p < pattern || *prev_p == '/') &&
+                               if (!(flags & WM_PATHNAME))
+                                       /* without WM_PATHNAME, '*' == '**' */
+                                       match_slash = 1;
+                               else if ((prev_p < pattern || *prev_p == '/') &&
                                    (*p == '\0' || *p == '/' ||
                                     (p[0] == '\\' && p[1] == '/'))) {
                                        /*
@@ -101,135 +101,172 @@ static int dowild(const uchar *p, const uchar *text, int force_lower_case)
                                         * both foo/bar and foo/a/bar.
                                         */
                                        if (p[0] == '/' &&
-                                           dowild(p + 1, text, force_lower_case) == MATCH)
-                                               return MATCH;
-                                       match_slash = TRUE;
+                                           dowild(p + 1, text, flags) == WM_MATCH)
+                                               return WM_MATCH;
+                                       match_slash = 1;
                                } else
-                                       return ABORT_MALFORMED;
+                                       return WM_ABORT_MALFORMED;
                        } else
-                               match_slash = FALSE;
+                               /* without WM_PATHNAME, '*' == '**' */
+                               match_slash = flags & WM_PATHNAME ? 0 : 1;
                        if (*p == '\0') {
                                /* Trailing "**" matches everything.  Trailing "*" matches
                                 * only if there are no more slash characters. */
                                if (!match_slash) {
                                        if (strchr((char*)text, '/') != NULL)
-                                               return NOMATCH;
+                                               return WM_NOMATCH;
                                }
-                               return MATCH;
+                               return WM_MATCH;
+                       } else if (!match_slash && *p == '/') {
+                               /*
+                                * _one_ asterisk followed by a slash
+                                * with WM_PATHNAME matches the next
+                                * directory
+                                */
+                               const char *slash = strchr((char*)text, '/');
+                               if (!slash)
+                                       return WM_NOMATCH;
+                               text = (const uchar*)slash;
+                               /* the slash is consumed by the top-level for loop */
+                               break;
                        }
                        while (1) {
                                if (t_ch == '\0')
                                        break;
-                               if ((matched = dowild(p, text,  force_lower_case)) != NOMATCH) {
-                                       if (!match_slash || matched != ABORT_TO_STARSTAR)
+                               /*
+                                * Try to advance faster when an asterisk is
+                                * followed by a literal. We know in this case
+                                * that the the string before the literal
+                                * must belong to "*".
+                                * If match_slash is false, do not look past
+                                * the first slash as it cannot belong to '*'.
+                                */
+                               if (!is_glob_special(*p)) {
+                                       p_ch = *p;
+                                       if ((flags & WM_CASEFOLD) && ISUPPER(p_ch))
+                                               p_ch = tolower(p_ch);
+                                       while ((t_ch = *text) != '\0' &&
+                                              (match_slash || t_ch != '/')) {
+                                               if ((flags & WM_CASEFOLD) && ISUPPER(t_ch))
+                                                       t_ch = tolower(t_ch);
+                                               if (t_ch == p_ch)
+                                                       break;
+                                               text++;
+                                       }
+                                       if (t_ch != p_ch)
+                                               return WM_NOMATCH;
+                               }
+                               if ((matched = dowild(p, text, flags)) != WM_NOMATCH) {
+                                       if (!match_slash || matched != WM_ABORT_TO_STARSTAR)
                                                return matched;
                                } else if (!match_slash && t_ch == '/')
-                                       return ABORT_TO_STARSTAR;
+                                       return WM_ABORT_TO_STARSTAR;
                                t_ch = *++text;
                        }
-                       return ABORT_ALL;
+                       return WM_ABORT_ALL;
                case '[':
                        p_ch = *++p;
 #ifdef NEGATE_CLASS2
                        if (p_ch == NEGATE_CLASS2)
                                p_ch = NEGATE_CLASS;
 #endif
-                       /* Assign literal TRUE/FALSE because of "matched" comparison. */
-                       negated = p_ch == NEGATE_CLASS? TRUE : FALSE;
+                       /* Assign literal 1/0 because of "matched" comparison. */
+                       negated = p_ch == NEGATE_CLASS ? 1 : 0;
                        if (negated) {
                                /* Inverted character class. */
                                p_ch = *++p;
                        }
                        prev_ch = 0;
-                       matched = FALSE;
+                       matched = 0;
                        do {
                                if (!p_ch)
-                                       return ABORT_ALL;
+                                       return WM_ABORT_ALL;
                                if (p_ch == '\\') {
                                        p_ch = *++p;
                                        if (!p_ch)
-                                               return ABORT_ALL;
+                                               return WM_ABORT_ALL;
                                        if (t_ch == p_ch)
-                                               matched = TRUE;
+                                               matched = 1;
                                } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
                                        p_ch = *++p;
                                        if (p_ch == '\\') {
                                                p_ch = *++p;
                                                if (!p_ch)
-                                                       return ABORT_ALL;
+                                                       return WM_ABORT_ALL;
                                        }
                                        if (t_ch <= p_ch && t_ch >= prev_ch)
-                                               matched = TRUE;
+                                               matched = 1;
                                        p_ch = 0; /* This makes "prev_ch" get set to 0. */
                                } else if (p_ch == '[' && p[1] == ':') {
                                        const uchar *s;
                                        int i;
                                        for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
                                        if (!p_ch)
-                                               return ABORT_ALL;
+                                               return WM_ABORT_ALL;
                                        i = p - s - 1;
                                        if (i < 0 || p[-1] != ':') {
                                                /* Didn't find ":]", so treat like a normal set. */
                                                p = s - 2;
                                                p_ch = '[';
                                                if (t_ch == p_ch)
-                                                       matched = TRUE;
+                                                       matched = 1;
                                                continue;
                                        }
                                        if (CC_EQ(s,i, "alnum")) {
                                                if (ISALNUM(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "alpha")) {
                                                if (ISALPHA(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "blank")) {
                                                if (ISBLANK(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "cntrl")) {
                                                if (ISCNTRL(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "digit")) {
                                                if (ISDIGIT(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "graph")) {
                                                if (ISGRAPH(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "lower")) {
                                                if (ISLOWER(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "print")) {
                                                if (ISPRINT(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "punct")) {
                                                if (ISPUNCT(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "space")) {
                                                if (ISSPACE(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "upper")) {
                                                if (ISUPPER(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else if (CC_EQ(s,i, "xdigit")) {
                                                if (ISXDIGIT(t_ch))
-                                                       matched = TRUE;
+                                                       matched = 1;
                                        } else /* malformed [:class:] string */
-                                               return ABORT_ALL;
+                                               return WM_ABORT_ALL;
                                        p_ch = 0; /* This makes "prev_ch" get set to 0. */
                                } else if (t_ch == p_ch)
-                                       matched = TRUE;
+                                       matched = 1;
                        } while (prev_ch = p_ch, (p_ch = *++p) != ']');
-                       if (matched == negated || t_ch == '/')
-                               return NOMATCH;
+                       if (matched == negated ||
+                           ((flags & WM_PATHNAME) && t_ch == '/'))
+                               return WM_NOMATCH;
                        continue;
                }
        }
 
-       return *text ? NOMATCH : MATCH;
+       return *text ? WM_NOMATCH : WM_MATCH;
 }
 
 /* Match the "pattern" against the "text" string. */
-int wildmatch(const char *pattern, const char *text, int flags)
+int wildmatch(const char *pattern, const char *text,
+             unsigned int flags, struct wildopts *wo)
 {
-       return dowild((const uchar*)pattern, (const uchar*)text,
-                     flags & FNM_CASEFOLD ? 1 :0);
+       return dowild((const uchar*)pattern, (const uchar*)text, flags);
 }
index 984a38cdc2a1a6d3b19816ccc0a45055a9c8508a..4090c8f4bb0587d36ec01069f1e8d832ea46d7d6 100644 (file)
@@ -1,9 +1,18 @@
-/* wildmatch.h */
+#ifndef WILDMATCH_H
+#define WILDMATCH_H
 
-#define ABORT_MALFORMED 2
-#define NOMATCH 1
-#define MATCH 0
-#define ABORT_ALL -1
-#define ABORT_TO_STARSTAR -2
+#define WM_CASEFOLD 1
+#define WM_PATHNAME 2
 
-int wildmatch(const char *pattern, const char *text, int flags);
+#define WM_ABORT_MALFORMED 2
+#define WM_NOMATCH 1
+#define WM_MATCH 0
+#define WM_ABORT_ALL -1
+#define WM_ABORT_TO_STARSTAR -2
+
+struct wildopts;
+
+int wildmatch(const char *pattern, const char *text,
+             unsigned int flags,
+             struct wildopts *wo);
+#endif
index d7cfe8f31cd3d2b4b8bdc18c5da2449255b0531b..aa2734fcbea68850a23468d36cc0d33f1caec2a7 100644 (file)
@@ -45,7 +45,7 @@ static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
 
        strbuf_vaddf(&sb, fmt, ap);
        if (!sb.len) {
-               strbuf_addch(&sb, '#');
+               strbuf_addch(&sb, comment_line_char);
                if (!trail)
                        strbuf_addch(&sb, ' ');
                color_print_strbuf(s->fp, color, &sb);
@@ -59,7 +59,7 @@ static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
 
                strbuf_reset(&linebuf);
                if (at_bol) {
-                       strbuf_addch(&linebuf, '#');
+                       strbuf_addch(&linebuf, comment_line_char);
                        if (*line != '\n' && *line != '\t')
                                strbuf_addch(&linebuf, ' ');
                }
@@ -762,8 +762,10 @@ static void wt_status_print_tracking(struct wt_status *s)
 
        for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
-                                "# %.*s", (int)(ep - cp), cp);
-       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
+                                "%c %.*s", comment_line_char,
+                                (int)(ep - cp), cp);
+       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
+                        comment_line_char);
 }
 
 static int has_unmerged(struct wt_status *s)