David S. Miller <davem@davemloft.net>
Deskin Miller <deskinm@umich.edu>
Dirk Süsserott <newsletter@dirk.my1.cc>
+Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com>
Fredrik Kuivinen <freku045@student.liu.se>
H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
İsmail Dönmez <ismail@pardus.org.tr>
Jay Soffian <jaysoffian+git@gmail.com>
Joachim Berdal Haga <cjhaga@fys.uio.no>
+Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at>
+Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net>
+Johannes Sixt <j6t@kdbg.org> <J.Sixt@eudaptics.com>
Jon Loeliger <jdl@freescale.com>
Jon Seymour <jon@blackcubes.dyndns.org>
Jonathan Nieder <jrnieder@uchicago.edu>
Li Hong <leehong@pku.edu.cn>
Lukas Sandström <lukass@etek.chalmers.se>
Martin Langhoff <martin@laptop.org>
+Martin von Zweigbergk <martinvonz@gmail.com> <martin.von.zweigbergk@gmail.com>
Michael Coleman <tutufan@gmail.com>
Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm>
Michael W. Olson <mwolson@gnu.org>
Nanako Shiraishi <nanako3@lavabit.com>
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
<nico@fluxnic.net> <nico@cam.org>
+Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se>
+Peter Krefting <peter@softwolves.pp.se> <peter@softwolves.pp.se>
Philippe Bruhat <book@cpan.org>
+Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
René Scharfe <rene.scharfe@lsrfire.ath.cx>
Robert Fitzsimons <robfitz@273k.net>
Shawn O. Pearce <spearce@spearce.org>
Steven Grimm <koreth@midwinter.com>
Theodore Ts'o <tytso@mit.edu>
+Thomas Rast <trast@inf.ethz.ch> <trast@student.ethz.ch>
Tony Luck <tony.luck@intel.com>
Uwe Kleine-König <Uwe_Zeisberger@digi.com>
Uwe Kleine-König <Uwe.Kleine-Koenig@digi.com>
- We do not use Process Substitution <(list) or >(list).
+ - Do not write control structures on a single line with semicolon.
+ "then" should be on the next line for if statements, and "do"
+ should be on the next line for "while" and "for".
+
- We prefer "test" over "[ ... ]".
- We do not write the noiseword "function" in front of shell
functions.
+ - We prefer a space between the function name and the parentheses. The
+ opening "{" should also be on the same line.
+ E.g.: my_function () {
+
- As to use of grep, stick to a subset of BRE (namely, no \{m,n\},
[::], [==], nor [..]) for portability.
quick-install-html: require-htmlrepo
'$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir)
+print-man1:
+ @for i in $(MAN1_TXT); do echo $$i; done
+
.PHONY: FORCE
* The Makefile rule to create assembly output (primarily for
debugging purposes) did not create it next to the source.
+ * The code to avoid mistaken attempt to add the object directory
+ itself as its own alternate could read beyond end of a string while
+ comparison.
+
* On some architectures, "block-sha1" did not compile correctly
when compilers inferred alignment guarantees from our source we
did not intend to make.
* "git mergetool" did not support --tool-help option to give the list
of supported backends, like "git difftool" does.
+ * "git grep" stopped spawning an external "grep" long time ago, but a
+ duplicated test to check internal and external "grep" was left
+ behind.
+
Also contains minor typofixes and documentation updates.
--- /dev/null
+Git v1.7.11.6 Release Notes
+===========================
+
+Fixes since v1.7.11.5
+---------------------
+
+This is primarily documentation and low-impact code clarification.
+
+ - "ciabot" script (in contrib/) has been updated with extensive
+ documentation.
+
+ - The "--rebase" option to "git pull" can be abbreviated to "-r",
+ but we didn't document it.
+
+ - It was generally understood that "--long-option"s to many of our
+ subcommands can be abbreviated to the unique prefix, but it was not
+ easy to find it described for new readers of the documentation set.
+
+ - The "--topo-order", "--date-order" (and the lack of either means
+ the default order) options to "rev-list" and "log" family of
+ commands were poorly described in the documentation.
+
+ - Older parts of the documentation described as if having a regular
+ file in .git/refs/ hierarchy were the only way to have branches and
+ tags, which is not true for quite some time.
+
+ - A utility shell function test_seq has been added as a replacement
+ for the 'seq' utility found on some platforms.
+
+ - Fallback 'getpass' implementation made unportable use of stdio API.
+
+ - "git commit --amend" let the user edit the log message and then
+ died when the human-readable committer name was given
+ insufficiently by getpwent(3).
* "git svn" got a large-looking code reorganization at the last
minute before the code freeze.
+
Also contains minor documentation updates and code clean-ups.
releases are contained in this release (see release notes to them for
details).
- * "git grep" stopped spawning an external "grep" long time ago, but a
- duplicated test to check internal and external "grep" was left
- behind.
- (merge 4ca9453 rj/maint-grep-remove-redundant-test later to maint).
-
- * The code to avoid mistaken attempt to add the object directory
- itself as its own alternate could read beyond end of a string while
- comparison.
- (merge cb2912c hv/link-alt-odb-entry later to maint).
-
* "git submodule add" was confused when the superproject did not have
its repository in its usual place in the working tree and GIT_DIR
and GIT_WORK_TREE was used to access it.
--- /dev/null
+Git v1.8.0 Release Notes
+========================
+
+Backward compatibility notes
+----------------------------
+
+In the next major release, we will change the behaviour of the "git
+push" command. When "git push [$there]" does not say what to push, we
+have used the traditional "matching" semantics (all your branches were
+sent to the remote as long as there already are branches of the same
+name over there). We will use the "simple" semantics, that pushes the
+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, and
+"git push" will warn about the upcoming change until you set this
+variable.
+
+
+Updates since v1.7.12
+---------------------
+
+UI, Workflows & Features
+
+ * A credential helper for Win32 to allow access to the keychain of
+ the logged-in user has been added.
+
+ * "git cherry-pick" learned the "--allow-empty-message" option to
+ allow it to replay a commit without any log message.
+
+ * "git daemon" learned the "--access-hook" option to allow an
+ external command to decline service based on the client address,
+ repository path, etc.
+
+ * "git difftool --dir-diff" learned to use symbolic links to prepare
+ temporary copy of the working tree when available.
+
+ * "git grep" learned to use a non-standard pattern type by default if
+ a configuration variable tells it to.
+
+Foreign Interface
+
+ * "git svn" has been updated to work with SVN 1.7.
+
+Performance, Internal Implementation, etc. (please report possible regressions)
+
+ * The "check-docs" build target has been updated and greatly
+ simplified.
+
+ * The documentation in the TeXinfo format was using indented output
+ for materials meant to be examples that are better typeset in
+ monospace.
+
+ * Compatibility wrapper to learn the maximum number of file
+ descriptors we can open around sysconf(_SC_OPEN_MAX) and
+ getrlimit(RLIMIT_NO_FILE) has been introduced for portability.
+
+ * Compatibility wrapper around some mkdir(2) implementations that
+ reject parameter with trailing slash has been introduced.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.12
+-------------------
+
+Unless otherwise noted, all the fixes since v1.7.12 in the
+maintenance track are contained in this release (see release notes
+to them for details).
+
+ * The exit status code from "git config" was way overspecified while
+ being incorrect. The implementation has been updated to give the
+ documented status for a case that was documented, and introduce a
+ new code for "all other errors".
+ (merge 9409c7a jc/maint-config-exit-status later to maint).
+
+ * "git foo" errored out with "Not a directory" when the user had a
+ non-directory on $PATH, and worse yet it masked an alias "foo" from
+ running. (merge a785508 jc/maint-sane-execvp-notdir later to
+ maint).
+
+ * The interactive prompt "git send-email" gives was error prone. It
+ asked "What e-mail address do you want to use?" with the address it
+ guessed (correctly) the user would want to use in its prompt,
+ tempting the user to say "y". But the response was taken as "No,
+ please use 'y' as the e-mail address instead", which is most
+ certainly not what the user meant.
+ (merge 51bbccf jc/send-email-reconfirm later to maint).
+
+ * "git stash apply/pop" did not trigger "rerere" upon conflicts
+ unlike other mergy operations.
+ (merge 743bf6d ph/stash-rerere later to maint).
+
+ * "git submodule update -f" did not update paths in the working tree
+ that has local changes.
+ (merge 01d4721 sz/submodule-force-update later to maint).
+
+ * We used curl_easy_strerror() without checking version of cURL,
+ breaking the build for versions before curl 7.12.0.
+ (merge 4246b0b js/no-curl-easy-strerror-on-old-curl later to maint).
+
+ * Code to work around MacOS X UTF-8 gotcha has been cleaned up.
+ (merge 9a27f96 rr/precompose-utf8-cleanup later to maint).
+
+ * Documentation for the configuration file format had a confusing
+ example.
+ (merge d1e1fe7 mh/maint-config-doc-proxy-command later to maint).
+
+ * "git submodule <cmd> path" did not error out when the path to the
+ submodule was misspelt.
+ (merge be9d0a3 hv/submodule-path-unmatch later to maint).
+
+ * Some capabilities were asked by fetch-pack even when upload-pack
+ did not advertise that they are available. fetch-pack has been
+ fixed not to do so.
+
+ * The reflog entries left by "git rebase" and "git rebase -i" were
+ inconsistent (the interactive one gave an abbreviated object name).
+ (merge 1af221e mg/rebase-i-onto-reflog-in-full later to maint).
+
+ * When the user exports a non-default IFS without HT, scripts that
+ rely on being able to parse "ls-files -s | while read a b c..."
+ start to fail. Protect them from such a misconfiguration.
+ (merge 785063e jc/maint-protect-sh-from-ifs later to maint).
+
+ * "git prune" without "-v" used to warn about leftover temporary
+ files (which is an indication of an earlier aborted operation).
+ (merge 90b29cb bc/prune-info later to maint).
+
+ * When "git push" triggered the automatic gc on the receiving end, a
+ message from "git prune" that said it was removing cruft leaked to
+ the standard output, breaking the communication protocol.
+ (merge 4b7f2fa bc/receive-pack-stdout-protection later to maint).
+
+ * "git diff" had a confusion between taking data from a path in the
+ working tree and taking data from an object that happens to have
+ name 0{40} recorded in a tree.
+ (merge c479d14 jk/maint-null-in-trees later to maint).
+
+ * The output from "git diff -B" for a file that ends with an
+ incomplete line did not put "\ No newline..." on a line of its own.
+
+ * "git send-email" did not unquote encoded words that appear on the
+ header correctly, and lost "_" from strings.
+ (merge b622d4d tr/maint-send-email-2047 later to maint).
+
+ * When the user gives an argument that can be taken as both a
+ revision name and a pathname without disambiguating with "--", we
+ used to give a help message "Use '--' to separate". The message
+ has been clarified to show where that '--' goes on the command
+ line.
+ (merge 4d4b573 mm/die-with-dashdash-help later to maint).
+
+ * "gitweb" when used with PATH_INFO failed to notice directories with
+ SP (and other characters that need URL-style quoting) in them.
+ (merge cacfc09 js/gitweb-path-info-unquote later to maint).
# v1.72 breaks with this because it replaces dots not in roff requests.
[listingblock]
<example><title>{title}</title>
-<literallayout>
+<literallayout class="monospaced">
ifdef::doctype-manpage[]
.ft C
endif::doctype-manpage[]
# The following two small workarounds insert a simple paragraph after screen
[listingblock]
<example><title>{title}</title>
-<literallayout>
+<literallayout class="monospaced">
|
</literallayout><simpara></simpara>
{title#}</example>
grep.lineNumber::
If set to true, enable '-n' option by default.
+grep.patternType::
+ Set the default matching behavior. Using a value of 'basic', 'extended',
+ 'fixed', or 'perl' will enable the '--basic-regexp', '--extended-regexp',
+ '--fixed-strings', or '--perl-regexp' option accordingly, while the
+ value 'default' will return to the default matching behavior.
+
grep.extendedRegexp::
- If set to true, enable '--extended-regexp' option by default.
+ If set to true, enable '--extended-regexp' option by default. This
+ option is ignored when the 'grep.patternType' option is set to a value
+ other than 'default'.
gpg.program::
Use this custom program instead of "gpg" found on $PATH when
previous commit are dropped. To force the inclusion of those commits
use `--keep-redundant-commits`.
+--allow-empty-message::
+ By default, cherry-picking a commit with an empty message will fail.
+ This option overrides that behaviour, allowing commits with empty
+ messages to be cherry picked.
+
--keep-redundant-commits::
If a commit being cherry picked duplicates a commit already in the
current history, it will become empty. By default these
'--file <filename>' can be used to tell the command to write to
that location (you can say '--local' but that is the default).
-This command will fail (with exit code ret) if:
+This command will fail with non-zero status upon error. Some exit
+codes are:
. The config file is invalid (ret=3),
. can not write to the config file (ret=4),
. no section or name was provided (ret=2),
. the section or key is invalid (ret=1),
. you try to unset an option which does not exist (ret=5),
-. you try to unset/set an option for which multiple lines match (ret=5),
-. you try to use an invalid regexp (ret=6), or
-. you use '--global' option without $HOME being properly set (ret=128).
+. you try to unset/set an option for which multiple lines match (ret=5), or
+. you try to use an invalid regexp (ret=6).
On success, the command returns the exit code 0.
; Proxy settings
[core]
- gitproxy="proxy-command" for kernel.org
+ gitproxy=proxy-command for kernel.org
gitproxy=default-proxy ; for all the rest
you can set the filemode to true with
To add a new proxy, without altering any of the existing ones, use
------------
-% git config core.gitproxy '"proxy-command" for example.com'
+% git config --add core.gitproxy '"proxy-command" for example.com'
------------
An example to use customized color from the configuration in your
NAME
----
-git-credential-cache--daemon - temporarily store user credentials in memory
+git-credential-cache--daemon - Temporarily store user credentials in memory
SYNOPSIS
--------
NAME
----
-git-credential-cache - helper to temporarily store passwords in memory
+git-credential-cache - Helper to temporarily store passwords in memory
SYNOPSIS
--------
NAME
----
-git-credential-store - helper to store credentials on disk
+git-credential-store - Helper to store credentials on disk
SYNOPSIS
--------
[--reuseaddr] [--detach] [--pid-file=<file>]
[--enable=<service>] [--disable=<service>]
[--allow-override=<service>] [--forbid-override=<service>]
+ [--access-hook=<path>]
[--inetd | [--listen=<host_or_ipaddr>] [--port=<n>] [--user=<user> [--group=<group>]]
[<directory>...]
errors are not enabled, all errors report "access denied" to the
client. The default is --no-informative-errors.
+--access-hook=<path>::
+ Every time a client connects, first run an external command
+ specified by the <path> with service name (e.g. "upload-pack"),
+ path to the repository, hostname (%H), canonical hostname
+ (%CH), ip address (%IP), and tcp port (%P) as its command line
+ arguments. The external command can decide to decline the
+ service by exiting with a non-zero status (or to allow it by
+ exiting with a zero status). It can also look at the $REMOTE_ADDR
+ and $REMOTE_PORT environment variables to learn about the
+ requestor when making this decision.
++
+The external command can optionally write a single line to its
+standard output to be sent to the requestor as an error message when
+it declines the service.
+
<directory>::
A directory to add to the whitelist of allowed directories. Unless
--strict-paths is specified this will also include subdirectories
--all::
Instead of using only the annotated tags, use any ref
- found in `.git/refs/`. This option enables matching
+ found in `refs/` namespace. This option enables matching
any known branch, remote-tracking branch, or lightweight tag.
--tags::
Instead of using only the annotated tags, use any tag
- found in `.git/refs/tags`. This option enables matching
+ found in `refs/tags` namespace. This option enables matching
a lightweight (non-annotated) tag.
--contains::
--tool-help::
Print a list of diff tools that may be used with `--tool`.
+--symlinks::
+--no-symlinks::
+ 'git difftool''s default behavior is create symlinks to the
+ working tree when run in `--dir-diff` mode.
++
+ Specifying `--no-symlinks` instructs 'git difftool' to create
+ copies instead. `--no-symlinks` is the default on Windows.
+
-x <command>::
--extcmd=<command>::
Specify a custom command for viewing diffs.
useful in the future for compensating for some git bugs or such,
therefore such a usage is permitted.
-*NOTE*: This command honors `.git/info/grafts` and `.git/refs/replace/`.
+*NOTE*: This command honors `.git/info/grafts` file and refs in
+the `refs/replace/` namespace.
If you have any grafts or replacement refs defined, running this command
will make them permanent.
An object to treat as the head of an unreachability trace.
+
If no objects are given, 'git fsck' defaults to using the
-index file, all SHA1 references in .git/refs/*, and all reflogs (unless
---no-reflogs is given) as heads.
+index file, all SHA1 references in `refs` namespace, and all reflogs
+(unless --no-reflogs is given) as heads.
--unreachable::
Print out objects that exist but that aren't reachable from any
grep.lineNumber::
If set to true, enable '-n' option by default.
+grep.patternType::
+ Set the default matching behavior. Using a value of 'basic', 'extended',
+ 'fixed', or 'perl' will enable the '--basic-regexp', '--extended-regexp',
+ '--fixed-strings', or '--perl-regexp' option accordingly, while the
+ value 'default' will return to the default matching behavior.
+
grep.extendedRegexp::
- If set to true, enable '--extended-regexp' option by default.
+ If set to true, enable '--extended-regexp' option by default. This
+ option is ignored when the 'grep.patternType' option is set to a value
+ other than 'default'.
OPTIONS
------------
After making sure you know which the object is the tag you are looking
-for, you can reconnect it to your regular .git/refs hierarchy.
+for, you can reconnect it to your regular `refs` hierarchy by using
+the `update-ref` command.
------------
$ git cat-file -t 1ef2b196
Otherwise, 'git mergetool' will prompt the user to indicate the
success of the resolution after the custom tool has exited.
+--tool-help::
+ Print a list of merge tools that may be used with `--tool`.
+
-y::
--no-prompt::
Don't prompt before each invocation of the merge resolution
-----------
Traditionally, tips of branches and tags (collectively known as
-'refs') were stored one file per ref under `$GIT_DIR/refs`
+'refs') were stored one file per ref in a (sub)directory
+under `$GIT_DIR/refs`
directory. While many branch tips tend to be updated often,
most tags and some branch tips are never updated. When a
repository has hundreds or thousands of tags, this
performance.
This command is used to solve the storage and performance
-problem by stashing the refs in a single file,
+problem by storing the refs in a single file,
`$GIT_DIR/packed-refs`. When a ref is missing from the
-traditional `$GIT_DIR/refs` hierarchy, it is looked up in this
+traditional `$GIT_DIR/refs` directory hierarchy, it is looked
+up in this
file and used if found.
Subsequent updates to branches always create new files under
-`$GIT_DIR/refs` hierarchy.
+`$GIT_DIR/refs` directory hierarchy.
A recommended practice to deal with a repository with too many
refs is to pack its refs with `--all --prune` once, and
The command usually removes loose refs under `$GIT_DIR/refs`
hierarchy after packing them. This option tells it not to.
+
+BUGS
+----
+
+Older documentation written before the packed-refs mechanism was
+introduced may still say things like ".git/refs/heads/<branch> file
+exists" when it means "branch <branch> exists".
+
+
GIT
---
Part of the linkgit:git[1] suite
:git-pull: 1
+-r::
--rebase::
Rebase the current branch on top of the upstream branch after
fetching. If there is a remote-tracking branch corresponding to
DESCRIPTION
-----------
-Adds a 'replace' reference in `.git/refs/replace/`
+Adds a 'replace' reference in `refs/replace/` namespace.
The name of the 'replace' reference is the SHA1 of the object that is
replaced. The content of the 'replace' reference is the SHA1 of the
replacement object.
-Unless `-f` is given, the 'replace' reference must not yet exist in
-`.git/refs/replace/` directory.
+Unless `-f` is given, the 'replace' reference must not yet exist.
Replacement references will be used by default by all git commands
except those doing reachability traversal (prune, pack transfer and
+
If `--recursive` is specified, this command will recurse into the
registered submodules, and update any nested submodules within.
++
+If `--force` is specified, the submodule will be checked out (using
+`git checkout --force` if appropriate), even if the commit specified in the
+index of the containing repository already matches the commit checked out in
+the submodule.
summary::
Show commit summary between the given commit (defaults to HEAD) and
This option is only valid for add and update commands.
When running add, allow adding an otherwise ignored submodule path.
When running update, throw away local changes in submodules when
- switching to a different commit.
+ switching to a different commit; and always run a checkout operation
+ in the submodule, even if the commit listed in the index of the
+ containing repository matches the commit checked out in the submodule.
--cached::
This option is only valid for status and summary commands. These
DESCRIPTION
-----------
-Add a tag reference in `.git/refs/tags/`, unless `-d/-l/-v` is given
+Add a tag reference in `refs/tags/`, unless `-d/-l/-v` is given
to delete, list or verify tags.
-Unless `-f` is given, the tag to be created must not yet exist in the
-`.git/refs/tags/` directory.
+Unless `-f` is given, the named tag must not yet exist.
If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
creates a 'tag' object, and requires a tag message. Unless
and full access to internals.
See linkgit:gittutorial[7] to get started, then see
-link:everyday.html[Everyday Git] for a useful minimum set of commands, and
-"man git-commandname" for documentation of each command. CVS users may
-also want to read linkgit:gitcvs-migration[7]. See
-the link:user-manual.html[Git User's Manual] for a more in-depth
-introduction.
+link:everyday.html[Everyday Git] for a useful minimum set of
+commands. The link:user-manual.html[Git User's Manual] has a more
+in-depth introduction.
-The '<command>' is either a name of a Git command (see below) or an alias
-as defined in the configuration file (see linkgit:git-config[1]).
+After you mastered the basic concepts, you can come back to this
+page to learn what commands git offers. You can learn more about
+individual git commands with "git help command". linkgit:gitcli[7]
+manual page gives you an overview of the command line command syntax.
-Formatted and hyperlinked version of the latest git
-documentation can be viewed at
-`http://www.kernel.org/pub/software/scm/git/docs/`.
+Formatted and hyperlinked version of the latest git documentation
+can be viewed at `http://git-htmldocs.googlecode.com/git/git.html`.
ifdef::stalenotes[]
[NOTE]
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.7.11.4/git.html[documentation for release 1.7.11.4]
+* link:v1.7.12/git.html[documentation for release 1.7.12]
* release notes for
+ link:RelNotes/1.7.12.txt[1.7.12].
+
+* link:v1.7.11.5/git.html[documentation for release 1.7.11.5]
+
+* release notes for
+ link:RelNotes/1.7.11.5.txt[1.7.11.5],
link:RelNotes/1.7.11.4.txt[1.7.11.4],
link:RelNotes/1.7.11.3.txt[1.7.11.3],
link:RelNotes/1.7.11.2.txt[1.7.11.2],
linkgit:git-replace[1] for more information.
-FURTHER DOCUMENTATION
----------------------
-
-See the references above to get started using git. The following is
-probably more detail than necessary for a first-time user.
-
-The link:user-manual.html#git-concepts[git concepts chapter of the
-user-manual] and linkgit:gitcore-tutorial[7] both provide
-introductions to the underlying git architecture.
-
-See linkgit:gitworkflows[7] for an overview of recommended workflows.
-
-See also the link:howto-index.html[howto] documents for some useful
-examples.
-
-The internals are documented in the
-link:technical/api-index.html[GIT API documentation].
-
GIT COMMANDS
------------
for a given pathname. These stages are used to hold the various
unmerged version of a file when a merge is in progress.
+FURTHER DOCUMENTATION
+---------------------
+
+See the references in the "description" section to get started
+using git. The following is probably more detail than necessary
+for a first-time user.
+
+The link:user-manual.html#git-concepts[git concepts chapter of the
+user-manual] and linkgit:gitcore-tutorial[7] both provide
+introductions to the underlying git architecture.
+
+See linkgit:gitworkflows[7] for an overview of recommended workflows.
+
+See also the link:howto-index.html[howto] documents for some useful
+examples.
+
+The internals are documented in the
+link:technical/api-index.html[GIT API documentation].
+
+Users migrating from CVS may also want to
+read linkgit:gitcvs-migration[7].
+
+
Authors
-------
Git was started by Linus Torvalds, and is currently maintained by Junio
`git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work
if you happen to have a file called `HEAD` in the work tree.
+ * many commands allow a long option "--option" to be abbreviated
+ only to their unique prefix (e.g. if there is no other option
+ whose name begins with "opt", you may be able to spell "--opt" to
+ invoke the "--option" flag), but you should fully spell them out
+ when writing your scripts; later versions of Git may introduce a
+ new option whose name shares the same prefix, e.g. "--optimize",
+ to make a short prefix that used to be unique no longer unique.
+
ENHANCED OPTION PARSER
----------------------
By default, the commits are shown in reverse chronological order.
---topo-order::
+--date-order::
+ Show no parents before all of its children are shown, but
+ otherwise show commits in the commit timestamp order.
- This option makes them appear in topological order (i.e.
- descendant commits are shown before their parents).
+--topo-order::
+ Show no parents before all of its children are shown, and
+ avoid showing commits on multiple lines of history
+ intermixed.
++
+For example, in a commit history like this:
++
+----------------------------------------------------------------
---date-order::
+ ---1----2----4----7
+ \ \
+ 3----5----6----8---
- This option is similar to '--topo-order' in the sense that no
- parent comes before all of its children, but otherwise things
- are still ordered in the commit timestamp order.
+----------------------------------------------------------------
++
+where the numbers denote the order of commit timestamps, `git
+rev-list` and friends with `--date-order` show the commits in the
+timestamp order: 8 7 6 5 4 3 2 1.
++
+With `--topo-order`, they would show 8 6 5 3 7 4 2 1 (or 8 7 4 2 6 5
+3 1); some older commits are shown before newer ones in order to
+avoid showing the commits from two parallel development track mixed
+together.
--reverse::
# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
[listingblock]
<example><title>{title}</title>
-<literallayout>
+<literallayout class="monospaced">
|
</literallayout>
{title#}</example>
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.12-rc2
+DEF_VER=v1.7.12.GIT
LF='
'
### Check documentation
#
+ALL_COMMANDS = $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS)
+ALL_COMMANDS += git
+ALL_COMMANDS += gitk
+ALL_COMMANDS += gitweb
+ALL_COMMANDS += git-gui git-citool
check-docs::
- @(for v in $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk; \
+ @(for v in $(ALL_COMMANDS); \
do \
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
sed -e '/^#/d' \
-e 's/[ ].*//' \
-e 's/^/listed /' command-list.txt; \
- ls -1 Documentation/git*txt | \
+ $(MAKE) -C Documentation print-man1 | \
+ grep '\.txt$$' | \
sed -e 's|Documentation/|documented |' \
-e 's/\.txt//'; \
) | while read how cmd; \
do \
- case "$$how,$$cmd" in \
- *,git-citool | \
- *,git-gui | \
- *,git-help | \
- documented,gitattributes | \
- documented,gitignore | \
- documented,gitmodules | \
- documented,gitcli | \
- documented,git-tools | \
- documented,gitcore-tutorial | \
- documented,gitcvs-migration | \
- documented,gitdiffcore | \
- documented,gitglossary | \
- documented,githooks | \
- documented,gitrepository-layout | \
- documented,gitrevisions | \
- documented,gittutorial | \
- documented,gittutorial-2 | \
- documented,git-bisect-lk2009 | \
- documented,git-remote-helpers | \
- documented,gitworkflows | \
- sentinel,not,matching,is,ok ) continue ;; \
- esac; \
- case " $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk " in \
+ case " $(ALL_COMMANDS) " in \
*" $$cmd "*) ;; \
*) echo "removed but $$how: $$cmd" ;; \
esac; \
-Documentation/RelNotes/1.7.12.txt
\ No newline at end of file
+Documentation/RelNotes/1.8.0.txt
\ No newline at end of file
struct diff_options;
extern void setup_diff_pager(struct diff_options *);
-extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size);
+extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
int rejected;
unsigned ws_rule;
- unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
unsigned int is_toplevel_relative:1;
int textconv_object(const char *path,
unsigned mode,
const unsigned char *sha1,
+ int sha1_valid,
char **buf,
unsigned long *buf_size)
{
struct userdiff_driver *textconv;
df = alloc_filespec(path);
- fill_filespec(df, sha1, mode);
+ fill_filespec(df, sha1, sha1_valid, mode);
textconv = get_textconv(df);
if (!textconv) {
free_filespec(df);
num_read_blob++;
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size))
+ textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size))
;
else
file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
paths[1] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
diff_opts.single_follow = origin->path;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
* we do not want to use diffcore_rename() actually to
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
+ textconv_object(read_from, mode, null_sha1, 0, &buf_ptr, &buf_len))
strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
die("no such path %s in %s", path, final_commit_name);
if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
- textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf,
+ textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf,
&sb.final_buf_size))
;
else
die("git cat-file --textconv %s: <object> must be <sha1:path>",
obj_name);
- if (!textconv_object(obj_context.path, obj_context.mode, sha1, &buf, &size))
+ if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
die("git cat-file --textconv: unable to run textconv on %s",
obj_name);
break;
init_revisions(&rev, NULL);
rev.diffopt.flags = opts->flags;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
- if (diff_setup_done(&rev.diffopt) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&rev.diffopt);
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
}
static int get_value(const char *key_, const char *regex_)
{
- int ret = -1;
+ int ret = CONFIG_GENERIC_ERROR;
char *global = NULL, *xdg = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
struct config_include_data inc = CONFIG_INCLUDE_INIT;
if (regcomp(key_regexp, key, REG_EXTENDED)) {
fprintf(stderr, "Invalid key pattern: %s\n", key_);
free(key);
+ ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
} else {
- if (git_config_parse_key(key_, &key, NULL))
+ if (git_config_parse_key(key_, &key, NULL)) {
+ ret = CONFIG_INVALID_KEY;
goto free_strings;
+ }
}
if (regex_) {
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
}
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
+ int old_sha1_valid,
+ int new_sha1_valid,
const char *old_name,
const char *new_name)
{
one = alloc_filespec(old_name);
two = alloc_filespec(new_name);
- fill_filespec(one, old_sha1, old_mode);
- fill_filespec(two, new_sha1, new_mode);
+ fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
+ fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
diff_queue(&diff_queued_diff, one, two);
}
stuff_change(&revs->diffopt,
blob[0].mode, canon_mode(st.st_mode),
blob[0].sha1, null_sha1,
+ 1, 0,
path, path);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
stuff_change(&revs->diffopt,
blob[0].mode, blob[1].mode,
blob[0].sha1, blob[1].sha1,
+ 1, 1,
blob[0].name, blob[1].name);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
argc = setup_revisions(argc, argv, &rev, NULL);
if (!rev.diffopt.output_format) {
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
- if (diff_setup_done(&rev.diffopt) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&rev.diffopt);
}
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
#include "remote.h"
#include "run-command.h"
#include "transport.h"
+#include "version.h"
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
static int no_done;
static int fetch_fsck_objects = -1;
static int transfer_fsck_objects = -1;
+static int agent_supported;
static struct fetch_pack_args args = {
/* .uploadpack = */ "git-upload-pack",
};
if (args.no_progress) strbuf_addstr(&c, " no-progress");
if (args.include_tag) strbuf_addstr(&c, " include-tag");
if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta");
+ if (agent_supported) strbuf_addf(&c, " agent=%s",
+ git_user_agent_sanitized());
packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
strbuf_release(&c);
} else
{
struct ref *ref = copy_ref_list(orig_ref);
unsigned char sha1[20];
+ const char *agent_feature;
+ int agent_len;
sort_ref_list(&ref, ref_compare_name);
fprintf(stderr, "Server supports side-band\n");
use_sideband = 1;
}
+ if (!server_supports("thin-pack"))
+ args.use_thin_pack = 0;
+ if (!server_supports("no-progress"))
+ args.no_progress = 0;
+ if (!server_supports("include-tag"))
+ args.include_tag = 0;
if (server_supports("ofs-delta")) {
if (args.verbose)
fprintf(stderr, "Server supports ofs-delta\n");
} else
prefer_ofs_delta = 0;
+
+ if ((agent_feature = server_feature_value("agent", &agent_len))) {
+ agent_supported = 1;
+ if (args.verbose && agent_len)
+ fprintf(stderr, "Server version is %.*s\n",
+ agent_len, agent_feature);
+ }
+
if (everything_local(&ref, nr_match, match)) {
packet_flush(fd[1]);
goto all_done;
}
#endif
+static int parse_pattern_type_arg(const char *opt, const char *arg)
+{
+ if (!strcmp(arg, "default"))
+ return GREP_PATTERN_TYPE_UNSPECIFIED;
+ else if (!strcmp(arg, "basic"))
+ return GREP_PATTERN_TYPE_BRE;
+ else if (!strcmp(arg, "extended"))
+ return GREP_PATTERN_TYPE_ERE;
+ else if (!strcmp(arg, "fixed"))
+ return GREP_PATTERN_TYPE_FIXED;
+ else if (!strcmp(arg, "perl"))
+ return GREP_PATTERN_TYPE_PCRE;
+ die("bad %s argument: %s", opt, arg);
+}
+
+static void grep_pattern_type_options(const int pattern_type, struct grep_opt *opt)
+{
+ switch (pattern_type) {
+ case GREP_PATTERN_TYPE_UNSPECIFIED:
+ /* fall through */
+
+ case GREP_PATTERN_TYPE_BRE:
+ opt->fixed = 0;
+ opt->pcre = 0;
+ opt->regflags &= ~REG_EXTENDED;
+ break;
+
+ case GREP_PATTERN_TYPE_ERE:
+ opt->fixed = 0;
+ opt->pcre = 0;
+ opt->regflags |= REG_EXTENDED;
+ break;
+
+ case GREP_PATTERN_TYPE_FIXED:
+ opt->fixed = 1;
+ opt->pcre = 0;
+ opt->regflags &= ~REG_EXTENDED;
+ break;
+
+ case GREP_PATTERN_TYPE_PCRE:
+ opt->fixed = 0;
+ opt->pcre = 1;
+ opt->regflags &= ~REG_EXTENDED;
+ break;
+ }
+}
+
static int grep_config(const char *var, const char *value, void *cb)
{
struct grep_opt *opt = cb;
if (!strcmp(var, "grep.extendedregexp")) {
if (git_config_bool(var, value))
- opt->regflags |= REG_EXTENDED;
+ opt->extended_regexp_option = 1;
else
- opt->regflags &= ~REG_EXTENDED;
+ opt->extended_regexp_option = 0;
return 0;
}
+ if (!strcmp(var, "grep.patterntype")) {
+ opt->pattern_type_option = parse_pattern_type_arg(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "grep.linenumber")) {
opt->linenum = git_config_bool(var, value);
return 0;
int i;
int dummy;
int use_index = 1;
- enum {
- pattern_type_unspecified = 0,
- pattern_type_bre,
- pattern_type_ere,
- pattern_type_fixed,
- pattern_type_pcre,
- };
- int pattern_type = pattern_type_unspecified;
+ int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
struct option options[] = {
OPT_BOOLEAN(0, "cached", &cached,
"descend at most <depth> levels", PARSE_OPT_NONEG,
NULL, 1 },
OPT_GROUP(""),
- OPT_SET_INT('E', "extended-regexp", &pattern_type,
+ OPT_SET_INT('E', "extended-regexp", &pattern_type_arg,
"use extended POSIX regular expressions",
- pattern_type_ere),
- OPT_SET_INT('G', "basic-regexp", &pattern_type,
+ GREP_PATTERN_TYPE_ERE),
+ OPT_SET_INT('G', "basic-regexp", &pattern_type_arg,
"use basic POSIX regular expressions (default)",
- pattern_type_bre),
- OPT_SET_INT('F', "fixed-strings", &pattern_type,
+ GREP_PATTERN_TYPE_BRE),
+ OPT_SET_INT('F', "fixed-strings", &pattern_type_arg,
"interpret patterns as fixed strings",
- pattern_type_fixed),
- OPT_SET_INT('P', "perl-regexp", &pattern_type,
+ GREP_PATTERN_TYPE_FIXED),
+ OPT_SET_INT('P', "perl-regexp", &pattern_type_arg,
"use Perl-compatible regular expressions",
- pattern_type_pcre),
+ GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"),
OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
opt.header_tail = &opt.header_list;
opt.regflags = REG_NEWLINE;
opt.max_depth = -1;
+ opt.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
+ opt.extended_regexp_option = 0;
strcpy(opt.color_context, "");
strcpy(opt.color_filename, "");
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_STOP_AT_NON_OPTION |
PARSE_OPT_NO_INTERNAL_HELP);
- switch (pattern_type) {
- case pattern_type_fixed:
- opt.fixed = 1;
- opt.pcre = 0;
- break;
- case pattern_type_bre:
- opt.fixed = 0;
- opt.pcre = 0;
- opt.regflags &= ~REG_EXTENDED;
- break;
- case pattern_type_ere:
- opt.fixed = 0;
- opt.pcre = 0;
- opt.regflags |= REG_EXTENDED;
- break;
- case pattern_type_pcre:
- opt.fixed = 0;
- opt.pcre = 1;
- break;
- default:
- break; /* nothing */
- }
+
+ if (pattern_type_arg != GREP_PATTERN_TYPE_UNSPECIFIED)
+ grep_pattern_type_options(pattern_type_arg, &opt);
+ else if (opt.pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
+ grep_pattern_type_options(opt.pattern_type_option, &opt);
+ else if (opt.extended_regexp_option)
+ grep_pattern_type_options(GREP_PATTERN_TYPE_ERE, &opt);
if (use_index && !startup_info->have_repository)
/* die the same way as if we did it at the beginning */
return 0;
}
-static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
+static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
{
struct rev_info check_rev;
struct commit *commit;
init_patch_ids(ids);
/* given a range a..b get all patch ids for b..a */
- init_revisions(&check_rev, prefix);
+ init_revisions(&check_rev, rev->prefix);
+ check_rev.max_parents = 1;
o1->flags ^= UNINTERESTING;
o2->flags ^= UNINTERESTING;
add_pending_object(&check_rev, o1, "o1");
die(_("revision walk setup failed"));
while ((commit = get_revision(&check_rev)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
add_commit_patch_id(commit, ids);
}
if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
return 0;
}
- get_patch_ids(&rev, &ids, prefix);
+ get_patch_ids(&rev, &ids);
}
if (!use_stdout)
}
init_revisions(&revs, prefix);
- revs.diff = 1;
- revs.combine_merges = 0;
- revs.ignore_merges = 1;
- DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+ revs.max_parents = 1;
if (add_pending_commit(head, &revs, 0))
die(_("Unknown commit %s"), head);
return 0;
}
- get_patch_ids(&revs, &ids, prefix);
+ get_patch_ids(&revs, &ids);
if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
die(_("Unknown commit %s"), limit);
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
while ((commit = get_revision(&revs)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
commit_list_insert(commit, &list);
}
opts.output_format |=
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
opts.detect_rename = DIFF_DETECT_RENAME;
- if (diff_setup_done(&opts) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&opts);
diff_tree_sha1(head, new_head, "", &opts);
diffcore_std(&opts);
diff_flush(&opts);
return error("Could not stat '%s'", fullpath);
if (st.st_mtime > expire)
return 0;
- printf("Removing stale temporary file %s\n", fullpath);
+ if (show_only || verbose)
+ printf("Removing stale temporary file %s\n", fullpath);
if (!show_only)
unlink_or_warn(fullpath);
return 0;
add_refspec(refspec.buf);
}
+static char warn_unspecified_push_default_msg[] =
+N_("push.default is unset; its implicit value is changing in\n"
+ "Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
+ "and maintain the current behavior after the default changes, use:\n"
+ "\n"
+ " git config --global push.default matching\n"
+ "\n"
+ "To squelch this message and adopt the new behavior now, use:\n"
+ "\n"
+ " git config --global push.default simple\n"
+ "\n"
+ "See 'git help config' and search for 'push.default' for further information.\n"
+ "(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n"
+ "'current' instead of 'simple' if you sometimes use older versions of Git)");
+
+static void warn_unspecified_push_default_configuration(void)
+{
+ static int warn_once;
+
+ if (warn_once++)
+ return;
+ warning("%s\n", _(warn_unspecified_push_default_msg));
+}
+
static void setup_default_push_refspecs(struct remote *remote)
{
switch (push_default) {
default:
case PUSH_DEFAULT_UNSPECIFIED:
default_matching_used = 1;
+ warn_unspecified_push_default_configuration();
/* fallthru */
case PUSH_DEFAULT_MATCHING:
add_refspec(":");
static const char message_advice_use_upstream[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
"counterpart. If you did not intend to push that branch, you may want to\n"
- "specify branches to push or set the 'push.default' configuration\n"
- "variable to 'current' or 'upstream' to push only the current branch.");
+ "specify branches to push or set the 'push.default' configuration variable\n"
+ "to 'simple', 'current' or 'upstream' to push only the current branch.");
static const char message_advice_checkout_pull_push[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
#include "string-list.h"
#include "sha1-array.h"
#include "connected.h"
+#include "version.h"
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
if (sent_capabilities)
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
else
- packet_write(1, "%s %s%c%s%s\n",
+ packet_write(1, "%s %s%c%s%s agent=%s\n",
sha1_to_hex(sha1), path, 0,
" report-status delete-refs side-band-64k quiet",
- prefer_ofs_delta ? " ofs-delta" : "");
+ prefer_ofs_delta ? " ofs-delta" : "",
+ git_user_agent_sanitized());
sent_capabilities = 1;
}
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
};
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ int opt = RUN_GIT_CMD | RUN_COMMAND_STDOUT_TO_STDERR;
+ run_command_v_opt(argv_gc_auto, opt);
}
if (auto_update_server_info)
update_server_info(0);
OPT_END(),
OPT_END(),
OPT_END(),
+ OPT_END(),
};
if (opts->action == REPLAY_PICK) {
OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"),
+ OPT_BOOLEAN(0, "allow-empty-message", &opts->allow_empty_message, "allow commits with empty messages"),
OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"),
OPT_END(),
};
#include "send-pack.h"
#include "quote.h"
#include "transport.h"
+#include "version.h"
static const char send_pack_usage[] =
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
int status_report = 0;
int use_sideband = 0;
int quiet_supported = 0;
+ int agent_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
use_sideband = 1;
if (server_supports("quiet"))
quiet_supported = 1;
+ if (server_supports("agent"))
+ agent_supported = 1;
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
char *new_hex = sha1_to_hex(ref->new_sha1);
int quiet = quiet_supported && (args->quiet || !args->progress);
- if (!cmds_sent && (status_report || use_sideband || args->quiet)) {
- packet_buf_write(&req_buf, "%s %s %s%c%s%s%s",
+ if (!cmds_sent && (status_report || use_sideband ||
+ quiet || agent_supported)) {
+ packet_buf_write(&req_buf,
+ "%s %s %s%c%s%s%s%s%s",
old_hex, new_hex, ref->name, 0,
status_report ? " report-status" : "",
use_sideband ? " side-band-64k" : "",
- quiet ? " quiet" : "");
+ quiet ? " quiet" : "",
+ agent_supported ? " agent=" : "",
+ agent_supported ? git_user_agent_sanitized() : ""
+ );
}
else
packet_buf_write(&req_buf, "%s %s %s",
};
extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
extern int server_supports(const char *feature);
-extern const char *parse_feature_request(const char *features, const char *feature);
+extern int parse_feature_request(const char *features, const char *feature);
+extern const char *server_feature_value(const char *feature, int *len_ret);
+extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
#define CONFIG_NO_WRITE 4
#define CONFIG_NOTHING_SET 5
#define CONFIG_INVALID_PATTERN 6
+#define CONFIG_GENERIC_ERROR 7
typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *);
return xcalloc(1, 1);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(path);
- fill_filespec(df, sha1, mode);
+ fill_filespec(df, sha1, 1, mode);
*size = fill_textconv(textconv, df, &blob);
free_filespec(df);
} else {
&result_size, NULL, NULL);
} else if (textconv) {
struct diff_filespec *df = alloc_filespec(elem->path);
- fill_filespec(df, null_sha1, st.st_mode);
+ fill_filespec(df, null_sha1, 0, st.st_mode);
result_size = fill_textconv(textconv, df, &result);
free_filespec(df);
} else if (0 <= (fd = open(elem->path, O_RDONLY))) {
git-config ancillarymanipulators
git-count-objects ancillaryinterrogators
git-credential purehelpers
+git-credential-cache purehelpers
+git-credential-store purehelpers
git-cvsexportcommit foreignscminterface
git-cvsimport foreignscminterface
git-cvsserver foreignscminterface
git-show-branch ancillaryinterrogators
git-show-index plumbinginterrogators
git-show-ref plumbinginterrogators
+git-sh-i18n purehelpers
git-sh-setup purehelpers
git-stash mainporcelain
git-status mainporcelain common
--- /dev/null
+#include "../git-compat-util.h"
+#undef mkdir
+
+/* for platforms that can't deal with a trailing '/' */
+int compat_mkdir_wo_trailing_slash(const char *dir, mode_t mode)
+{
+ int retval;
+ char *tmp_dir = NULL;
+ size_t len = strlen(dir);
+
+ if (len && dir[len-1] == '/') {
+ if ((tmp_dir = strdup(dir)) == NULL)
+ return -1;
+ tmp_dir[len-1] = '\0';
+ }
+ else
+ tmp_dir = (char *)dir;
+
+ retval = mkdir(tmp_dir, mode);
+ if (tmp_dir != dir)
+ free(tmp_dir);
+
+ return retval;
+}
/*
* Converts filenames from decomposed unicode into precomposed unicode.
* Used on MacOS X.
-*/
-
+ */
#define PRECOMPOSE_UNICODE_C
#include "precompose_utf8.h"
typedef char *iconv_ibp;
-const static char *repo_encoding = "UTF-8";
-const static char *path_encoding = "UTF-8-MAC";
-
+static const char *repo_encoding = "UTF-8";
+static const char *path_encoding = "UTF-8-MAC";
-static size_t has_utf8(const char *s, size_t maxlen, size_t *strlen_c)
+static size_t has_non_ascii(const char *s, size_t maxlen, size_t *strlen_c)
{
- const uint8_t *utf8p = (const uint8_t*) s;
+ const uint8_t *ptr = (const uint8_t *)s;
size_t strlen_chars = 0;
size_t ret = 0;
- if ((!utf8p) || (!*utf8p)) {
+ if (!ptr || !*ptr)
return 0;
- }
- while((*utf8p) && maxlen) {
- if (*utf8p & 0x80)
+ while (*ptr && maxlen) {
+ if (*ptr & 0x80)
ret++;
strlen_chars++;
- utf8p++;
+ ptr++;
maxlen--;
}
if (strlen_c)
void probe_utf8_pathname_composition(char *path, int len)
{
- const static char *auml_nfc = "\xc3\xa4";
- const static char *auml_nfd = "\x61\xcc\x88";
+ static const char *auml_nfc = "\xc3\xa4";
+ static const char *auml_nfd = "\x61\xcc\x88";
int output_fd;
if (precomposed_unicode != -1)
return; /* We found it defined in the global config, respect it */
- path[len] = 0;
strcpy(path + len, auml_nfc);
output_fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600);
- if (output_fd >=0) {
+ if (output_fd >= 0) {
close(output_fd);
- path[len] = 0;
strcpy(path + len, auml_nfd);
/* Indicate to the user, that we can configure it to true */
- if (0 == access(path, R_OK))
+ if (!access(path, R_OK))
git_config_set("core.precomposeunicode", "false");
- /* To be backward compatible, set precomposed_unicode to 0 */
+ /* To be backward compatible, set precomposed_unicode to 0 */
precomposed_unicode = 0;
- path[len] = 0;
strcpy(path + len, auml_nfc);
- unlink(path);
+ if (unlink(path))
+ die_errno(_("failed to unlink '%s'"), path);
}
}
while (i < argc) {
size_t namelen;
oldarg = argv[i];
- if (has_utf8(oldarg, (size_t)-1, &namelen)) {
+ if (has_non_ascii(oldarg, (size_t)-1, &namelen)) {
newarg = reencode_string_iconv(oldarg, namelen, ic_precompose);
if (newarg)
argv[i] = newarg;
prec_dir->dirent_nfc->d_ino = res->d_ino;
prec_dir->dirent_nfc->d_type = res->d_type;
- if ((precomposed_unicode == 1) && has_utf8(res->d_name, (size_t)-1, NULL)) {
+ if ((precomposed_unicode == 1) && has_non_ascii(res->d_name, (size_t)-1, NULL)) {
if (prec_dir->ic_precompose == (iconv_t)-1) {
die("iconv_open(%s,%s) failed, but needed:\n"
" precomposed unicode is not supported.\n"
namelenz = 0; /* trigger strlcpy */
}
}
- }
- else
+ } else
namelenz = 0;
if (!namelenz)
return list;
}
-int server_supports(const char *feature)
-{
- return !!parse_feature_request(server_capabilities, feature);
-}
-
-const char *parse_feature_request(const char *feature_list, const char *feature)
+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)
{
int len;
const char *found = strstr(feature_list, feature);
if (!found)
return NULL;
- if ((feature_list == found || isspace(found[-1])) &&
- (!found[len] || isspace(found[len]) || found[len] == '='))
- return found;
+ if (feature_list == found || isspace(found[-1])) {
+ const char *value = found + len;
+ /* feature with no value (e.g., "thin-pack") */
+ if (!*value || isspace(*value)) {
+ if (lenp)
+ *lenp = 0;
+ return value;
+ }
+ /* feature with a value (e.g., "agent=git/1.2.3") */
+ else if (*value == '=') {
+ value++;
+ if (lenp)
+ *lenp = strcspn(value, " \t\n");
+ return value;
+ }
+ /*
+ * otherwise we matched a substring of another feature;
+ * keep looking
+ */
+ }
feature_list = found + 1;
}
return NULL;
}
+int parse_feature_request(const char *feature_list, const char *feature)
+{
+ return !!parse_feature_value(feature_list, feature, NULL);
+}
+
+const char *server_feature_value(const char *feature, int *len)
+{
+ return parse_feature_value(server_capabilities, feature, len);
+}
+
+int server_supports(const char *feature)
+{
+ return !!server_feature_value(feature, NULL);
+}
+
enum protocol {
PROTO_LOCAL = 1,
PROTO_SSH,
--- /dev/null
+= Installation instructions =
+
+Two scripts are included. The Python one (ciabot.py) is faster and
+more capable; the shell one (ciabot.sh) is a fallback in case Python
+gives your git hosting site indigestion. (I know of no such sites.)
+
+It is no longer necessary to modify the script in order to put it
+in place; in fact, this is now discouraged. It is entirely
+configurable with the following git config variables:
+
+ciabot.project = name of the project
+ciabot.repo = name of the project repo for gitweb/cgit purposes
+ciabot.xmlrpc = if true, ship notifications via XML-RPC
+ciabot.revformat = format in which the revision is shown
+
+The revformat variable may have the following values
+raw -> full hex ID of commit
+short -> first 12 chars of hex ID
+describe -> describe relative to last tag, falling back to short
+
+ciabot.project defaults to the directory name of the repository toplevel.
+ciabot.repo defaults to ciabot.project lowercased.
+ciabot.xmlrpc defaults to True
+ciabot.revformat defaults to 'describe'.
+
+This means that in the normal case you need not do any configuration at all,
+however setting ciabot.project will allow the hook to run slightly faster.
+
+Once you've set these variables, try your script with -n to see the
+notification message dumped to stdout and verify that it looks sane.
+
+To live-test these scripts, your project needs to have been registered with
+the CIA site. Here are the steps:
+
+1. Open an IRC window on irc://freenode/commits or your registered
+ project IRC channel.
+
+2. Run ciabot.py and/or ciabot.sh from any directory under git
+ control.
+
+You should see a notification on the channel for your most recent commit.
+
+After verifying correct function, install one of these scripts either
+in a post-commit hook or in an update hook.
+
+In post-commit, run it without arguments. It will query for
+current HEAD and the latest commit ID to get the information it
+needs.
+
+In update, call it with a refname followed by a list of commits:
+You want to reverse the order git rev-list emits because it lists
+from most recent to oldest.
+
+/path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac)
better documented. The shell version is maintained only as a fallback
for use on hosting sites that don't permit Python hook scripts.
-You will find installation instructions for each script in its comment
-header.
+See the file INSTALL for installation instructions.
# usage: ciabot.py [-V] [-n] [-p projectname] [refname [commits...]]
#
# This script is meant to be run either in a post-commit hook or in an
-# update hook. If there's nothing unusual about your hosting setup,
-# you can specify the project name with a -p option and avoid having
-# to modify this script. Try it with -n to see the notification mail
-# dumped to stdout and verify that it looks sane. With -V it dumps its
-# version and exits.
+# update hook. Try it with -n to see the notification mail dumped to
+# stdout and verify that it looks sane. With -V it dumps its version
+# and exits.
#
-# In post-commit, run it without arguments (other than possibly a -p
-# option). It will query for current HEAD and the latest commit ID to
-# get the information it needs.
+# In post-commit, run it without arguments. It will query for
+# current HEAD and the latest commit ID to get the information it
+# needs.
#
# In update, call it with a refname followed by a list of commits:
-# You want to reverse the order git rev-list emits becxause it lists
+# You want to reverse the order git rev-list emits because it lists
# from most recent to oldest.
#
# /path/to/ciabot.py ${refname} $(git rev-list ${oldhead}..${newhead} | tac)
#
-# Note: this script uses mail, not XML-RPC, in order to avoid stalling
-# until timeout when the CIA XML-RPC server is down.
+# Configuration variables affecting this script:
#
-
+# ciabot.project = name of the project
+# ciabot.repo = name of the project repo for gitweb/cgit purposes
+# ciabot.xmlrpc = if true (default), ship notifications via XML-RPC
+# ciabot.revformat = format in which the revision is shown
#
-# The project as known to CIA. You will either want to change this
-# or invoke the script with a -p option to set it.
+# ciabot.project defaults to the directory name of the repository toplevel.
+# ciabot.repo defaults to ciabot.project lowercased.
#
-project=None
-
+# This means that in the normal case you need not do any configuration at all,
+# but setting the project name will speed it up slightly.
#
-# You may not need to change these:
+# The revformat variable may have the following values
+# raw -> full hex ID of commit
+# short -> first 12 chars of hex ID
+# describe = -> describe relative to last tag, falling back to short
+# The default is 'describe'.
+#
+# Note: the CIA project now says only XML-RPC is reliable, so
+# we default to that.
#
-import os, sys, commands, socket, urllib
-
-# Name of the repository.
-# You can hardwire this to make the script faster.
-repo = os.path.basename(os.getcwd())
-# Fully-qualified domain name of this host.
-# You can hardwire this to make the script faster.
-host = socket.getfqdn()
+import os, sys, commands, socket, urllib
+from xml.sax.saxutils import escape
# Changeset URL prefix for your repo: when the commit ID is appended
# to this, it should point at a CGI that will display the commit
<message>
<generator>
<name>CIA Python client for Git</name>
- <version>%(gitver)s</version>
+ <version>%(version)s</version>
<url>%(generator)s</url>
</generator>
<source>
# No user-serviceable parts below this line:
#
-# Addresses for the e-mail. The from address is a dummy, since CIA
-# will never reply to this mail.
-fromaddr = "CIABOT-NOREPLY@" + host
-toaddr = "cia@cia.navi.cx"
+# Where to ship e-mail notifications.
+toaddr = "cia@cia.vc"
# Identify the generator script.
# Should only change when the script itself gets a new home and maintainer.
-generator="http://www.catb.org/~esr/ciabot.py"
+generator = "http://www.catb.org/~esr/ciabot.py"
+version = "3.6"
def do(command):
return commands.getstatusoutput(command)[1]
-def report(refname, merged):
+def report(refname, merged, xmlrpc=True):
"Generate a commit notification to be reported to CIA"
# Try to tinyfy a reference to a web view for this commit.
branch = os.path.basename(refname)
- # Compute a shortnane for the revision
- rev = do("git describe '"+ merged +"' 2>/dev/null") or merged[:12]
-
- # Extract the neta-information for the commit
- rawcommit = do("git cat-file commit " + merged)
+ # Compute a description for the revision
+ if revformat == 'raw':
+ rev = merged
+ elif revformat == 'short':
+ rev = ''
+ else: # revformat == 'describe'
+ rev = do("git describe %s 2>/dev/null" % merged)
+ if not rev:
+ rev = merged[:12]
+
+ # Extract the meta-information for the commit
files=do("git diff-tree -r --name-only '"+ merged +"' | sed -e '1d' -e 's-.*-<file>&</file>-'")
- inheader = True
- headers = {}
- logmsg = ""
- for line in rawcommit.split("\n"):
- if inheader:
- if line:
- fields = line.split()
- headers[fields[0]] = " ".join(fields[1:])
- else:
- inheader = False
- else:
- logmsg = line
- break
- (author, ts) = headers["author"].split(">")
+ metainfo = do("git log -1 '--pretty=format:%an <%ae>%n%at%n%s' " + merged)
+ (author, ts, logmsg) = metainfo.split("\n")
+ logmsg = escape(logmsg)
- # This discards the part of the authors addrsss after @.
- # Might be bnicece to ship the full email address, if not
+ # This discards the part of the author's address after @.
+ # Might be be nice to ship the full email address, if not
# for spammers' address harvesters - getting this wrong
# would make the freenode #commits channel into harvester heaven.
- author = author.replace("<", "").split("@")[0].split()[-1]
+ author = escape(author.replace("<", "").split("@")[0].split()[-1])
# This ignores the timezone. Not clear what to do with it...
ts = ts.strip().split()[0]
context.update(globals())
out = xml % context
-
- message = '''\
+ mail = '''\
Message-ID: <%(merged)s.%(author)s@%(project)s>
From: %(fromaddr)s
To: %(toaddr)s
%(out)s''' % locals()
- return message
+ if xmlrpc:
+ return out
+ else:
+ return mail
if __name__ == "__main__":
import getopt
+ # Get all config variables
+ revformat = do("git config --get ciabot.revformat")
+ project = do("git config --get ciabot.project")
+ repo = do("git config --get ciabot.repo")
+ xmlrpc = do("git config --get ciabot.xmlrpc")
+ xmlrpc = not (xmlrpc and xmlrpc == "false")
+
+ host = socket.getfqdn()
+ fromaddr = "CIABOT-NOREPLY@" + host
+
try:
- (options, arguments) = getopt.getopt(sys.argv[1:], "np:V")
+ (options, arguments) = getopt.getopt(sys.argv[1:], "np:xV")
except getopt.GetoptError, msg:
print "ciabot.py: " + str(msg)
raise SystemExit, 1
- mailit = True
+ notify = True
for (switch, val) in options:
if switch == '-p':
project = val
elif switch == '-n':
- mailit = False
+ notify = False
+ elif switch == '-x':
+ xmlrpc = True
elif switch == '-V':
- print "ciabot.py: version 3.2"
+ print "ciabot.py: version", version
sys.exit(0)
- # Cough and die if user has not specified a project
+ # The project variable defaults to the name of the repository toplevel.
if not project:
- sys.stderr.write("ciabot.py: no project specified, bailing out.\n")
- sys.exit(1)
-
- # We'll need the git version number.
- gitver = do("git --version").split()[0]
+ here = os.getcwd()
+ while True:
+ if os.path.exists(os.path.join(here, ".git")):
+ project = os.path.basename(here)
+ break
+ elif here == '/':
+ sys.stderr.write("ciabot.py: no .git below root!\n")
+ sys.exit(1)
+ here = os.path.dirname(here)
+
+ if not repo:
+ repo = project.lower()
urlprefix = urlprefix % globals()
refname = arguments[0]
merges = arguments[1:]
- if mailit:
- import smtplib
- server = smtplib.SMTP('localhost')
+ if notify:
+ if xmlrpc:
+ import xmlrpclib
+ server = xmlrpclib.Server('http://cia.vc/RPC2');
+ else:
+ import smtplib
+ server = smtplib.SMTP('localhost')
for merged in merges:
- message = report(refname, merged)
- if mailit:
- server.sendmail(fromaddr, [toaddr], message)
- else:
+ message = report(refname, merged, xmlrpc)
+ if not notify:
print message
+ elif xmlrpc:
+ try:
+ # RPC server is flaky, this can fail due to timeout.
+ server.hub.deliver(message)
+ except socket.error, e:
+ sys.stderr.write("%s\n" % e)
+ else:
+ server.sendmail(fromaddr, [toaddr], message)
- if mailit:
- server.quit()
+ if notify:
+ if not xmlrpc:
+ server.quit()
#End
# Copyright (c) 2006 Fernando J. Pereda <ferdy@gentoo.org>
# Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com>
# Copyright (c) 2010 Eric S. Raymond <esr@thyrsus.com>
+# Assistance and review by Petr Baudis, author of ciabot.pl,
+# is gratefully acknowledged.
#
# This is a version 3.x of ciabot.sh; use -V to find the exact
# version. Versions 1 and 2 were shipped in 2006 and 2008 and are not
# Note: This script should be considered obsolete.
# There is a faster, better-documented rewrite in Python: find it as ciabot.py
# Use this only if your hosting site forbids Python hooks.
+# It requires: git(1), hostname(1), cut(1), sendmail(1), and wget(1).
#
# Originally based on Git ciabot.pl by Petr Baudis.
# This script contains porcelain and porcelain byproducts.
# usage: ciabot.sh [-V] [-n] [-p projectname] [refname commit]
#
# This script is meant to be run either in a post-commit hook or in an
-# update hook. If there's nothing unusual about your hosting setup,
-# you can specify the project name with a -p option and avoid having
-# to modify this script. Try it with -n first to see the notification
-# mail dumped to stdout and verify that it looks sane. Use -V to dump
-# the version and exit.
+# update hook. Try it with -n to see the notification mail dumped to
+# stdout and verify that it looks sane. With -V it dumps its version
+# and exits.
#
-# In post-commit, run it without arguments (other than possibly a -p
-# option). It will query for current HEAD and the latest commit ID to
-# get the information it needs.
+# In post-commit, run it without arguments. It will query for
+# current HEAD and the latest commit ID to get the information it
+# needs.
#
# In update, you have to call it once per merged commit:
#
# oldhead=$2
# newhead=$3
# for merged in $(git rev-list ${oldhead}..${newhead} | tac) ; do
-# /path/to/ciabot.bash ${refname} ${merged}
+# /path/to/ciabot.sh ${refname} ${merged}
# done
#
-# The reason for the tac call ids that git rev-list emits commits from
+# The reason for the tac call is that git rev-list emits commits from
# most recent to least - better to ship notifactions from oldest to newest.
#
-# Note: this script uses mail, not XML-RPC, in order to avoid stalling
-# until timeout when the CIA XML-RPC server is down.
+# Configuration variables affecting this script:
#
-
+# ciabot.project = name of the project
+# ciabot.repo = name of the project repo for gitweb/cgit purposes
+# ciabot.revformat = format in which the revision is shown
#
-# The project as known to CIA. You will either want to change this
-# or set the project name with a -p option.
+# ciabot.project defaults to the directory name of the repository toplevel.
+# ciabot.repo defaults to ciabot.project lowercased.
#
-project=
-
+# This means that in the normal case you need not do any configuration at all,
+# but setting the project name will speed it up slightly.
#
-# You may not need to change these:
+# The revformat variable may have the following values
+# raw -> full hex ID of commit
+# short -> first 12 chars of hex ID
+# describe = -> describe relative to last tag, falling back to short
+# The default is 'describe'.
#
+# Note: the shell ancestors of this script used mail, not XML-RPC, in
+# order to avoid stalling until timeout when the CIA XML-RPC server is
+# down. It is unknown whether this is still an issue in 2010, but
+# XML-RPC would be annoying to do from sh in any case. (XML-RPC does
+# have the advantage that it guarantees notification of multiple commits
+# shpped from an update in their actual order.)
+#
+
+# The project as known to CIA. You can set this with a -p option,
+# or let it default to the directory name of the repo toplevel.
+project=$(git config --get ciabot.project)
+
+if [ -z $project ]
+then
+ here=`pwd`;
+ while :; do
+ if [ -d $here/.git ]
+ then
+ project=`basename $here`
+ break
+ elif [ $here = '/' ]
+ then
+ echo "ciabot.sh: no .git below root!"
+ exit 1
+ fi
+ here=`dirname $here`
+ done
+fi
-# Name of the repository.
-# You can hardwire this to make the script faster.
-repo="`basename ${PWD}`"
+# Name of the repo for gitweb/cgit purposes
+repo=$(git config --get ciabot.repo)
+[ -z $repo] && repo=$(echo "${project}" | tr '[A-Z]' '[a-z]')
-# Fully qualified domain name of the repo host.
-# You can hardwire this to make the script faster.
-host=`hostname --fqdn`
+# What revision format do we want in the summary?
+revformat=$(git config --get ciabot.revformat)
+
+# Fully qualified domain name of the repo host. You can hardwire this
+# to make the script faster. The -f option works under Linux and FreeBSD,
+# but not OpenBSD and NetBSD. But under OpenBSD and NetBSD,
+# hostname without options gives the FQDN.
+if hostname -f >/dev/null 2>&1
+then
+ hostname=`hostname -f`
+else
+ hostname=`hostname`
+fi
# Changeset URL prefix for your repo: when the commit ID is appended
# to this, it should point at a CGI that will display the commit
# You probably will not need to change the following:
#
-# Identify the script. Should change only when the script itself
-# gets a new home and maintainer.
+# Identify the script. The 'generator' variable should change only
+# when the script itself gets a new home and maintainer.
generator="http://www.catb.org/~esr/ciabot/ciabot.sh"
+version=3.5
# Addresses for the e-mail
-from="CIABOT-NOREPLY@${host}"
-to="cia@cia.navi.cx"
+from="CIABOT-NOREPLY@${hostname}"
+to="cia@cia.vc"
# SMTP client to use - may need to edit the absolute pathname for your system
sendmail="sendmail -t -f ${from}"
case $opt in
p) project=$2; shift ; shift ;;
n) mode=dumpit; shift ;;
- V) echo "ciabot.sh: version 3.2"; exit 0; shift ;;
+ V) echo "ciabot.sh: version $version"; exit 0; shift ;;
esac
done
refname=${refname##refs/heads/}
-gitver=$(git --version)
-gitver=${gitver##* }
-
-rev=$(git describe ${merged} 2>/dev/null)
-# ${merged:0:12} was the only bashism left in the 2008 version of this
-# script, according to checkbashisms. Replace it with ${merged} here
-# because it was just a fallback anyway, and it's worth accepting a
-# longer fallback for faster execution and removing the bash
-# dependency.
-[ -z ${rev} ] && rev=${merged}
+case $revformat in
+raw) rev=$merged ;;
+short) rev='' ;;
+*) rev=$(git describe ${merged} 2>/dev/null) ;;
+esac
+[ -z ${rev} ] && rev=$(echo "$merged" | cut -c 1-12)
-# This discards the part of the author's address after @.
+# We discard the part of the author's address after @.
# Might be nice to ship the full email address, if not
# for spammers' address harvesters - getting this wrong
# would make the freenode #commits channel into harvester heaven.
-rawcommit=$(git cat-file commit ${merged})
-author=$(echo "$rawcommit" | sed -n -e '/^author .*<\([^@]*\).*$/s--\1-p')
-logmessage=$(echo "$rawcommit" | sed -e '1,/^$/d' | head -n 1)
-logmessage=$(echo "$logmessage" | sed 's/\&/&\;/g; s/</<\;/g; s/>/>\;/g')
-ts=$(echo "$rawcommit" | sed -n -e '/^author .*> \([0-9]\+\).*$/s--\1-p')
+author=$(git log -1 '--pretty=format:%an <%ae>' $merged)
+author=$(echo "$author" | sed -n -e '/^.*<\([^@]*\).*$/s--\1-p')
+
+logmessage=$(git log -1 '--pretty=format:%s' $merged)
+ts=$(git log -1 '--pretty=format:%at' $merged)
files=$(git diff-tree -r --name-only ${merged} | sed -e '1d' -e 's-.*-<file>&</file>-')
out="
<message>
<generator>
<name>CIA Shell client for Git</name>
- <version>${gitver}</version>
+ <version>${version}</version>
<url>${generator}</url>
</generator>
<source>
}
__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
- tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3
+ tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3 codecompare
"
_git_difftool ()
--- /dev/null
+all: git-credential-wincred.exe
+
+CC = gcc
+RM = rm -f
+CFLAGS = -O2 -Wall
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+git-credential-wincred.exe : git-credential-wincred.c
+ $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
+
+clean:
+ $(RM) git-credential-wincred.exe
--- /dev/null
+/*
+ * A git credential helper that interface with Windows' Credential Manager
+ *
+ */
+#include <windows.h>
+#include <stdio.h>
+#include <io.h>
+#include <fcntl.h>
+
+/* common helpers */
+
+static void die(const char *err, ...)
+{
+ char msg[4096];
+ va_list params;
+ va_start(params, err);
+ vsnprintf(msg, sizeof(msg), err, params);
+ fprintf(stderr, "%s\n", msg);
+ va_end(params);
+ exit(1);
+}
+
+static void *xmalloc(size_t size)
+{
+ void *ret = malloc(size);
+ if (!ret && !size)
+ ret = malloc(1);
+ if (!ret)
+ die("Out of memory");
+ return ret;
+}
+
+static char *xstrdup(const char *str)
+{
+ char *ret = strdup(str);
+ if (!ret)
+ die("Out of memory");
+ return ret;
+}
+
+/* MinGW doesn't have wincred.h, so we need to define stuff */
+
+typedef struct _CREDENTIAL_ATTRIBUTEW {
+ LPWSTR Keyword;
+ DWORD Flags;
+ DWORD ValueSize;
+ LPBYTE Value;
+} CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
+
+typedef struct _CREDENTIALW {
+ DWORD Flags;
+ DWORD Type;
+ LPWSTR TargetName;
+ LPWSTR Comment;
+ FILETIME LastWritten;
+ DWORD CredentialBlobSize;
+ LPBYTE CredentialBlob;
+ DWORD Persist;
+ DWORD AttributeCount;
+ PCREDENTIAL_ATTRIBUTEW Attributes;
+ LPWSTR TargetAlias;
+ LPWSTR UserName;
+} CREDENTIALW, *PCREDENTIALW;
+
+#define CRED_TYPE_GENERIC 1
+#define CRED_PERSIST_LOCAL_MACHINE 2
+#define CRED_MAX_ATTRIBUTES 64
+
+typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
+typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
+ LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
+typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
+ PCREDENTIALW **);
+typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
+ PBYTE, DWORD *);
+typedef VOID (WINAPI *CredFreeT)(PVOID);
+typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
+
+static HMODULE advapi, credui;
+static CredWriteWT CredWriteW;
+static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
+static CredEnumerateWT CredEnumerateW;
+static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
+static CredFreeT CredFree;
+static CredDeleteWT CredDeleteW;
+
+static void load_cred_funcs(void)
+{
+ /* load DLLs */
+ advapi = LoadLibrary("advapi32.dll");
+ credui = LoadLibrary("credui.dll");
+ if (!advapi || !credui)
+ die("failed to load DLLs");
+
+ /* get function pointers */
+ CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
+ CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
+ GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
+ CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
+ "CredEnumerateW");
+ CredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)
+ GetProcAddress(credui, "CredPackAuthenticationBufferW");
+ CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
+ CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
+ if (!CredWriteW || !CredUnPackAuthenticationBufferW ||
+ !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||
+ !CredDeleteW)
+ die("failed to load functions");
+}
+
+static char target_buf[1024];
+static char *protocol, *host, *path, *username;
+static WCHAR *wusername, *password, *target;
+
+static void write_item(const char *what, WCHAR *wbuf)
+{
+ char *buf;
+ int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
+ FALSE);
+ buf = xmalloc(len);
+
+ if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
+ die("WideCharToMultiByte failed!");
+
+ printf("%s=", what);
+ fwrite(buf, 1, len - 1, stdout);
+ putchar('\n');
+ free(buf);
+}
+
+static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
+ const char *want)
+{
+ int i;
+ if (!want)
+ return 1;
+
+ for (i = 0; i < cred->AttributeCount; ++i)
+ if (!wcscmp(cred->Attributes[i].Keyword, keyword))
+ return !strcmp((const char *)cred->Attributes[i].Value,
+ want);
+
+ return 0; /* not found */
+}
+
+static int match_cred(const CREDENTIALW *cred)
+{
+ return (!wusername || !wcscmp(wusername, cred->UserName)) &&
+ match_attr(cred, L"git_protocol", protocol) &&
+ match_attr(cred, L"git_host", host) &&
+ match_attr(cred, L"git_path", path);
+}
+
+static void get_credential(void)
+{
+ WCHAR *user_buf, *pass_buf;
+ DWORD user_buf_size = 0, pass_buf_size = 0;
+ CREDENTIALW **creds, *cred = NULL;
+ DWORD num_creds;
+ int i;
+
+ if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
+ return;
+
+ /* search for the first credential that matches username */
+ for (i = 0; i < num_creds; ++i)
+ if (match_cred(creds[i])) {
+ cred = creds[i];
+ break;
+ }
+ if (!cred)
+ return;
+
+ CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
+ cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
+ NULL, &pass_buf_size);
+
+ user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
+ pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
+
+ if (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
+ cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,
+ pass_buf, &pass_buf_size))
+ die("CredUnPackAuthenticationBuffer failed");
+
+ CredFree(creds);
+
+ /* zero-terminate (sizes include zero-termination) */
+ user_buf[user_buf_size - 1] = L'\0';
+ pass_buf[pass_buf_size - 1] = L'\0';
+
+ write_item("username", user_buf);
+ write_item("password", pass_buf);
+
+ free(user_buf);
+ free(pass_buf);
+}
+
+static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
+ const char *value)
+{
+ attr->Keyword = (LPWSTR)keyword;
+ attr->Flags = 0;
+ attr->ValueSize = strlen(value) + 1; /* store zero-termination */
+ attr->Value = (LPBYTE)value;
+}
+
+static void store_credential(void)
+{
+ CREDENTIALW cred;
+ BYTE *auth_buf;
+ DWORD auth_buf_size = 0;
+ CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
+
+ if (!wusername || !password)
+ return;
+
+ /* query buffer size */
+ CredPackAuthenticationBufferW(0, wusername, password,
+ NULL, &auth_buf_size);
+
+ auth_buf = xmalloc(auth_buf_size);
+
+ if (!CredPackAuthenticationBufferW(0, wusername, password,
+ auth_buf, &auth_buf_size))
+ die("CredPackAuthenticationBuffer failed");
+
+ cred.Flags = 0;
+ cred.Type = CRED_TYPE_GENERIC;
+ cred.TargetName = target;
+ cred.Comment = L"saved by git-credential-wincred";
+ cred.CredentialBlobSize = auth_buf_size;
+ cred.CredentialBlob = auth_buf;
+ cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
+ cred.AttributeCount = 1;
+ cred.Attributes = attrs;
+ cred.TargetAlias = NULL;
+ cred.UserName = wusername;
+
+ write_attr(attrs, L"git_protocol", protocol);
+
+ if (host) {
+ write_attr(attrs + cred.AttributeCount, L"git_host", host);
+ cred.AttributeCount++;
+ }
+
+ if (path) {
+ write_attr(attrs + cred.AttributeCount, L"git_path", path);
+ cred.AttributeCount++;
+ }
+
+ if (!CredWriteW(&cred, 0))
+ die("CredWrite failed");
+}
+
+static void erase_credential(void)
+{
+ CREDENTIALW **creds;
+ DWORD num_creds;
+ int i;
+
+ if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
+ return;
+
+ for (i = 0; i < num_creds; ++i) {
+ if (match_cred(creds[i]))
+ CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
+ }
+
+ CredFree(creds);
+}
+
+static WCHAR *utf8_to_utf16_dup(const char *str)
+{
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
+ WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
+ MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
+ return wstr;
+}
+
+static void read_credential(void)
+{
+ char buf[1024];
+
+ while (fgets(buf, sizeof(buf), stdin)) {
+ char *v;
+
+ if (!strcmp(buf, "\n"))
+ break;
+ buf[strlen(buf)-1] = '\0';
+
+ v = strchr(buf, '=');
+ if (!v)
+ die("bad input: %s", buf);
+ *v++ = '\0';
+
+ if (!strcmp(buf, "protocol"))
+ protocol = xstrdup(v);
+ else if (!strcmp(buf, "host"))
+ host = xstrdup(v);
+ else if (!strcmp(buf, "path"))
+ path = xstrdup(v);
+ else if (!strcmp(buf, "username")) {
+ username = xstrdup(v);
+ wusername = utf8_to_utf16_dup(v);
+ } else if (!strcmp(buf, "password"))
+ password = utf8_to_utf16_dup(v);
+ else
+ die("unrecognized input");
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ const char *usage =
+ "Usage: git credential-wincred <get|store|erase>\n";
+
+ if (!argv[1])
+ die(usage);
+
+ /* git use binary pipes to avoid CRLF-issues */
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+
+ read_credential();
+
+ load_cred_funcs();
+
+ if (!protocol || !(host || path))
+ return 0;
+
+ /* prepare 'target', the unique key for the credential */
+ strncat(target_buf, "git:", sizeof(target_buf));
+ strncat(target_buf, protocol, sizeof(target_buf));
+ strncat(target_buf, "://", sizeof(target_buf));
+ if (username) {
+ strncat(target_buf, username, sizeof(target_buf));
+ strncat(target_buf, "@", sizeof(target_buf));
+ }
+ if (host)
+ strncat(target_buf, host, sizeof(target_buf));
+ if (path) {
+ strncat(target_buf, "/", sizeof(target_buf));
+ strncat(target_buf, path, sizeof(target_buf));
+ }
+
+ target = utf8_to_utf16_dup(target_buf);
+
+ if (!strcmp(argv[1], "get"))
+ get_credential();
+ else if (!strcmp(argv[1], "store"))
+ store_credential();
+ else if (!strcmp(argv[1], "erase"))
+ erase_credential();
+ /* otherwise, ignore unknown action */
+ return 0;
+}
" [--interpolated-path=<path>]\n"
" [--reuseaddr] [--pid-file=<file>]\n"
" [--(enable|disable|allow-override|forbid-override)=<service>]\n"
+" [--access-hook=<path>]\n"
" [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n"
" [--detach] [--user=<user> [--group=<group>]]\n"
" [<directory>...]";
return -1;
}
+static char *access_hook;
+
+static int run_access_hook(struct daemon_service *service, const char *dir, const char *path)
+{
+ struct child_process child;
+ struct strbuf buf = STRBUF_INIT;
+ const char *argv[8];
+ const char **arg = argv;
+ char *eol;
+ int seen_errors = 0;
+
+#define STRARG(x) ((x) ? (x) : "")
+ *arg++ = access_hook;
+ *arg++ = service->name;
+ *arg++ = path;
+ *arg++ = STRARG(hostname);
+ *arg++ = STRARG(canon_hostname);
+ *arg++ = STRARG(ip_address);
+ *arg++ = STRARG(tcp_port);
+ *arg = NULL;
+#undef STRARG
+
+ memset(&child, 0, sizeof(child));
+ child.use_shell = 1;
+ child.argv = argv;
+ child.no_stdin = 1;
+ child.no_stderr = 1;
+ child.out = -1;
+ if (start_command(&child)) {
+ logerror("daemon access hook '%s' failed to start",
+ access_hook);
+ goto error_return;
+ }
+ if (strbuf_read(&buf, child.out, 0) < 0) {
+ logerror("failed to read from pipe to daemon access hook '%s'",
+ access_hook);
+ strbuf_reset(&buf);
+ seen_errors = 1;
+ }
+ if (close(child.out) < 0) {
+ logerror("failed to close pipe to daemon access hook '%s'",
+ access_hook);
+ seen_errors = 1;
+ }
+ if (finish_command(&child))
+ seen_errors = 1;
+
+ if (!seen_errors) {
+ strbuf_release(&buf);
+ return 0;
+ }
+
+error_return:
+ strbuf_ltrim(&buf);
+ if (!buf.len)
+ strbuf_addstr(&buf, "service rejected");
+ eol = strchr(buf.buf, '\n');
+ if (eol)
+ *eol = '\0';
+ errno = EACCES;
+ daemon_error(dir, buf.buf);
+ strbuf_release(&buf);
+ return -1;
+}
+
static int run_service(char *dir, struct daemon_service *service)
{
const char *path;
return daemon_error(dir, "service not enabled");
}
+ /*
+ * Optionally, a hook can choose to deny access to the
+ * repository depending on the phase of the moon.
+ */
+ if (access_hook && run_access_hook(service, dir, path))
+ return -1;
+
/*
* We'll ignore SIGTERM from now on, we have a
* good client.
export_all_trees = 1;
continue;
}
+ if (!prefixcmp(arg, "--access-hook=")) {
+ access_hook = arg + 14;
+ continue;
+ }
if (!prefixcmp(arg, "--timeout=")) {
timeout = atoi(arg+10);
continue;
if (silent_on_removed)
continue;
diff_addremove(&revs->diffopt, '-', ce->ce_mode,
- ce->sha1, ce->name, 0);
+ ce->sha1, !is_null_sha1(ce->sha1),
+ ce->name, 0);
continue;
}
changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
+ !is_null_sha1(ce->sha1), (changed ? 0 : !is_null_sha1(ce->sha1)),
ce->name, 0, dirty_submodule);
}
static void diff_index_show_file(struct rev_info *revs,
const char *prefix,
struct cache_entry *ce,
- const unsigned char *sha1, unsigned int mode,
+ const unsigned char *sha1, int sha1_valid,
+ unsigned int mode,
unsigned dirty_submodule)
{
diff_addremove(&revs->diffopt, prefix[0], mode,
- sha1, ce->name, dirty_submodule);
+ sha1, sha1_valid, ce->name, dirty_submodule);
}
static int get_stat_data(struct cache_entry *ce,
&dirty_submodule, &revs->diffopt) < 0)
return;
- diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule);
+ diff_index_show_file(revs, "+", new, sha1, !is_null_sha1(sha1), mode, dirty_submodule);
}
static int show_modified(struct rev_info *revs,
&dirty_submodule, &revs->diffopt) < 0) {
if (report_missing)
diff_index_show_file(revs, "-", old,
- old->sha1, old->ce_mode, 0);
+ old->sha1, 1, old->ce_mode, 0);
return -1;
}
return 0;
diff_change(&revs->diffopt, oldmode, mode,
- old->sha1, sha1, old->name, 0, dirty_submodule);
+ old->sha1, sha1, 1, !is_null_sha1(sha1),
+ old->name, 0, dirty_submodule);
return 0;
}
struct diff_filepair *pair;
pair = diff_unmerge(&revs->diffopt, idx->name);
if (tree)
- fill_filespec(pair->one, tree->sha1, tree->ce_mode);
+ fill_filespec(pair->one, tree->sha1, 1, tree->ce_mode);
return;
}
* Something removed from the tree?
*/
if (!idx) {
- diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0);
+ diff_index_show_file(revs, "-", tree, tree->sha1, 1, tree->ce_mode, 0);
return;
}
if (!name)
name = "/dev/null";
s = alloc_filespec(name);
- fill_filespec(s, null_sha1, mode);
+ fill_filespec(s, null_sha1, 0, mode);
if (name == file_from_standard_input)
populate_from_stdin(s);
return s;
DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
revs->max_count = -2;
- if (diff_setup_done(&revs->diffopt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&revs->diffopt);
setup_diff_pager(&revs->diffopt);
DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
if (!endp) {
const char *plain = diff_get_color(ecb->color_diff,
DIFF_PLAIN);
+ putc('\n', ecb->opt->file);
emit_line_0(ecb->opt, plain, reset, '\\',
nneof, strlen(nneof));
}
}
void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
- unsigned short mode)
+ int sha1_valid, unsigned short mode)
{
if (mode) {
spec->mode = canon_mode(mode);
hashcpy(spec->sha1, sha1);
- spec->sha1_valid = !is_null_sha1(sha1);
+ spec->sha1_valid = sha1_valid;
}
}
}
}
-int diff_setup_done(struct diff_options *options)
+void diff_setup_done(struct diff_options *options)
{
int count = 0;
options->output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
}
-
- return 0;
}
static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
+ int sha1_valid,
const char *concatpath, unsigned dirty_submodule)
{
struct diff_filespec *one, *two;
two = alloc_filespec(concatpath);
if (addremove != '+')
- fill_filespec(one, sha1, mode);
+ fill_filespec(one, sha1, sha1_valid, mode);
if (addremove != '-') {
- fill_filespec(two, sha1, mode);
+ fill_filespec(two, sha1, sha1_valid, mode);
two->dirty_submodule = dirty_submodule;
}
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
+ int old_sha1_valid, int new_sha1_valid,
const char *concatpath,
unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
const unsigned char *tmp_c;
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+ tmp = old_sha1_valid; old_sha1_valid = new_sha1_valid;
+ new_sha1_valid = tmp;
tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
new_dirty_submodule = tmp;
}
one = alloc_filespec(concatpath);
two = alloc_filespec(concatpath);
- fill_filespec(one, old_sha1, old_mode);
- fill_filespec(two, new_sha1, new_mode);
+ fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
+ fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
one->dirty_submodule = old_dirty_submodule;
two->dirty_submodule = new_dirty_submodule;
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
+ int old_sha1_valid, int new_sha1_valid,
const char *fullpath,
unsigned old_dirty_submodule, unsigned new_dirty_submodule);
typedef void (*add_remove_fn_t)(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
+ int sha1_valid,
const char *fullpath, unsigned dirty_submodule);
typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
int addremove,
unsigned mode,
const unsigned char *sha1,
+ int sha1_valid,
const char *fullpath, unsigned dirty_submodule);
extern void diff_change(struct diff_options *,
unsigned mode1, unsigned mode2,
const unsigned char *sha1,
const unsigned char *sha2,
+ int sha1_valid,
+ int sha2_valid,
const char *fullpath,
unsigned dirty_submodule1, unsigned dirty_submodule2);
extern int diff_use_color_default;
extern void diff_setup(struct diff_options *);
extern int diff_opt_parse(struct diff_options *, const char **, int);
-extern int diff_setup_done(struct diff_options *);
+extern void diff_setup_done(struct diff_options *);
#define DIFF_DETECT_RENAME 1
#define DIFF_DETECT_COPY 2
memmove(rename_dst + first + 1, rename_dst + first,
(rename_dst_nr - first - 1) * sizeof(*rename_dst));
rename_dst[first].two = alloc_filespec(two->path);
- fill_filespec(rename_dst[first].two, two->sha1, two->mode);
+ fill_filespec(rename_dst[first].two, two->sha1, two->sha1_valid, two->mode);
rename_dst[first].pair = NULL;
return &(rename_dst[first]);
}
extern struct diff_filespec *alloc_filespec(const char *);
extern void free_filespec(struct diff_filespec *);
extern void fill_filespec(struct diff_filespec *, const unsigned char *,
- unsigned short);
+ int, unsigned short);
extern int diff_populate_filespec(struct diff_filespec *, int);
extern void diff_free_filespec_data(struct diff_filespec *);
static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
{
int retval;
+ int has_null_sha1 = 0;
int has_full_path = 0;
int has_empty_name = 0;
int has_zero_pad = 0;
while (desc.size) {
unsigned mode;
const char *name;
+ const unsigned char *sha1;
- tree_entry_extract(&desc, &name, &mode);
+ sha1 = tree_entry_extract(&desc, &name, &mode);
+ if (is_null_sha1(sha1))
+ has_null_sha1 = 1;
if (strchr(name, '/'))
has_full_path = 1;
if (!*name)
}
retval = 0;
+ if (has_null_sha1)
+ retval += error_func(&item->object, FSCK_WARN, "contains entries pointing to null sha1");
if (has_full_path)
retval += error_func(&item->object, FSCK_WARN, "contains full pathnames");
if (has_empty_name)
static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
{
+ if (!*msgid)
+ return "";
return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
}
#define probe_utf8_pathname_composition(a,b)
#endif
+#ifdef MKDIR_WO_TRAILING_SLASH
+#define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b))
+extern int compat_mkdir_wo_trailing_slash(const char*, mode_t);
+#endif
+
#ifndef NO_LIBGEN_H
#include <libgen.h>
#else
use File::Compare;
use File::Find;
use File::stat;
-use File::Path qw(mkpath);
+use File::Path qw(mkpath rmtree);
use File::Temp qw(tempdir);
use Getopt::Long qw(:config pass_through);
use Git;
-my @tools;
-my @working_tree;
-my $rc;
-my $repo = Git->repository();
-my $repo_path = $repo->repo_path();
-
sub usage
{
my $exitcode = shift;
sub find_worktree
{
+ my ($repo) = @_;
+
# Git->repository->wc_path() does not honor changes to the working
# tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'
# config variable.
return $worktree;
}
-my $workdir = find_worktree();
-
sub filter_tool_scripts
{
+ my ($tools) = @_;
if (-d $_) {
if ($_ ne ".") {
# Ignore files in subdirectories
}
} else {
if ((-f $_) && ($_ ne "defaults")) {
- push(@tools, $_);
+ push(@$tools, $_);
}
}
}
sub print_tool_help
{
- my ($cmd, @found, @notfound);
+ my ($cmd, @found, @notfound, @tools);
my $gitpath = Git::exec_path();
- find(\&filter_tool_scripts, "$gitpath/mergetools");
+ find(sub { filter_tool_scripts(\@tools) }, "$gitpath/mergetools");
foreach my $tool (@tools) {
$cmd = "TOOL_MODE=diff";
}
}
- print "'git difftool --tool=<tool>' may be set to one of the following:\n";
+ print << 'EOF';
+'git difftool --tool=<tool>' may be set to one of the following:
+EOF
print "\t$_\n" for (sort(@found));
- print "\nThe following tools are valid, but not currently available:\n";
+ print << 'EOF';
+
+The following tools are valid, but not currently available:
+EOF
print "\t$_\n" for (sort(@notfound));
- print "\nNOTE: Some of the tools listed above only work in a windowed\n";
- print "environment. If run in a terminal-only session, they will fail.\n";
+ print << 'EOF';
+NOTE: Some of the tools listed above only work in a windowed
+environment. If run in a terminal-only session, they will fail.
+EOF
exit(0);
}
+sub exit_cleanup
+{
+ my ($tmpdir, $status) = @_;
+ my $errno = $!;
+ rmtree($tmpdir);
+ if ($status and $errno) {
+ my ($package, $file, $line) = caller();
+ warn "$file line $line: $errno\n";
+ }
+ exit($status | ($status >> 8));
+}
+
sub setup_dir_diff
{
+ my ($repo, $workdir, $symlinks) = @_;
+
# Run the diff; exit immediately if no diff found
# 'Repository' and 'WorkingCopy' must be explicitly set to insure that
# if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used
# by Git->repository->command*.
- my $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);
- my $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);
- exit(0) if (length($diffrtn) == 0);
+ my $repo_path = $repo->repo_path();
+ my %repo_args = (Repository => $repo_path, WorkingCopy => $workdir);
+ my $diffrepo = Git->repository(%repo_args);
- # Setup temp directories
- my $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);
- my $ldir = "$tmpdir/left";
- my $rdir = "$tmpdir/right";
- mkpath($ldir) or die $!;
- mkpath($rdir) or die $!;
+ my @gitargs = ('diff', '--raw', '--no-abbrev', '-z', @ARGV);
+ my $diffrtn = $diffrepo->command_oneline(@gitargs);
+ exit(0) unless defined($diffrtn);
# Build index info for left and right sides of the diff
my $submodule_mode = '160000';
my $rindex = '';
my %submodule;
my %symlink;
+ my @working_tree = ();
my @rawdiff = split('\0', $diffrtn);
my $i = 0;
while ($i < $#rawdiff) {
if ($rawdiff[$i] =~ /^::/) {
- print "Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\n";
+ warn << 'EOF';
+Combined diff formats ('-c' and '--cc') are not supported in
+directory diff mode ('-d' and '--dir-diff').
+EOF
exit(1);
}
- my ($lmode, $rmode, $lsha1, $rsha1, $status) = split(' ', substr($rawdiff[$i], 1));
+ my ($lmode, $rmode, $lsha1, $rsha1, $status) =
+ split(' ', substr($rawdiff[$i], 1));
my $src_path = $rawdiff[$i + 1];
my $dst_path;
$i += 2;
}
- if (($lmode eq $submodule_mode) or ($rmode eq $submodule_mode)) {
+ if ($lmode eq $submodule_mode or $rmode eq $submodule_mode) {
$submodule{$src_path}{left} = $lsha1;
if ($lsha1 ne $rsha1) {
$submodule{$dst_path}{right} = $rsha1;
}
if ($lmode eq $symlink_mode) {
- $symlink{$src_path}{left} = $diffrepo->command_oneline('show', "$lsha1");
+ $symlink{$src_path}{left} =
+ $diffrepo->command_oneline('show', "$lsha1");
}
if ($rmode eq $symlink_mode) {
- $symlink{$dst_path}{right} = $diffrepo->command_oneline('show', "$rsha1");
+ $symlink{$dst_path}{right} =
+ $diffrepo->command_oneline('show', "$rsha1");
}
- if (($lmode ne $null_mode) and ($status !~ /^C/)) {
+ if ($lmode ne $null_mode and $status !~ /^C/) {
$lindex .= "$lmode $lsha1\t$src_path\0";
}
}
}
+ # Setup temp directories
+ my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 0, TMPDIR => 1);
+ my $ldir = "$tmpdir/left";
+ my $rdir = "$tmpdir/right";
+ mkpath($ldir) or exit_cleanup($tmpdir, 1);
+ mkpath($rdir) or exit_cleanup($tmpdir, 1);
+
# If $GIT_DIR is not set prior to calling 'git update-index' and
# 'git checkout-index', then those commands will fail if difftool
# is called from a directory other than the repo root.
# Populate the left and right directories based on each index file
my ($inpipe, $ctx);
$ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
- ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+ ($inpipe, $ctx) =
+ $repo->command_input_pipe(qw(update-index -z --index-info));
print($inpipe $lindex);
$repo->command_close_pipe($inpipe, $ctx);
- $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
- exit($rc | ($rc >> 8)) if ($rc != 0);
+
+ my $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
+ exit_cleanup($tmpdir, $rc) if $rc != 0;
$ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
- ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+ ($inpipe, $ctx) =
+ $repo->command_input_pipe(qw(update-index -z --index-info));
print($inpipe $rindex);
$repo->command_close_pipe($inpipe, $ctx);
+
$rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
- exit($rc | ($rc >> 8)) if ($rc != 0);
+ exit_cleanup($tmpdir, $rc) if $rc != 0;
# If $GIT_DIR was explicitly set just for the update/checkout
# commands, then it should be unset before continuing.
for my $file (@working_tree) {
my $dir = dirname($file);
unless (-d "$rdir/$dir") {
- mkpath("$rdir/$dir") or die $!;
+ mkpath("$rdir/$dir") or
+ exit_cleanup($tmpdir, 1);
+ }
+ if ($symlinks) {
+ symlink("$workdir/$file", "$rdir/$file") or
+ exit_cleanup($tmpdir, 1);
+ } else {
+ copy("$workdir/$file", "$rdir/$file") or
+ exit_cleanup($tmpdir, 1);
+
+ my $mode = stat("$workdir/$file")->mode;
+ chmod($mode, "$rdir/$file") or
+ exit_cleanup($tmpdir, 1);
}
- copy("$workdir/$file", "$rdir/$file") or die $!;
- chmod(stat("$workdir/$file")->mode, "$rdir/$file") or die $!;
}
# Changes to submodules require special treatment. This loop writes a
# temporary file to both the left and right directories to show the
# change in the recorded SHA1 for the submodule.
for my $path (keys %submodule) {
+ my $ok;
if (defined($submodule{$path}{left})) {
- write_to_file("$ldir/$path", "Subproject commit $submodule{$path}{left}");
+ $ok = write_to_file("$ldir/$path",
+ "Subproject commit $submodule{$path}{left}");
}
if (defined($submodule{$path}{right})) {
- write_to_file("$rdir/$path", "Subproject commit $submodule{$path}{right}");
+ $ok = write_to_file("$rdir/$path",
+ "Subproject commit $submodule{$path}{right}");
}
+ exit_cleanup($tmpdir, 1) if not $ok;
}
# Symbolic links require special treatment. The standard "git diff"
# shows only the link itself, not the contents of the link target.
# This loop replicates that behavior.
for my $path (keys %symlink) {
+ my $ok;
if (defined($symlink{$path}{left})) {
- write_to_file("$ldir/$path", $symlink{$path}{left});
+ $ok = write_to_file("$ldir/$path",
+ $symlink{$path}{left});
}
if (defined($symlink{$path}{right})) {
- write_to_file("$rdir/$path", $symlink{$path}{right});
+ $ok = write_to_file("$rdir/$path",
+ $symlink{$path}{right});
}
+ exit_cleanup($tmpdir, 1) if not $ok;
}
- return ($ldir, $rdir);
+ return ($ldir, $rdir, $tmpdir, @working_tree);
}
sub write_to_file
# Make sure the path to the file exists
my $dir = dirname($path);
unless (-d "$dir") {
- mkpath("$dir") or die $!;
+ mkpath("$dir") or return 0;
}
# If the file already exists in that location, delete it. This
# is required in the case of symbolic links.
- unlink("$path");
+ unlink($path);
- open(my $fh, '>', "$path") or die $!;
+ open(my $fh, '>', $path) or return 0;
print($fh $value);
close($fh);
-}
-# parse command-line options. all unrecognized options and arguments
-# are passed through to the 'git diff' command.
-my ($difftool_cmd, $dirdiff, $extcmd, $gui, $help, $prompt, $tool_help);
-GetOptions('g|gui!' => \$gui,
- 'd|dir-diff' => \$dirdiff,
- 'h' => \$help,
- 'prompt!' => \$prompt,
- 'y' => sub { $prompt = 0; },
- 't|tool:s' => \$difftool_cmd,
- 'tool-help' => \$tool_help,
- 'x|extcmd:s' => \$extcmd);
-
-if (defined($help)) {
- usage(0);
-}
-if (defined($tool_help)) {
- print_tool_help();
+ return 1;
}
-if (defined($difftool_cmd)) {
- if (length($difftool_cmd) > 0) {
- $ENV{GIT_DIFF_TOOL} = $difftool_cmd;
- } else {
- print "No <tool> given for --tool=<tool>\n";
- usage(1);
+
+sub main
+{
+ # parse command-line options. all unrecognized options and arguments
+ # are passed through to the 'git diff' command.
+ my %opts = (
+ difftool_cmd => undef,
+ dirdiff => undef,
+ extcmd => undef,
+ gui => undef,
+ help => undef,
+ prompt => undef,
+ symlinks => $^O ne 'cygwin' &&
+ $^O ne 'MSWin32' && $^O ne 'msys',
+ tool_help => undef,
+ );
+ GetOptions('g|gui!' => \$opts{gui},
+ 'd|dir-diff' => \$opts{dirdiff},
+ 'h' => \$opts{help},
+ 'prompt!' => \$opts{prompt},
+ 'y' => sub { $opts{prompt} = 0; },
+ 'symlinks' => \$opts{symlinks},
+ 'no-symlinks' => sub { $opts{symlinks} = 0; },
+ 't|tool:s' => \$opts{difftool_cmd},
+ 'tool-help' => \$opts{tool_help},
+ 'x|extcmd:s' => \$opts{extcmd});
+
+ if (defined($opts{help})) {
+ usage(0);
}
-}
-if (defined($extcmd)) {
- if (length($extcmd) > 0) {
- $ENV{GIT_DIFFTOOL_EXTCMD} = $extcmd;
- } else {
- print "No <cmd> given for --extcmd=<cmd>\n";
- usage(1);
+ if (defined($opts{tool_help})) {
+ print_tool_help();
}
-}
-if ($gui) {
- my $guitool = '';
- $guitool = Git::config('diff.guitool');
- if (length($guitool) > 0) {
- $ENV{GIT_DIFF_TOOL} = $guitool;
+ if (defined($opts{difftool_cmd})) {
+ if (length($opts{difftool_cmd}) > 0) {
+ $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd};
+ } else {
+ print "No <tool> given for --tool=<tool>\n";
+ usage(1);
+ }
+ }
+ if (defined($opts{extcmd})) {
+ if (length($opts{extcmd}) > 0) {
+ $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd};
+ } else {
+ print "No <cmd> given for --extcmd=<cmd>\n";
+ usage(1);
+ }
+ }
+ if ($opts{gui}) {
+ my $guitool = Git::config('diff.guitool');
+ if (length($guitool) > 0) {
+ $ENV{GIT_DIFF_TOOL} = $guitool;
+ }
+ }
+
+ # In directory diff mode, 'git-difftool--helper' is called once
+ # to compare the a/b directories. In file diff mode, 'git diff'
+ # will invoke a separate instance of 'git-difftool--helper' for
+ # each file that changed.
+ if (defined($opts{dirdiff})) {
+ dir_diff($opts{extcmd}, $opts{symlinks});
+ } else {
+ file_diff($opts{prompt});
}
}
-# In directory diff mode, 'git-difftool--helper' is called once
-# to compare the a/b directories. In file diff mode, 'git diff'
-# will invoke a separate instance of 'git-difftool--helper' for
-# each file that changed.
-if (defined($dirdiff)) {
- my ($a, $b) = setup_dir_diff();
+sub dir_diff
+{
+ my ($extcmd, $symlinks) = @_;
+ my $rc;
+ my $error = 0;
+ my $repo = Git->repository();
+ my $workdir = find_worktree($repo);
+ my ($a, $b, $tmpdir, @worktree) =
+ setup_dir_diff($repo, $workdir, $symlinks);
+
if (defined($extcmd)) {
$rc = system($extcmd, $a, $b);
} else {
$ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
$rc = system('git', 'difftool--helper', $a, $b);
}
-
- exit($rc | ($rc >> 8)) if ($rc != 0);
-
# If the diff including working copy files and those
# files were modified during the diff, then the changes
- # should be copied back to the working tree
- for my $file (@working_tree) {
- if (-e "$b/$file" && compare("$b/$file", "$workdir/$file")) {
- copy("$b/$file", "$workdir/$file") or die $!;
- chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!;
+ # should be copied back to the working tree.
+ # Do not copy back files when symlinks are used and the
+ # external tool did not replace the original link with a file.
+ for my $file (@worktree) {
+ next if $symlinks && -l "$b/$file";
+ next if ! -f "$b/$file";
+
+ my $diff = compare("$b/$file", "$workdir/$file");
+ if ($diff == 0) {
+ next;
+ } elsif ($diff == -1) {
+ my $errmsg = "warning: Could not compare ";
+ $errmsg += "'$b/$file' with '$workdir/$file'\n";
+ warn $errmsg;
+ $error = 1;
+ } elsif ($diff == 1) {
+ my $mode = stat("$b/$file")->mode;
+ copy("$b/$file", "$workdir/$file") or
+ exit_cleanup($tmpdir, 1);
+
+ chmod($mode, "$workdir/$file") or
+ exit_cleanup($tmpdir, 1);
}
}
-} else {
+ if ($error) {
+ warn "warning: Temporary files exist in '$tmpdir'.\n";
+ warn "warning: You may want to cleanup or recover these.\n";
+ exit(1);
+ } else {
+ exit_cleanup($tmpdir, $rc);
+ }
+}
+
+sub file_diff
+{
+ my ($prompt) = @_;
+
if (defined($prompt)) {
if ($prompt) {
$ENV{GIT_DIFFTOOL_PROMPT} = 'true';
my $rc = system('git', 'diff', @ARGV);
exit($rc | ($rc >> 8));
}
+
+main();
else
tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
fi
- tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3"
+ tools="$tools gvimdiff diffuse ecmerge p4merge araxis bc3 codecompare"
fi
case "${VISUAL:-$EDITOR}" in
*vim*)
# Returns true if the mode reflects a symlink
is_symlink () {
- test "$1" = 120000
+ test "$1" = 120000
}
is_submodule () {
- test "$1" = 160000
+ test "$1" = 160000
}
local_present () {
- test -n "$local_mode"
+ test -n "$local_mode"
}
remote_present () {
- test -n "$remote_mode"
+ test -n "$remote_mode"
}
base_present () {
- test -n "$base_mode"
+ test -n "$base_mode"
}
cleanup_temp_files () {
- if test "$1" = --save-backup ; then
- rm -rf -- "$MERGED.orig"
- test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
- rm -f -- "$LOCAL" "$REMOTE" "$BASE"
- else
- rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
- fi
+ if test "$1" = --save-backup
+ then
+ rm -rf -- "$MERGED.orig"
+ test -e "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
+ rm -f -- "$LOCAL" "$REMOTE" "$BASE"
+ else
+ rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
+ fi
}
describe_file () {
- mode="$1"
- branch="$2"
- file="$3"
-
- printf " {%s}: " "$branch"
- if test -z "$mode"; then
- echo "deleted"
- elif is_symlink "$mode" ; then
- echo "a symbolic link -> '$(cat "$file")'"
- elif is_submodule "$mode" ; then
- echo "submodule commit $file"
- else
- if base_present; then
- echo "modified file"
+ mode="$1"
+ branch="$2"
+ file="$3"
+
+ printf " {%s}: " "$branch"
+ if test -z "$mode"
+ then
+ echo "deleted"
+ elif is_symlink "$mode"
+ then
+ echo "a symbolic link -> '$(cat "$file")'"
+ elif is_submodule "$mode"
+ then
+ echo "submodule commit $file"
+ elif base_present
+ then
+ echo "modified file"
else
- echo "created file"
+ echo "created file"
fi
- fi
}
-
resolve_symlink_merge () {
- while true; do
- printf "Use (l)ocal or (r)emote, or (a)bort? "
- read ans || return 1
- case "$ans" in
- [lL]*)
- git checkout-index -f --stage=2 -- "$MERGED"
- git add -- "$MERGED"
- cleanup_temp_files --save-backup
- return 0
- ;;
- [rR]*)
- git checkout-index -f --stage=3 -- "$MERGED"
- git add -- "$MERGED"
- cleanup_temp_files --save-backup
- return 0
- ;;
- [aA]*)
- return 1
- ;;
- esac
+ while true
+ do
+ printf "Use (l)ocal or (r)emote, or (a)bort? "
+ read ans || return 1
+ case "$ans" in
+ [lL]*)
+ git checkout-index -f --stage=2 -- "$MERGED"
+ git add -- "$MERGED"
+ cleanup_temp_files --save-backup
+ return 0
+ ;;
+ [rR]*)
+ git checkout-index -f --stage=3 -- "$MERGED"
+ git add -- "$MERGED"
+ cleanup_temp_files --save-backup
+ return 0
+ ;;
+ [aA]*)
+ return 1
+ ;;
+ esac
done
}
resolve_deleted_merge () {
- while true; do
- if base_present; then
- printf "Use (m)odified or (d)eleted file, or (a)bort? "
- else
- printf "Use (c)reated or (d)eleted file, or (a)bort? "
- fi
- read ans || return 1
- case "$ans" in
- [mMcC]*)
- git add -- "$MERGED"
- cleanup_temp_files --save-backup
- return 0
- ;;
- [dD]*)
- git rm -- "$MERGED" > /dev/null
- cleanup_temp_files
- return 0
- ;;
- [aA]*)
- return 1
- ;;
- esac
+ while true
+ do
+ if base_present
+ then
+ printf "Use (m)odified or (d)eleted file, or (a)bort? "
+ else
+ printf "Use (c)reated or (d)eleted file, or (a)bort? "
+ fi
+ read ans || return 1
+ case "$ans" in
+ [mMcC]*)
+ git add -- "$MERGED"
+ cleanup_temp_files --save-backup
+ return 0
+ ;;
+ [dD]*)
+ git rm -- "$MERGED" > /dev/null
+ cleanup_temp_files
+ return 0
+ ;;
+ [aA]*)
+ return 1
+ ;;
+ esac
done
}
resolve_submodule_merge () {
- while true; do
- printf "Use (l)ocal or (r)emote, or (a)bort? "
- read ans || return 1
- case "$ans" in
- [lL]*)
- if ! local_present; then
- if test -n "$(git ls-tree HEAD -- "$MERGED")"; then
- # Local isn't present, but it's a subdirectory
- git ls-tree --full-name -r HEAD -- "$MERGED" | git update-index --index-info || exit $?
- else
- test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
- git update-index --force-remove "$MERGED"
+ while true
+ do
+ printf "Use (l)ocal or (r)emote, or (a)bort? "
+ read ans || return 1
+ case "$ans" in
+ [lL]*)
+ if ! local_present
+ then
+ if test -n "$(git ls-tree HEAD -- "$MERGED")"
+ then
+ # Local isn't present, but it's a subdirectory
+ git ls-tree --full-name -r HEAD -- "$MERGED" |
+ git update-index --index-info || exit $?
+ else
+ test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ git update-index --force-remove "$MERGED"
+ cleanup_temp_files --save-backup
+ fi
+ elif is_submodule "$local_mode"
+ then
+ stage_submodule "$MERGED" "$local_sha1"
+ else
+ git checkout-index -f --stage=2 -- "$MERGED"
+ git add -- "$MERGED"
+ fi
+ return 0
+ ;;
+ [rR]*)
+ if ! remote_present
+ then
+ if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"
+ then
+ # Remote isn't present, but it's a subdirectory
+ git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" |
+ git update-index --index-info || exit $?
+ else
+ test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ git update-index --force-remove "$MERGED"
+ fi
+ elif is_submodule "$remote_mode"
+ then
+ ! is_submodule "$local_mode" &&
+ test -e "$MERGED" &&
+ mv -- "$MERGED" "$BACKUP"
+ stage_submodule "$MERGED" "$remote_sha1"
+ else
+ test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
+ git checkout-index -f --stage=3 -- "$MERGED"
+ git add -- "$MERGED"
+ fi
cleanup_temp_files --save-backup
- fi
- elif is_submodule "$local_mode"; then
- stage_submodule "$MERGED" "$local_sha1"
- else
- git checkout-index -f --stage=2 -- "$MERGED"
- git add -- "$MERGED"
- fi
- return 0
- ;;
- [rR]*)
- if ! remote_present; then
- if test -n "$(git ls-tree MERGE_HEAD -- "$MERGED")"; then
- # Remote isn't present, but it's a subdirectory
- git ls-tree --full-name -r MERGE_HEAD -- "$MERGED" | git update-index --index-info || exit $?
- else
- test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
- git update-index --force-remove "$MERGED"
- fi
- elif is_submodule "$remote_mode"; then
- ! is_submodule "$local_mode" && test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
- stage_submodule "$MERGED" "$remote_sha1"
- else
- test -e "$MERGED" && mv -- "$MERGED" "$BACKUP"
- git checkout-index -f --stage=3 -- "$MERGED"
- git add -- "$MERGED"
- fi
- cleanup_temp_files --save-backup
- return 0
- ;;
- [aA]*)
- return 1
- ;;
- esac
+ return 0
+ ;;
+ [aA]*)
+ return 1
+ ;;
+ esac
done
}
stage_submodule () {
- path="$1"
- submodule_sha1="$2"
- mkdir -p "$path" || die "fatal: unable to create directory for module at $path"
- # Find $path relative to work tree
- work_tree_root=$(cd_to_toplevel && pwd)
- work_rel_path=$(cd "$path" && GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix)
- test -n "$work_rel_path" || die "fatal: unable to get path of module $path relative to work tree"
- git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
+ path="$1"
+ submodule_sha1="$2"
+ mkdir -p "$path" ||
+ die "fatal: unable to create directory for module at $path"
+ # Find $path relative to work tree
+ work_tree_root=$(cd_to_toplevel && pwd)
+ work_rel_path=$(cd "$path" &&
+ GIT_WORK_TREE="${work_tree_root}" git rev-parse --show-prefix
+ )
+ test -n "$work_rel_path" ||
+ die "fatal: unable to get path of module $path relative to work tree"
+ git update-index --add --replace --cacheinfo 160000 "$submodule_sha1" "${work_rel_path%/}" || die
}
checkout_staged_file () {
- tmpfile=$(expr \
- "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
- : '\([^ ]*\) ')
-
- if test $? -eq 0 -a -n "$tmpfile" ; then
- mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
- else
- >"$3"
- fi
+ tmpfile=$(expr \
+ "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \
+ : '\([^ ]*\) ')
+
+ if test $? -eq 0 -a -n "$tmpfile"
+ then
+ mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
+ else
+ >"$3"
+ fi
}
merge_file () {
- MERGED="$1"
+ MERGED="$1"
- f=$(git ls-files -u -- "$MERGED")
- if test -z "$f" ; then
- if test ! -f "$MERGED" ; then
- echo "$MERGED: file not found"
- else
- echo "$MERGED: file does not need merging"
+ f=$(git ls-files -u -- "$MERGED")
+ if test -z "$f"
+ then
+ if test ! -f "$MERGED"
+ then
+ echo "$MERGED: file not found"
+ else
+ echo "$MERGED: file does not need merging"
+ fi
+ return 1
fi
- return 1
- fi
-
- ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
- BACKUP="./$MERGED.BACKUP.$ext"
- LOCAL="./$MERGED.LOCAL.$ext"
- REMOTE="./$MERGED.REMOTE.$ext"
- BASE="./$MERGED.BASE.$ext"
-
- base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
- local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
- remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
-
- if is_submodule "$local_mode" || is_submodule "$remote_mode"; then
- echo "Submodule merge conflict for '$MERGED':"
- local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
- remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
- describe_file "$local_mode" "local" "$local_sha1"
- describe_file "$remote_mode" "remote" "$remote_sha1"
- resolve_submodule_merge
- return
- fi
-
- mv -- "$MERGED" "$BACKUP"
- cp -- "$BACKUP" "$MERGED"
-
- checkout_staged_file 1 "$MERGED" "$BASE"
- checkout_staged_file 2 "$MERGED" "$LOCAL"
- checkout_staged_file 3 "$MERGED" "$REMOTE"
-
- if test -z "$local_mode" -o -z "$remote_mode"; then
- echo "Deleted merge conflict for '$MERGED':"
- describe_file "$local_mode" "local" "$LOCAL"
- describe_file "$remote_mode" "remote" "$REMOTE"
- resolve_deleted_merge
- return
- fi
- if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
- echo "Symbolic link merge conflict for '$MERGED':"
+ ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
+ BACKUP="./$MERGED.BACKUP.$ext"
+ LOCAL="./$MERGED.LOCAL.$ext"
+ REMOTE="./$MERGED.REMOTE.$ext"
+ BASE="./$MERGED.BASE.$ext"
+
+ base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
+ local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
+ remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
+
+ if is_submodule "$local_mode" || is_submodule "$remote_mode"
+ then
+ echo "Submodule merge conflict for '$MERGED':"
+ local_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $2;}')
+ remote_sha1=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $2;}')
+ describe_file "$local_mode" "local" "$local_sha1"
+ describe_file "$remote_mode" "remote" "$remote_sha1"
+ resolve_submodule_merge
+ return
+ fi
+
+ mv -- "$MERGED" "$BACKUP"
+ cp -- "$BACKUP" "$MERGED"
+
+ checkout_staged_file 1 "$MERGED" "$BASE"
+ checkout_staged_file 2 "$MERGED" "$LOCAL"
+ checkout_staged_file 3 "$MERGED" "$REMOTE"
+
+ if test -z "$local_mode" -o -z "$remote_mode"
+ then
+ echo "Deleted merge conflict for '$MERGED':"
+ describe_file "$local_mode" "local" "$LOCAL"
+ describe_file "$remote_mode" "remote" "$REMOTE"
+ resolve_deleted_merge
+ return
+ fi
+
+ if is_symlink "$local_mode" || is_symlink "$remote_mode"
+ then
+ echo "Symbolic link merge conflict for '$MERGED':"
+ describe_file "$local_mode" "local" "$LOCAL"
+ describe_file "$remote_mode" "remote" "$REMOTE"
+ resolve_symlink_merge
+ return
+ fi
+
+ echo "Normal merge conflict for '$MERGED':"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
- resolve_symlink_merge
- return
- fi
-
- echo "Normal merge conflict for '$MERGED':"
- describe_file "$local_mode" "local" "$LOCAL"
- describe_file "$remote_mode" "remote" "$REMOTE"
- if "$prompt" = true; then
- printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
- read ans || return 1
- fi
-
- if base_present; then
- present=true
- else
- present=false
- fi
-
- if ! run_merge_tool "$merge_tool" "$present"; then
- echo "merge of $MERGED failed" 1>&2
- mv -- "$BACKUP" "$MERGED"
-
- if test "$merge_keep_temporaries" = "false"; then
- cleanup_temp_files
+ if "$prompt" = true
+ then
+ printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
+ read ans || return 1
fi
- return 1
- fi
+ if base_present
+ then
+ present=true
+ else
+ present=false
+ fi
+
+ if ! run_merge_tool "$merge_tool" "$present"
+ then
+ echo "merge of $MERGED failed" 1>&2
+ mv -- "$BACKUP" "$MERGED"
+
+ if test "$merge_keep_temporaries" = "false"
+ then
+ cleanup_temp_files
+ fi
- if test "$merge_keep_backup" = "true"; then
- mv -- "$BACKUP" "$MERGED.orig"
- else
- rm -- "$BACKUP"
- fi
+ return 1
+ fi
- git add -- "$MERGED"
- cleanup_temp_files
- return 0
+ if test "$merge_keep_backup" = "true"
+ then
+ mv -- "$BACKUP" "$MERGED.orig"
+ else
+ rm -- "$BACKUP"
+ fi
+
+ git add -- "$MERGED"
+ cleanup_temp_files
+ return 0
}
show_tool_help () {
while test $# != 0
do
- case "$1" in
+ case "$1" in
--tool-help)
show_tool_help
;;
-t|--tool*)
- case "$#,$1" in
+ case "$#,$1" in
*,*=*)
- merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
- ;;
+ merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
+ ;;
1,*)
- usage ;;
+ usage ;;
*)
- merge_tool="$2"
- shift ;;
- esac
- ;;
+ merge_tool="$2"
+ shift ;;
+ esac
+ ;;
-y|--no-prompt)
- prompt=false
- ;;
+ prompt=false
+ ;;
--prompt)
- prompt=true
- ;;
+ prompt=true
+ ;;
--)
- shift
- break
- ;;
+ shift
+ break
+ ;;
-*)
- usage
- ;;
- *)
- break
- ;;
- esac
- shift
-done
-
-prompt_after_failed_merge() {
- while true; do
- printf "Continue merging other unresolved paths (y/n) ? "
- read ans || return 1
- case "$ans" in
-
- [yY]*)
- return 0
+ usage
;;
-
- [nN]*)
- return 1
+ *)
+ break
;;
esac
- done
+ shift
+done
+
+prompt_after_failed_merge () {
+ while true
+ do
+ printf "Continue merging other unresolved paths (y/n) ? "
+ read ans || return 1
+ case "$ans" in
+ [yY]*)
+ return 0
+ ;;
+ [nN]*)
+ return 1
+ ;;
+ esac
+ done
}
-if test -z "$merge_tool"; then
- merge_tool=$(get_merge_tool "$merge_tool") || exit
+if test -z "$merge_tool"
+then
+ merge_tool=$(get_merge_tool "$merge_tool") || exit
fi
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
rollup_status=0
files=
-if test $# -eq 0 ; then
- cd_to_toplevel
+if test $# -eq 0
+then
+ cd_to_toplevel
- if test -e "$GIT_DIR/MERGE_RR"
- then
- files=$(git rerere remaining)
- else
- files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u)
- fi
+ if test -e "$GIT_DIR/MERGE_RR"
+ then
+ files=$(git rerere remaining)
+ else
+ files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u)
+ fi
else
- files=$(git ls-files -u -- "$@" | sed -e 's/^[^ ]* //' | sort -u)
+ files=$(git ls-files -u -- "$@" | sed -e 's/^[^ ]* //' | sort -u)
fi
-if test -z "$files" ; then
- echo "No files need merging"
- exit 0
+if test -z "$files"
+then
+ echo "No files need merging"
+ exit 0
fi
printf "Merging:\n"
'
for i in $files
do
- if test $last_status -ne 0; then
- prompt_after_failed_merge || exit 1
- fi
- printf "\n"
- merge_file "$i"
- last_status=$?
- if test $last_status -ne 0; then
- rollup_status=1
- fi
+ if test $last_status -ne 0
+ then
+ prompt_after_failed_merge || exit 1
+ fi
+ printf "\n"
+ merge_file "$i"
+ last_status=$?
+ if test $last_status -ne 0
+ then
+ rollup_status=1
+ fi
done
exit $rollup_status
test -s "$todo" && return
comment_for_reflog finish &&
- shortonto=$(git rev-parse --short $onto) &&
newhead=$(git rev-parse HEAD) &&
case $head_name in
refs/*)
- message="$GIT_REFLOG_ACTION: $head_name onto $shortonto" &&
+ message="$GIT_REFLOG_ACTION: $head_name onto $onto" &&
git update-ref -m "$message" $head_name $newhead $orig_head &&
git symbolic-ref \
-m "$GIT_REFLOG_ACTION: returning to $head_name" \
my ($prompt, %arg) = @_;
my $valid_re = $arg{valid_re};
my $default = $arg{default};
+ my $confirm_only = $arg{confirm_only};
my $resp;
my $i = 0;
return defined $default ? $default : undef
if (!defined $valid_re or $resp =~ /$valid_re/) {
return $resp;
}
+ if ($confirm_only) {
+ my $yesno = $term->readline("Are you sure you want to use <$resp> [y/N]? ");
+ if (defined $yesno && $yesno =~ /y/i) {
+ return $resp;
+ }
+ }
}
return undef;
}
if (!defined $sender) {
$sender = $repoauthor || $repocommitter || '';
$sender = ask("Who should the emails appear to be from? [$sender] ",
- default => $sender);
+ default => $sender,
+ valid_re => qr/\@.*\./, confirm_only => 1);
print "Emails will be sent from: ", $sender, "\n";
$prompting++;
}
if (!@initial_to && !defined $to_cmd) {
- my $to = ask("Who should the emails be sent to? ");
+ my $to = ask("Who should the emails be sent to? ",
+ valid_re => qr/\@.*\./, confirm_only => 1);
push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
$prompting++;
}
if ($thread && !defined $initial_reply_to && $prompting) {
$initial_reply_to = ask(
- "Message-ID to be used as In-Reply-To for the first email? ");
+ "Message-ID to be used as In-Reply-To for the first email? ",
+ valid_re => qr/\@.*\./, confirm_only => 1);
}
if (defined $initial_reply_to) {
$initial_reply_to =~ s/^\s*<?//;
sub unquote_rfc2047 {
local ($_) = @_;
my $encoding;
- if (s/=\?([^?]+)\?q\?(.*)\?=/$2/g) {
+ s{=\?([^?]+)\?q\?(.*?)\?=}{
$encoding = $1;
- s/_/ /g;
- s/=([0-9A-F]{2})/chr(hex($1))/eg;
- }
+ my $e = $2;
+ $e =~ s/_/ /g;
+ $e =~ s/=([0-9A-F]{2})/chr(hex($1))/eg;
+ $e;
+ }eg;
return wantarray ? ($_, $encoding) : $_;
}
# you would cause "cd" to be taken to unexpected places. If you
# like CDPATH, define it for your interactive shell sessions without
# exporting it.
+# But we protect ourselves from such a user mistake nevertheless.
unset CDPATH
+# Similarly for IFS
+unset IFS
+
git_broken_path_fix () {
case ":$PATH:" in
*:$1:*) : ok ;;
else
# Merge conflict; keep the exit status from merge-recursive
status=$?
+ git rerere
if test -n "$INDEX_OPTION"
then
gettextln "Index was not unstashed." >&2
#
module_list()
{
- git ls-files --error-unmatch --stage -- "$@" |
+ (
+ git ls-files --error-unmatch --stage -- "$@" ||
+ echo "unmatched pathspec exists"
+ ) |
perl -e '
my %unmerged = ();
my ($null_sha1) = ("0" x 40);
+ my @out = ();
+ my $unmatched = 0;
while (<STDIN>) {
+ if (/^unmatched pathspec/) {
+ $unmatched = 1;
+ next;
+ }
chomp;
my ($mode, $sha1, $stage, $path) =
/^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
next unless $mode eq "160000";
if ($stage ne "0") {
if (!$unmerged{$path}++) {
- print "$mode $null_sha1 U\t$path\n";
+ push @out, "$mode $null_sha1 U\t$path\n";
}
next;
}
- print "$_\n";
+ push @out, "$_\n";
+ }
+ if ($unmatched) {
+ print "#unmatched\n";
+ } else {
+ print for (@out);
}
'
}
+die_if_unmatched ()
+{
+ if test "$1" = "#unmatched"
+ then
+ exit 1
+ fi
+}
+
#
# Map submodule path to submodule name
#
module_list |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
if test -e "$sm_path"/.git
then
say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
# Copy url setting when it is not set yet
err=
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
if test "$stage" = U
then
echo >&2 "Skipping unmerged submodule $sm_path"
die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
fi
- if test "$subsha1" != "$sha1"
+ if test "$subsha1" != "$sha1" -o -n "$force"
then
subforce=$force
# If we don't already have a -f flag and the submodule has never been checked out
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
displaypath="$prefix$sm_path"
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path")
url=$(git config -f .gitmodules --get submodule."$name".url)
use Git::SVN::Log;
use Git::SVN::Migration;
-use Git::SVN::Utils qw(fatal can_compress);
+use Git::SVN::Utils qw(
+ fatal
+ can_compress
+ canonicalize_path
+ canonicalize_url
+ join_paths
+ add_path_to_url
+ join_paths
+);
+
use Git qw(
git_cmd_try
command
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
$gs ||= Git::SVN->new;
my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
- $gs->prop_walk($gs->{path}, $r, sub {
+ $gs->prop_walk($gs->path, $r, sub {
my ($gs, $path, $props) = @_;
print STDOUT "\n# $path\n";
my $s = $props->{'svn:ignore'} or return;
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
$gs ||= Git::SVN->new;
my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
- $gs->prop_walk($gs->{path}, $r, sub {
+ $gs->prop_walk($gs->path, $r, sub {
my ($gs, $path, $props) = @_;
print STDOUT "\n# $path\n";
my $s = $props->{'svn:externals'} or return;
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
$gs ||= Git::SVN->new;
my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
- $gs->prop_walk($gs->{path}, $r, sub {
+ $gs->prop_walk($gs->path, $r, sub {
my ($gs, $path, $props) = @_;
# $path is of the form /path/to/dir/
$path = '.' . $path;
$gs->mkemptydirs($_revision);
}
-sub canonicalize_path {
- my ($path) = @_;
- my $dot_slash_added = 0;
- if (substr($path, 0, 1) ne "/") {
- $path = "./" . $path;
- $dot_slash_added = 1;
- }
- # File::Spec->canonpath doesn't collapse x/../y into y (for a
- # good reason), so let's do this manually.
- $path =~ s#/+#/#g;
- $path =~ s#/\.(?:/|$)#/#g;
- $path =~ s#/[^/]+/\.\.##g;
- $path =~ s#/$##g;
- $path =~ s#^\./## if $dot_slash_added;
- $path =~ s#^/##;
- $path =~ s#^\.$##;
- return $path;
-}
-
-sub canonicalize_url {
- my ($url) = @_;
- $url =~ s#^([^:]+://[^/]*/)(.*)$#$1 . canonicalize_path($2)#e;
- return $url;
-}
-
# get_svnprops(PATH)
# ------------------
# Helper for cmd_propget and cmd_proplist below.
$path = $cmd_dir_prefix . $path;
fatal("No such file or directory: $path") unless -e $path;
my $is_dir = -d $path ? 1 : 0;
- $path = $gs->{path} . '/' . $path;
+ $path = join_paths($gs->{path}, $path);
# canonicalize the path (otherwise libsvn will abort or fail to
# find the file)
fatal("Needed URL or usable git-svn --id in ",
"the command-line\n", $usage);
}
- $url = $gs->{url};
- $svn_path = $gs->{path};
+ $url = $gs->url;
+ $svn_path = $gs->path;
}
unless (defined $_revision) {
fatal("-r|--revision is a required argument\n", $usage);
}
}
-sub escape_uri_only {
- my ($uri) = @_;
- my @tmp;
- foreach (split m{/}, $uri) {
- s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
- push @tmp, $_;
- }
- join('/', @tmp);
-}
-
-sub escape_url {
- my ($url) = @_;
- if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
- my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
- $url = "$scheme://$domain$uri";
- }
- $url;
-}
sub cmd_info {
my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
# canonicalize_path() will return "" to make libsvn 1.5.x happy,
$path = "." if $path eq "";
- my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
+ my $full_url = canonicalize_url( add_path_to_url( $url, $fullpath ) );
if ($_url) {
- print escape_url($full_url), "\n";
+ print "$full_url\n";
return;
}
my $result = "Path: $path\n";
$result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
- $result .= "URL: " . escape_url($full_url) . "\n";
+ $result .= "URL: $full_url\n";
eval {
my $repos_root = $gs->repos_root;
Git::SVN::remove_username($repos_root);
- $result .= "Repository Root: " . escape_url($repos_root) . "\n";
+ $result .= "Repository Root: " . canonicalize_url($repos_root) . "\n";
};
if ($@) {
$result .= "Repository Root: (offline)\n";
sub complete_svn_url {
my ($url, $path) = @_;
- $path =~ s#/+$##;
+ $path = canonicalize_path($path);
+
+ # If the path is not a URL...
if ($path !~ m#^[a-z\+]+://#) {
if (!defined $url || $url !~ m#^[a-z\+]+://#) {
fatal("E: '$path' is not a complete URL ",
print STDERR "W: $switch not specified\n";
return;
}
- $repo_path =~ s#/+$##;
+ $repo_path = canonicalize_path($repo_path);
if ($repo_path =~ m#^[a-z\+]+://#) {
$ra = Git::SVN::Ra->new($repo_path);
$repo_path = '';
"and a separate URL is not specified");
}
}
- my $url = $ra->{url};
+ my $url = $ra->url;
my $gs = Git::SVN->init($url, undef, undef, undef, 1);
my $k = "svn-remote.$gs->{repo_id}.url";
my $orig_url = eval { command_oneline(qw/config --get/, $k) };
- if ($orig_url && ($orig_url ne $gs->{url})) {
+ if ($orig_url && ($orig_url ne $gs->url)) {
die "$k already set: $orig_url\n",
- "wanted to set to: $gs->{url}\n";
+ "wanted to set to: $gs->url\n";
}
- command_oneline('config', $k, $gs->{url}) unless $orig_url;
- my $remote_path = "$gs->{path}/$repo_path";
+ command_oneline('config', $k, $gs->url) unless $orig_url;
+
+ my $remote_path = join_paths( $gs->path, $repo_path );
$remote_path =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
- $remote_path =~ s#/+#/#g;
$remote_path =~ s#^/##g;
$remote_path .= "/*" if $remote_path !~ /\*/;
my ($n) = ($switch =~ /^--(\w+)/);
# to build the base URL ourselves:
our $path_info = decode_utf8($ENV{"PATH_INFO"});
if ($path_info) {
+ # $path_info has already been URL-decoded by the web server, but
+ # $my_url and $my_uri have not. URL-decode them so we can properly
+ # strip $path_info.
+ $my_url = unescape($my_url);
+ $my_uri = unescape($my_uri);
if ($my_url =~ s,\Q$path_info\E$,, &&
$my_uri =~ s,\Q$path_info\E$,, &&
defined $ENV{'SCRIPT_NAME'}) {
GREP_NODE_OR
};
+enum grep_pattern_type {
+ GREP_PATTERN_TYPE_UNSPECIFIED = 0,
+ GREP_PATTERN_TYPE_BRE,
+ GREP_PATTERN_TYPE_ERE,
+ GREP_PATTERN_TYPE_FIXED,
+ GREP_PATTERN_TYPE_PCRE
+};
+
struct grep_expr {
enum grep_expr_node node;
unsigned hit;
int max_depth;
int funcname;
int funcbody;
+ int extended_regexp_option;
+ int pattern_type_option;
char color_context[COLOR_MAXLEN];
char color_filename[COLOR_MAXLEN];
char color_function[COLOR_MAXLEN];
ret = HTTP_REAUTH;
}
} else {
+#if LIBCURL_VERSION_NUM >= 0x070c00
if (!curl_errorstr[0])
strlcpy(curl_errorstr,
curl_easy_strerror(results.curl_result),
sizeof(curl_errorstr));
+#endif
ret = HTTP_ERROR;
}
} else {
opts.rename_score = o->rename_score;
opts.show_rename_progress = o->show_rename_progress;
opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opts) < 0)
- die(_("diff setup failed"));
+ diff_setup_done(&opts);
diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
diffcore_std(&opts);
if (opts.needed_rename_limit > o->needed_rename_limit)
return newpath;
}
-static void flush_buffer(int fd, const char *buf, unsigned long size)
-{
- while (size > 0) {
- long ret = write_in_full(fd, buf, size);
- if (ret < 0) {
- /* Ignore epipe */
- if (errno == EPIPE)
- break;
- die_errno("merge-recursive");
- } else if (!ret) {
- die(_("merge-recursive: disk full?"));
- }
- size -= ret;
- buf += ret;
- }
-}
-
static int dir_in_way(const char *path, int check_working_copy)
{
int pos, pathlen = strlen(path);
fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
if (fd < 0)
die_errno(_("failed to open '%s'"), path);
- flush_buffer(fd, buf, size);
+ write_in_full(fd, buf, size);
close(fd);
} else if (S_ISLNK(mode)) {
char *lnk = xmemdupz(buf, size);
--- /dev/null
+diff_cmd () {
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+}
+
+merge_cmd () {
+ touch "$BACKUP"
+ if $base_present
+ then
+ "$merge_tool_path" -MF="$LOCAL" -TF="$REMOTE" -BF="$BASE" \
+ -RF="$MERGED"
+ else
+ "$merge_tool_path" -MF="$LOCAL" -TF="$REMOTE" \
+ -RF="$MERGED"
+ fi
+ check_unchanged
+}
+
+translate_merge_tool_path() {
+ if merge_mode
+ then
+ echo CodeMerge
+ else
+ echo CodeCompare
+ fi
+}
diff_setup(&opt);
DIFF_OPT_SET(&opt, RECURSIVE);
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&opt);
diff_tree_sha1(base, remote, "", &opt);
diffcore_std(&opt);
diff_setup(&opt);
DIFF_OPT_SET(&opt, RECURSIVE);
opt.output_format = DIFF_FORMAT_NO_OUTPUT;
- if (diff_setup_done(&opt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&opt);
diff_tree_sha1(base, local, "", &opt);
diffcore_std(&opt);
memset(ids, 0, sizeof(*ids));
diff_setup(&ids->diffopts);
DIFF_OPT_SET(&ids->diffopts, RECURSIVE);
- if (diff_setup_done(&ids->diffopts) < 0)
- return error("diff_setup_done failed");
+ diff_setup_done(&ids->diffopts);
return 0;
}
command_output_pipe
command_close_pipe
);
-use Git::SVN::Utils qw(fatal can_compress);
+use Git::SVN::Utils qw(
+ fatal
+ can_compress
+ join_paths
+ canonicalize_path
+ canonicalize_url
+ add_path_to_url
+);
my $can_use_yaml;
BEGIN {
} elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
$r->{$1}->{svm} = {};
} elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
- $r->{$1}->{url} = $2;
+ $r->{$1}->{url} = canonicalize_url($2);
} elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
- $r->{$1}->{pushurl} = $2;
+ $r->{$1}->{pushurl} = canonicalize_url($2);
} elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) {
$r->{$1}->{ignore_refs_regex} = $2;
} elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
sub init_remote_config {
my ($self, $url, $no_write) = @_;
- $url =~ s!/+$!!; # strip trailing slash
+ $url = canonicalize_url($url);
my $r = read_all_remotes();
my $existing = find_existing_remote($url, $r);
if ($existing) {
print STDERR "Using higher level of URL: ",
"$url => $min_url\n";
}
- my $old_path = $self->{path};
- $self->{path} = $url;
- $self->{path} =~ s!^\Q$min_url\E(/|$)!!;
- if (length $old_path) {
- $self->{path} .= "/$old_path";
- }
+ my $old_path = $self->path;
+ $url =~ s!^\Q$min_url\E(/|$)!!;
+ $url = join_paths($url, $old_path);
+ $self->path($url);
$url = $min_url;
}
}
unless ($no_write) {
command_noisy('config',
"svn-remote.$self->{repo_id}.url", $url);
- $self->{path} =~ s{^/}{};
- $self->{path} =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
+ my $path = $self->path;
+ $path =~ s{^/}{};
+ $path =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
+ $self->path($path);
command_noisy('config', '--add',
"svn-remote.$self->{repo_id}.fetch",
- "$self->{path}:".$self->refname);
+ $self->path.":".$self->refname);
}
- $self->{url} = $url;
+ $self->url($url);
}
sub find_by_url { # repos_root and, path are optional
my ($class, $full_url, $repos_root, $path) = @_;
+ $full_url = canonicalize_url($full_url);
+
return undef unless defined $full_url;
remove_username($full_url);
remove_username($repos_root) if defined $repos_root;
}
$p =~ s#^\Q$z\E(?:/|$)#$prefix# or next;
}
+
+ # remote fetch paths are not URI escaped. Decode ours
+ # so they match
+ $p = uri_decode($p);
+
foreach my $f (keys %$fetch) {
next if $f ne $p;
return Git::SVN->new($fetch->{$f}, $repo_id, $f);
}
}
my $self = _new($class, $repo_id, $ref_id, $path);
- if (!defined $self->{path} || !length $self->{path}) {
+ if (!defined $self->path || !length $self->path) {
my $fetch = command_oneline('config', '--get',
"svn-remote.$repo_id.fetch",
":$ref_id\$") or
die "Failed to read \"svn-remote.$repo_id.fetch\" ",
"\":$ref_id\$\" in config\n";
- ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
+ my($path) = split(/\s*:\s*/, $fetch);
+ $self->path($path);
}
- $self->{path} =~ s{/+}{/}g;
- $self->{path} =~ s{\A/}{};
- $self->{path} =~ s{/\z}{};
- $self->{url} = command_oneline('config', '--get',
- "svn-remote.$repo_id.url") or
+ {
+ my $path = $self->path;
+ $path =~ s{\A/}{};
+ $path =~ s{/\z}{};
+ $self->path($path);
+ }
+ my $url = command_oneline('config', '--get',
+ "svn-remote.$repo_id.url") or
die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
+ $self->url($url);
$self->{pushurl} = eval { command_oneline('config', '--get',
"svn-remote.$repo_id.pushurl") };
$self->rebuild;
# username is of no interest
$src =~ s{(^[a-z\+]*://)[^/@]*@}{$1};
- my $replace = $ra->{url};
- $replace .= "/$path" if length $path;
+ my $replace = add_path_to_url($ra->url, $path);
my $section = "svn-remote.$self->{repo_id}";
tmp_config("$section.svm-source", $src);
}
my $r = $ra->get_latest_revnum;
- my $path = $self->{path};
+ my $path = $self->path;
my %tried;
while (length $path) {
- unless ($tried{"$self->{url}/$path"}) {
+ my $try = add_path_to_url($self->url, $path);
+ unless ($tried{$try}) {
return $ra if $self->read_svm_props($ra, $path, $r);
- $tried{"$self->{url}/$path"} = 1;
+ $tried{$try} = 1;
}
$path =~ s#/?[^/]+$##;
}
die "Path: '$path' should be ''\n" if $path ne '';
return $ra if $self->read_svm_props($ra, $path, $r);
- $tried{"$self->{url}/$path"} = 1;
+ $tried{ add_path_to_url($self->url, $path) } = 1;
- if ($ra->{repos_root} eq $self->{url}) {
+ if ($ra->{repos_root} eq $self->url) {
die @err, (map { " $_\n" } keys %tried), "\n";
}
$path = $ra->{svn_path};
$ra = Git::SVN::Ra->new($ra->{repos_root});
while (length $path) {
- unless ($tried{"$ra->{url}/$path"}) {
+ my $try = add_path_to_url($ra->url, $path);
+ unless ($tried{$try}) {
$ok = $self->read_svm_props($ra, $path, $r);
last if $ok;
- $tried{"$ra->{url}/$path"} = 1;
+ $tried{$try} = 1;
}
$path =~ s#/?[^/]+$##;
}
die "Path: '$path' should be ''\n" if $path ne '';
$ok ||= $self->read_svm_props($ra, $path, $r);
- $tried{"$ra->{url}/$path"} = 1;
+ $tried{ add_path_to_url($ra->url, $path) } = 1;
if (!$ok) {
die @err, (map { " $_\n" } keys %tried), "\n";
}
- Git::SVN::Ra->new($self->{url});
+ Git::SVN::Ra->new($self->url);
}
sub svnsync {
if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/i) {
$self->{ra_uuid} = $uuid;
} else {
- die "ra_uuid called without URL\n" unless $self->{url};
+ die "ra_uuid called without URL\n" unless $self->url;
$self->{ra_uuid} = $self->ra->get_uuid;
tmp_config('--add', $key, $self->{ra_uuid});
}
sub ra {
my ($self) = shift;
- my $ra = Git::SVN::Ra->new($self->{url});
+ my $ra = Git::SVN::Ra->new($self->url);
$self->_set_repos_root($ra->{repos_root});
if ($self->use_svm_props && !$self->{svm}) {
if ($self->no_metadata) {
$path =~ s#^/*#/#g;
my $p = $path;
# Strip the irrelevant part of the path.
- $p =~ s#^/+\Q$self->{path}\E(/|$)#/#;
+ $p =~ s#^/+\Q@{[$self->path]}\E(/|$)#/#;
# Ensure the path is terminated by a `/'.
$p =~ s#/*$#/#;
foreach (sort keys %$dirent) {
next if $dirent->{$_}->{kind} != $SVN::Node::dir;
- $self->prop_walk($self->{path} . $p . $_, $rev, $sub);
+ $self->prop_walk($self->path . $p . $_, $rev, $sub);
}
}
sub metadata_url {
my ($self) = @_;
- ($self->rewrite_root || $self->{url}) .
- (length $self->{path} ? '/' . $self->{path} : '');
+ my $url = $self->rewrite_root || $self->url;
+ return canonicalize_url( add_path_to_url( $url, $self->path ) );
}
sub full_url {
my ($self) = @_;
- $self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
+ return canonicalize_url( add_path_to_url( $self->url, $self->path ) );
}
sub full_pushurl {
my ($self) = @_;
if ($self->{pushurl}) {
- return $self->{pushurl} . (length $self->{path} ? '/' .
- $self->{path} : '');
+ return canonicalize_url( add_path_to_url( $self->{pushurl}, $self->path ) );
} else {
return $self->full_url;
}
sub match_paths {
my ($self, $paths, $r) = @_;
- return 1 if $self->{path} eq '';
- if (my $path = $paths->{"/$self->{path}"}) {
+ return 1 if $self->path eq '';
+ if (my $path = $paths->{"/".$self->path}) {
return ($path->{action} eq 'D') ? 0 : 1;
}
- $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
+ $self->{path_regex} ||= qr{^/\Q@{[$self->path]}\E/};
if (grep /$self->{path_regex}/, keys %$paths) {
return 1;
}
my $c = '';
- foreach (split m#/#, $self->{path}) {
+ foreach (split m#/#, $self->path) {
$c .= "/$_";
next unless ($paths->{$c} &&
($paths->{$c}->{action} =~ /^[AR]$/));
- if ($self->ra->check_path($self->{path}, $r) ==
+ if ($self->ra->check_path($self->path, $r) ==
$SVN::Node::dir) {
return 1;
}
unless (defined $paths) {
my $err_handler = $SVN::Error::handler;
$SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs;
- $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1,
+ $self->ra->get_log([$self->path], $rev, $rev, 0, 1, 1,
sub { $paths = $_[0] });
$SVN::Error::handler = $err_handler;
}
return undef unless defined $paths;
# look for a parent from another branch:
- my @b_path_components = split m#/#, $self->{path};
+ my @b_path_components = split m#/#, $self->path;
my @a_path_components;
my $i;
while (@b_path_components) {
}
my $r = $i->{copyfrom_rev};
my $repos_root = $self->ra->{repos_root};
- my $url = $self->ra->{url};
- my $new_url = $url . $branch_from;
+ my $url = $self->ra->url;
+ my $new_url = canonicalize_url( add_path_to_url( $url, $branch_from ) );
print STDERR "Found possible branch point: ",
"$new_url => ", $self->full_url, ", $r\n"
unless $::_q > 1;
($base, $head) = parse_revision_argument(0, $r);
} else {
if ($r0 < $r) {
- $gs->ra->get_log([$gs->{path}], $r0 + 1, $r, 1,
+ $gs->ra->get_log([$gs->path], $r0 + 1, $r, 1,
0, 1, sub { $base = $_[1] - 1 });
}
}
# at the moment), so we can't rely on it
$self->{last_rev} = $r0;
$self->{last_commit} = $parent;
- $ed = Git::SVN::Fetcher->new($self, $gs->{path});
+ $ed = Git::SVN::Fetcher->new($self, $gs->path);
$gs->ra->gs_do_switch($r0, $rev, $gs,
$self->full_url, $ed)
or die "SVN connection failed somewhere...\n";
close $fh;
}
- my $strip = qr/\A\Q$self->{path}\E(?:\/|$)/;
+ my $strip = qr/\A\Q@{[$self->path]}\E(?:\/|$)/;
foreach my $d (sort keys %empty_dirs) {
$d = uri_decode($d);
$d =~ s/$strip//;
for my $ticket ( @tickets ) {
my ($uuid, $path, $rev) = split /:/, $ticket;
if ( $uuid eq $self->ra_uuid ) {
- my $url = $self->{url};
- my $repos_root = $url;
+ my $repos_root = $self->url;
my $branch_from = $path;
$branch_from =~ s{^/}{};
- my $gs = $self->other_gs($repos_root."/".$branch_from,
- $url,
+ my $gs = $self->other_gs(add_path_to_url( $repos_root, $branch_from ),
+ $repos_root,
$branch_from,
$rev,
$self->{ref_id});
# are now marked as merge, we can add the tip as a parent.
my @merges = split "\n", $mergeinfo;
my @merge_tips;
- my $url = $self->{url};
+ my $url = $self->url;
my $uuid = $self->ra_uuid;
my %ranges;
for my $merge ( @merges ) {
$email ||= "$author\@$uuid";
$commit_email ||= "$author\@$uuid";
} elsif ($self->use_svnsync_props) {
- my $full_url = $self->svnsync->{url};
- $full_url .= "/$self->{path}" if length $self->{path};
+ my $full_url = canonicalize_url(
+ add_path_to_url( $self->svnsync->{url}, $self->path )
+ );
remove_username($full_url);
my $uuid = $self->svnsync->{uuid};
$log_entry{metadata} = "$full_url\@$rev $uuid";
tree_b => $tree,
editor_cb => sub {
$self->set_tree_cb($log_entry, $tree, @_) },
- svn_path => $self->{path} );
+ svn_path => $self->path );
if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\nr$self->{last_rev} = $tree\n";
}
$_[3] = $path = '' unless (defined $path);
mkpath([$dir]);
- bless {
+ my $obj = bless {
ref_id => $ref_id, dir => $dir, index => "$dir/index",
- path => $path, config => "$ENV{GIT_DIR}/svn/config",
+ config => "$ENV{GIT_DIR}/svn/config",
map_root => "$dir/.rev_map", repo_id => $repo_id }, $class;
+
+ # Ensure it gets canonicalized
+ $obj->path($path);
+
+ return $obj;
+}
+
+sub path {
+ my $self = shift;
+
+ if (@_) {
+ my $path = shift;
+ $self->{path} = canonicalize_path($path);
+ return;
+ }
+
+ return $self->{path};
+}
+
+sub url {
+ my $self = shift;
+
+ if (@_) {
+ my $url = shift;
+ $self->{url} = canonicalize_url($url);
+ return;
+ }
+
+ return $self->{url};
}
# for read-only access of old .rev_db formats
chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
local $/ = "\0";
- my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};
+ my $pfx = defined($switch_path) ? $switch_path : $git_svn->path;
$pfx .= '/' if length($pfx);
while (<$ls>) {
chomp;
my $ra = Git::SVN::Ra->new($url);
# skip existing cases where we already connect to the root
- if (($ra->{url} eq $ra->{repos_root}) ||
+ if (($ra->url eq $ra->{repos_root}) ||
($ra->{repos_root} eq $repo_id)) {
- $root_repos->{$ra->{url}} = $repo_id;
+ $root_repos->{$ra->url} = $repo_id;
next;
}
my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
- my $root_path = $ra->{url};
+ my $root_path = $ra->url;
$root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
foreach my $path (keys %$fetch) {
my $ref_id = $fetch->{$path};
use strict;
use warnings;
use SVN::Client;
+use Git::SVN::Utils qw(
+ canonicalize_url
+ canonicalize_path
+ add_path_to_url
+);
+
use SVN::Ra;
BEGIN {
@ISA = qw(SVN::Ra);
\@rv;
}
-sub escape_uri_only {
- my ($uri) = @_;
- my @tmp;
- foreach (split m{/}, $uri) {
- s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
- push @tmp, $_;
- }
- join('/', @tmp);
-}
-
-sub escape_url {
- my ($url) = @_;
- if ($url =~ m#^(https?)://([^/]+)(.*)$#) {
- my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
- $url = "$scheme://$domain$uri";
- }
- $url;
-}
sub new {
my ($class, $url) = @_;
- $url =~ s!/+$!!;
- return $RA if ($RA && $RA->{url} eq $url);
+ $url = canonicalize_url($url);
+ return $RA if ($RA && $RA->url eq $url);
::_req_svn();
$Git::SVN::Prompt::_no_auth_cache = 1;
}
} # no warnings 'once'
- my $self = SVN::Ra->new(url => escape_url($url), auth => $baton,
+
+ my $self = SVN::Ra->new(url => $url, auth => $baton,
config => $config,
pool => SVN::Pool->new,
auth_provider_callbacks => $callbacks);
- $self->{url} = $url;
+ $RA = bless $self, $class;
+
+ # Make sure its canonicalized
+ $self->url($url);
$self->{svn_path} = $url;
$self->{repos_root} = $self->get_repos_root;
$self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##;
$self->{cache} = { check_path => { r => 0, data => {} },
get_dir => { r => 0, data => {} } };
- $RA = bless $self, $class;
+
+ return $RA;
+}
+
+sub url {
+ my $self = shift;
+
+ if (@_) {
+ my $url = shift;
+ $self->{url} = canonicalize_url($url);
+ return;
+ }
+
+ return $self->{url};
}
sub check_path {
qw/copyfrom_path copyfrom_rev action/;
if ($s{'copyfrom_path'}) {
$s{'copyfrom_path'} =~ s/$prefix_regex//;
+ $s{'copyfrom_path'} = canonicalize_path($s{'copyfrom_path'});
}
$_[0]{$p} = \%s;
}
sub gs_do_update {
my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
my $new = ($rev_a == $rev_b);
- my $path = $gs->{path};
+ my $path = $gs->path;
if ($new && -e $gs->{index}) {
unlink $gs->{index} or die
# svn_ra_reparent didn't work before 1.4)
sub gs_do_switch {
my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;
- my $path = $gs->{path};
+ my $path = $gs->path;
my $pool = SVN::Pool->new;
- my $full_url = $self->{url};
- my $old_url = $full_url;
- $full_url .= '/' . $path if length $path;
+ my $old_url = $self->url;
+ my $full_url = add_path_to_url( $self->url, $path );
my ($ra, $reparented);
if ($old_url =~ m#^svn(\+ssh)?://# ||
($full_url =~ m#^https?://# &&
- escape_url($full_url) ne $full_url)) {
+ canonicalize_url($full_url) ne $full_url)) {
$_[0] = undef;
$self = undef;
$RA = undef;
$ra = Git::SVN::Ra->new($full_url);
$ra_invalid = 1;
} elsif ($old_url ne $full_url) {
- SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
- $self->{url} = $full_url;
+ SVN::_Ra::svn_ra_reparent(
+ $self->{session},
+ canonicalize_url($full_url),
+ $pool
+ );
+ $self->url($full_url);
$reparented = 1;
}
$ra ||= $self;
- $url_b = escape_url($url_b);
+ $url_b = canonicalize_url($url_b);
my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
$reporter->set_path('', $rev_a, 0, @lock, $pool);
if ($reparented) {
SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);
- $self->{url} = $old_url;
+ $self->url($old_url);
}
$pool->clear;
my $common_max = scalar @$gsv;
foreach my $gs (@$gsv) {
- my @tmp = split m#/#, $gs->{path};
+ my @tmp = split m#/#, $gs->path;
my $p = '';
foreach (@tmp) {
$p .= length($p) ? "/$_" : $_;
my $inc = $_log_window_size;
my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
my $longest_path = longest_common_path($gsv, $globs);
- my $ra_url = $self->{url};
+ my $ra_url = $self->url;
my $find_trailing_edge;
while (1) {
my %revs;
($self->check_path($p, $r) !=
$SVN::Node::dir));
next unless $p =~ /$g->{path}->{regex}/;
- $exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
+ $exists->{$p} = Git::SVN->init($self->url, $p, undef,
$g->{ref}->full_path($de), 1);
}
}
next if ($self->check_path($pathname, $r) !=
$SVN::Node::dir);
$exists->{$pathname} = Git::SVN->init(
- $self->{url}, $pathname, undef,
+ $self->url, $pathname, undef,
$g->{ref}->full_path($p), 1);
}
my $c = '';
sub minimize_url {
my ($self) = @_;
- return $self->{url} if ($self->{url} eq $self->{repos_root});
+ return $self->url if ($self->url eq $self->{repos_root});
my $url = $self->{repos_root};
my @components = split(m!/!, $self->{svn_path});
my $c = '';
do {
- $url .= "/$c" if length $c;
+ $url = add_path_to_url($url, $c);
eval {
my $ra = (ref $self)->new($url);
my $latest = $ra->get_latest_revnum;
$ra->get_log("", $latest, 0, 1, 0, 1, sub {});
};
} while ($@ && ($c = shift @components));
- $url;
+
+ return canonicalize_url($url);
}
sub can_do_switch {
unless (defined $can_do_switch) {
my $pool = SVN::Pool->new;
my $rep = eval {
- $self->do_switch(1, '', 0, $self->{url},
+ $self->do_switch(1, '', 0, $self->url,
SVN::Delta::Editor->new, $pool);
};
if ($@) {
use strict;
use warnings;
+use SVN::Core;
+
use base qw(Exporter);
-our @EXPORT_OK = qw(fatal can_compress);
+our @EXPORT_OK = qw(
+ fatal
+ can_compress
+ canonicalize_path
+ canonicalize_url
+ join_paths
+ add_path_to_url
+);
=head1 NAME
}
+=head3 canonicalize_path
+
+ my $canoncalized_path = canonicalize_path($path);
+
+Converts $path into a canonical form which is safe to pass to the SVN
+API as a file path.
+
+=cut
+
+# Turn foo/../bar into bar
+sub _collapse_dotdot {
+ my $path = shift;
+
+ 1 while $path =~ s{/[^/]+/+\.\.}{};
+ 1 while $path =~ s{[^/]+/+\.\./}{};
+ 1 while $path =~ s{[^/]+/+\.\.}{};
+
+ return $path;
+}
+
+
+sub canonicalize_path {
+ my $path = shift;
+ my $rv;
+
+ # The 1.7 way to do it
+ if ( defined &SVN::_Core::svn_dirent_canonicalize ) {
+ $path = _collapse_dotdot($path);
+ $rv = SVN::_Core::svn_dirent_canonicalize($path);
+ }
+ # The 1.6 way to do it
+ # This can return undef on subversion-perl-1.4.2-2.el5 (CentOS 5.2)
+ elsif ( defined &SVN::_Core::svn_path_canonicalize ) {
+ $path = _collapse_dotdot($path);
+ $rv = SVN::_Core::svn_path_canonicalize($path);
+ }
+
+ return $rv if defined $rv;
+
+ # No SVN API canonicalization is available, or the SVN API
+ # didn't return a successful result, do it ourselves
+ return _canonicalize_path_ourselves($path);
+}
+
+
+sub _canonicalize_path_ourselves {
+ my ($path) = @_;
+ my $dot_slash_added = 0;
+ if (substr($path, 0, 1) ne "/") {
+ $path = "./" . $path;
+ $dot_slash_added = 1;
+ }
+ $path =~ s#/+#/#g;
+ $path =~ s#/\.(?:/|$)#/#g;
+ $path = _collapse_dotdot($path);
+ $path =~ s#/$##g;
+ $path =~ s#^\./## if $dot_slash_added;
+ $path =~ s#^/##;
+ $path =~ s#^\.$##;
+ return $path;
+}
+
+
+=head3 canonicalize_url
+
+ my $canonicalized_url = canonicalize_url($url);
+
+Converts $url into a canonical form which is safe to pass to the SVN
+API as a URL.
+
+=cut
+
+sub canonicalize_url {
+ my $url = shift;
+
+ # The 1.7 way to do it
+ if ( defined &SVN::_Core::svn_uri_canonicalize ) {
+ return SVN::_Core::svn_uri_canonicalize($url);
+ }
+ # There wasn't a 1.6 way to do it, so we do it ourself.
+ else {
+ return _canonicalize_url_ourselves($url);
+ }
+}
+
+
+sub _canonicalize_url_path {
+ my ($uri_path) = @_;
+
+ my @parts;
+ foreach my $part (split m{/+}, $uri_path) {
+ $part =~ s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
+ push @parts, $part;
+ }
+
+ return join('/', @parts);
+}
+
+sub _canonicalize_url_ourselves {
+ my ($url) = @_;
+ if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
+ my ($scheme, $domain, $uri) = ($1, $2, _canonicalize_url_path(canonicalize_path($3)));
+ $url = "$scheme://$domain$uri";
+ }
+ $url;
+}
+
+
+=head3 join_paths
+
+ my $new_path = join_paths(@paths);
+
+Appends @paths together into a single path. Any empty paths are ignored.
+
+=cut
+
+sub join_paths {
+ my @paths = @_;
+
+ @paths = grep { defined $_ && length $_ } @paths;
+
+ return '' unless @paths;
+ return $paths[0] if @paths == 1;
+
+ my $new_path = shift @paths;
+ $new_path =~ s{/+$}{};
+
+ my $last_path = pop @paths;
+ $last_path =~ s{^/+}{};
+
+ for my $path (@paths) {
+ $path =~ s{^/+}{};
+ $path =~ s{/+$}{};
+ $new_path .= "/$path";
+ }
+
+ return $new_path .= "/$last_path";
+}
+
+
+=head3 add_path_to_url
+
+ my $new_url = add_path_to_url($url, $path);
+
+Appends $path onto the $url. If $path is empty, $url is returned unchanged.
+
+=cut
+
+sub add_path_to_url {
+ my($url, $path) = @_;
+
+ return $url if !defined $path or !length $path;
+
+ # Strip trailing and leading slashes so we don't
+ # wind up with http://x.com///path
+ $url =~ s{/+$}{};
+ $path =~ s{^/+}{};
+
+ # If a path has a % in it, URI escape it so it's not
+ # mistaken for a URI escape later.
+ $path =~ s{%}{%25}g;
+
+ return join '/', $url, $path;
+}
+
1;
"Project-Id-Version: git 1.7.12\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
"POT-Creation-Date: 2012-08-06 23:47+0800\n"
-"PO-Revision-Date: 2012-08-09 06:36+0100\n"
+"PO-Revision-Date: 2012-08-14 09:58+0100\n"
"Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"Language: sv\n"
#, c-format
msgid " (use \"git %s <file>...\" to include in what will be committed)"
msgstr ""
-" (använd \"git %s <fil>...\" för att ta med i vad som skall checkas in)"
+" (använd \"git %s <fil>...\" för att ta med i det som skall checkas in)"
#: wt-status.c:244
msgid "bug"
#: wt-status.c:1033
msgid " (use \"git add\" to track)"
-msgstr " (använd \"git add\" för att spåra)"
+msgstr " (spåra med \"git add\")"
#: wt-status.c:1035 wt-status.c:1038 wt-status.c:1041
#, c-format
#: wt-status.c:1036
msgid " (create/copy files and use \"git add\" to track)"
-msgstr " (skapa/kopiera filer och använd \"git add\" för att spåra)"
+msgstr " (skapa/kopiera filer och spåra med \"git add\")"
#: wt-status.c:1039
msgid " (use -u to show untracked files)"
#: builtin/index-pack.c:986
msgid "Receiving objects"
-msgstr "Tar bort objeckt"
+msgstr "Tar bort objekt"
#: builtin/index-pack.c:986
msgid "Indexing objects"
#: common-cmds.h:28
msgid "Create, list, delete or verify a tag object signed with GPG"
-msgstr "Skapa, visa, ta bort eller verifiera ett taggobjekt signerat med GPG"
+msgstr "Skapa, visa, ta bort eller verifiera GPG-signerat taggobjekt"
#: git-am.sh:50
msgid "You need to set your committer info first"
size_t mmap_size;
struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
- errno = EBUSY;
if (istate->initialized)
return istate->cache_nr;
- errno = ENOENT;
istate->timestamp.sec = 0;
istate->timestamp.nsec = 0;
fd = open(path, O_RDONLY);
if (fstat(fd, &st))
die_errno("cannot stat the open index");
- errno = EINVAL;
mmap_size = xsize_t(st.st_size);
if (mmap_size < sizeof(struct cache_header) + 20)
die("index file smaller than expected");
mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
- close(fd);
if (mmap == MAP_FAILED)
die_errno("unable to map index file");
+ close(fd);
hdr = mmap;
if (verify_hdr(hdr, mmap_size) < 0)
unmap:
munmap(mmap, mmap_size);
- errno = EINVAL;
die("index file corrupt");
}
continue;
if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
ce_smudge_racily_clean_entry(ce);
+ if (is_null_sha1(ce->sha1))
+ return error("cache entry has null sha1: %s", ce->name);
if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
return -1;
}
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
+ int sha1_valid,
const char *fullpath, unsigned dirty_submodule)
{
int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
+ int old_sha1_valid, int new_sha1_valid,
const char *fullpath,
unsigned old_dirty_submodule, unsigned new_dirty_submodule)
{
if (revs->combine_merges)
revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
- if (diff_setup_done(&revs->diffopt) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&revs->diffopt);
compile_grep_patterns(&revs->grep_filter);
*/
if (errno == EACCES && !strchr(file, '/'))
errno = exists_in_PATH(file) ? EACCES : ENOENT;
+ else if (errno == ENOTDIR && !strchr(file, '/'))
+ errno = ENOENT;
return -1;
}
if (allow_empty)
argv_array_push(&array, "--allow-empty");
+ if (opts->allow_empty_message)
+ argv_array_push(&array, "--allow-empty-message");
+
rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
argv_array_clear(&array);
return rc;
int allow_ff;
int allow_rerere_auto;
int allow_empty;
+ int allow_empty_message;
int keep_redundant_commits;
int mainline;
{
if (!diagnose_misspelt_rev)
die("%s: no such path in the working tree.\n"
- "Use '-- <path>...' to specify paths that do not exist locally.",
+ "Use 'git <command> -- <path>...' to specify paths that do not exist locally.",
arg);
/*
* Saying "'(icase)foo' does not exist in the index" when the
/* ... or fall back the most general message. */
die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
- "Use '--' to separate paths from revisions", arg);
+ "Use '--' to separate paths from revisions, like this:\n"
+ "'git <command> [<revision>...] -- [<file>...]'", arg);
}
if (!check_filename(prefix, arg))
return;
die("ambiguous argument '%s': both revision and filename\n"
- "Use '--' to separate filenames from revisions", arg);
+ "Use '--' to separate paths from revisions, like this:\n"
+ "'git <command> [<revision>...] -- [<file>...]'", arg);
}
/*
}
}
+static unsigned int get_max_fd_limit(void)
+{
+#ifdef RLIMIT_NOFILE
+ struct rlimit lim;
+
+ if (getrlimit(RLIMIT_NOFILE, &lim))
+ die_errno("cannot get RLIMIT_NOFILE");
+
+ return lim.rlim_cur;
+#elif defined(_SC_OPEN_MAX)
+ return sysconf(_SC_OPEN_MAX);
+#elif defined(OPEN_MAX)
+ return OPEN_MAX;
+#else
+ return 1; /* see the caller ;-) */
+#endif
+}
+
/*
* Do not call this directly as this leaks p->pack_fd on error return;
* call open_packed_git() instead.
return error("packfile %s index unavailable", p->pack_name);
if (!pack_max_fds) {
- struct rlimit lim;
- unsigned int max_fds;
-
- if (getrlimit(RLIMIT_NOFILE, &lim))
- die_errno("cannot get RLIMIT_NOFILE");
-
- max_fds = lim.rlim_cur;
+ unsigned int max_fds = get_max_fd_limit();
/* Save 3 for stdin/stdout/stderr, 22 for work */
if (25 < max_fds)
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format |= DIFF_FORMAT_CALLBACK;
diff_opts.format_callback = submodule_collect_changed_cb;
- if (diff_setup_done(&diff_opts) < 0)
- die("diff_setup_done failed");
+ diff_setup_done(&diff_opts);
diff_tree_sha1(parent->item->object.sha1, commit->object.sha1, "", &diff_opts);
diffcore_std(&diff_opts);
diff_flush(&diff_opts);
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use Git::SVN::Utils qw(
+ add_path_to_url
+);
+
+# A reference cannot be a hash key, so we use an array.
+my @tests = (
+ ["http://x.com", "bar"] => 'http://x.com/bar',
+ ["http://x.com", ""] => 'http://x.com',
+ ["http://x.com/foo/", undef] => 'http://x.com/foo/',
+ ["http://x.com/foo/", "/bar/baz/"] => 'http://x.com/foo/bar/baz/',
+ ["http://x.com", 'per%cent'] => 'http://x.com/per%25cent',
+);
+
+while(@tests) {
+ my($have, $want) = splice @tests, 0, 2;
+
+ my $args = join ", ", map { qq['$_'] } map { defined($_) ? $_ : 'undef' } @$have;
+ my $name = "add_path_to_url($args) eq $want";
+ is add_path_to_url(@$have), $want, $name;
+}
--- /dev/null
+#!/usr/bin/env perl
+
+# Test our own home rolled URL canonicalizer. Test the private one
+# directly because we can't predict what the SVN API is doing to do.
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use Git::SVN::Utils;
+my $canonicalize_url = \&Git::SVN::Utils::_canonicalize_url_ourselves;
+
+my %tests = (
+ "http://x.com" => "http://x.com",
+ "http://x.com/" => "http://x.com",
+ "http://x.com/foo/bar" => "http://x.com/foo/bar",
+ "http://x.com//foo//bar//" => "http://x.com/foo/bar",
+ "http://x.com/ /%/" => "http://x.com/%20%20/%25",
+);
+
+for my $arg (keys %tests) {
+ my $want = $tests{$arg};
+
+ is $canonicalize_url->($arg), $want, "canonicalize_url('$arg') => $want";
+}
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use Git::SVN::Utils;
+my $collapse_dotdot = \&Git::SVN::Utils::_collapse_dotdot;
+
+my %tests = (
+ "foo/bar/baz" => "foo/bar/baz",
+ ".." => "..",
+ "foo/.." => "",
+ "/foo/bar/../../baz" => "/baz",
+ "deeply/.././deeply/nested" => "./deeply/nested",
+);
+
+for my $arg (keys %tests) {
+ my $want = $tests{$arg};
+
+ is $collapse_dotdot->($arg), $want, "_collapse_dotdot('$arg') => $want";
+}
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use Git::SVN::Utils qw(
+ join_paths
+);
+
+# A reference cannot be a hash key, so we use an array.
+my @tests = (
+ [] => '',
+ ["/x.com", "bar"] => '/x.com/bar',
+ ["x.com", ""] => 'x.com',
+ ["/x.com/foo/", undef, "bar"] => '/x.com/foo/bar',
+ ["x.com/foo/", "/bar/baz/"] => 'x.com/foo/bar/baz/',
+ ["foo", "bar"] => 'foo/bar',
+ ["/foo/bar", "baz", "/biff"] => '/foo/bar/baz/biff',
+ ["", undef, "."] => '.',
+ [] => '',
+
+);
+
+while(@tests) {
+ my($have, $want) = splice @tests, 0, 2;
+
+ my $args = join ", ", map { qq['$_'] } map { defined($_) ? $_ : 'undef' } @$have;
+ my $name = "join_paths($args) eq '$want'";
+ is join_paths(@$have), $want, $name;
+}
Git was compiled with USE_LIBPCRE=YesPlease. Wrap any tests
that use git-grep --perl-regexp or git-grep -P in these.
+ - CASE_INSENSITIVE_FS
+
+ Test is run on a case insensitive file system.
+
+ - UTF8_NFD_TO_NFC
+
+ Test is run on a filesystem which converts decomposed utf-8 (nfd)
+ to precomposed utf-8 (nfc).
+
Tips for Writing Tests
----------------------
cat stderr &&
false
fi &&
+ if test_have_prereq MINGW
+ then
+ dos2unix -q stderr
+ fi &&
test_cmp expect-stdout stdout &&
test_cmp expect-stderr stderr
}
else
echo "perf $test_count - $1:"
fi
- for i in $(seq 1 $GIT_PERF_REPEAT_COUNT); do
+ for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do
say >&3 "running: $2"
if test_run_perf_ "$2"
then
'
-test_expect_success 'check whether FS is case-insensitive' '
- mkdir junk &&
- echo good >junk/CamelCase &&
- echo bad >junk/camelcase &&
- if test "$(cat junk/CamelCase)" != good
- then
- test_set_prereq CASE_INSENSITIVE_FS
- fi
-'
-
test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
echo "$t -> $2" >expect
test_expect_${3:-success} "relative date ($2)" "
test-date show $t >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
"
}
test_expect_success 'test help' '
test_must_fail test-parse-options -h > output 2> output.err &&
test ! -s output.err &&
- test_cmp expect output
+ test_i18ncmp expect output
'
mv expect expect.err
test_cmp expect output
}
+check_i18n() {
+ what="$1" &&
+ shift &&
+ expect="$1" &&
+ shift &&
+ sed "s/^$what .*/$what $expect/" <expect.template >expect &&
+ test-parse-options $* >output 2>output.err &&
+ test ! -s output.err &&
+ test_i18ncmp expect output
+}
+
check_unknown() {
case "$1" in
--*)
test_cmp expect output.err
}
+check_unknown_i18n() {
+ case "$1" in
+ --*)
+ echo error: unknown option \`${1#--}\' >expect ;;
+ -*)
+ echo error: unknown switch \`${1#-}\' >expect ;;
+ esac &&
+ cat expect.err >>expect &&
+ test_must_fail test-parse-options $* >output 2>output.err &&
+ test ! -s output &&
+ test_i18ncmp expect output.err
+}
+
test_expect_success 'OPT_BOOL() #1' 'check boolean: 1 --yes'
test_expect_success 'OPT_BOOL() #2' 'check boolean: 1 --no-doubt'
test_expect_success 'OPT_BOOL() #3' 'check boolean: 1 -D'
test_expect_success 'OPT_BOOL() negation #1' 'check boolean: 0 -D --no-yes'
test_expect_success 'OPT_BOOL() negation #2' 'check boolean: 0 -D --no-no-doubt'
-test_expect_success 'OPT_BOOL() no negation #1' 'check_unknown --fear'
-test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown --no-no-fear'
+test_expect_success 'OPT_BOOL() no negation #1' 'check_unknown_i18n --fear'
+test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown_i18n --no-no-fear'
test_expect_success 'OPT_BOOL() positivation' 'check boolean: 0 -D --doubt'
test_expect_success 'OPT_CALLBACK() and callback errors work' '
test_must_fail test-parse-options --no-length > output 2> output.err &&
- test_cmp expect output &&
- test_cmp expect.err output.err
+ test_i18ncmp expect output &&
+ test_i18ncmp expect.err output.err
'
cat > expect <<EOF
auml=$(printf '\303\244')
aumlcdiar=$(printf '\141\314\210')
-case_insensitive=
-unibad=
-no_symlinks=
-test_expect_success 'see what we expect' '
-
- test_case=test_expect_success &&
- test_unicode=test_expect_success &&
- mkdir junk &&
- echo good >junk/CamelCase &&
- echo bad >junk/camelcase &&
- if test "$(cat junk/CamelCase)" != good
- then
- test_case=test_expect_failure &&
- case_insensitive=t
- fi &&
- rm -fr junk &&
- mkdir junk &&
- >junk/"$auml" &&
- case "$(cd junk && echo *)" in
- "$aumlcdiar")
- test_unicode=test_expect_failure &&
- unibad=t
- ;;
- *) ;;
- esac &&
- rm -fr junk &&
- {
- ln -s x y 2> /dev/null &&
- test -h y 2> /dev/null ||
- no_symlinks=1 &&
- rm -f y
- }
-'
-
-test "$case_insensitive" &&
+if test_have_prereq CASE_INSENSITIVE_FS
+then
say "will test on a case insensitive filesystem"
-test "$unibad" &&
+ test_case=test_expect_failure
+else
+ test_case=test_expect_success
+fi
+
+if test_have_prereq UTF8_NFD_TO_NFC
+then
say "will test on a unicode corrupting filesystem"
-test "$no_symlinks" &&
+ test_unicode=test_expect_failure
+else
+ test_unicode=test_expect_success
+fi
+
+test_have_prereq SYMLINKS ||
say "will test on a filesystem lacking symbolic links"
-if test "$case_insensitive"
+if test_have_prereq CASE_INSENSITIVE_FS
then
test_expect_success "detection of case insensitive filesystem during repo init" '
'
fi
-if test "$no_symlinks"
+if test_have_prereq SYMLINKS
then
test_expect_success "detection of filesystem w/o symlink support during repo init" '
- v=$(git config --bool core.symlinks) &&
- test "$v" = false
+ 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" '
- test_must_fail git config --bool core.symlinks ||
- test "$(git config --bool core.symlinks)" = true
+ v=$(git config --bool core.symlinks) &&
+ test "$v" = false
'
fi
test_expect_success 'no arguments, but no crash' '
test_must_fail git config >output 2>&1 &&
- grep usage output
+ test_i18ngrep usage output
'
cat > .git/config << EOF
grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
'
+_bz='\0'
+_bz5="$_bz$_bz$_bz$_bz$_bz"
+_bz20="$_bz5$_bz5$_bz5$_bz5"
+
+test_expect_success 'fsck notices blob entry pointing to null sha1' '
+ (git init null-blob &&
+ cd null-blob &&
+ sha=$(printf "100644 file$_bz$_bz20" |
+ git hash-object -w --stdin -t tree) &&
+ git fsck 2>out &&
+ cat out &&
+ grep "warning.*null sha1" out
+ )
+'
+
+test_expect_success 'fsck notices submodule entry pointing to null sha1' '
+ (git init null-commit &&
+ cd null-commit &&
+ sha=$(printf "160000 submodule$_bz$_bz20" |
+ git hash-object -w --stdin -t tree) &&
+ git fsck 2>out &&
+ cat out &&
+ grep "warning.*null sha1" out
+ )
+'
+
test_done
test_expect_success 'test --parseopt help output' '
test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec &&
- test_cmp expect output
+ test_i18ncmp expect output
'
cat > expect <<EOF
test_expect_success 'checkout-index --gobbledegook' '
test_expect_code 129 git checkout-index --gobbledegook 2>err &&
- grep "[Uu]sage" err
+ test_i18ngrep "[Uu]sage" err
'
test_expect_success 'checkout-index -h in broken repository' '
>.git/index &&
test_expect_code 129 git checkout-index -h >usage 2>&1
) &&
- grep "[Uu]sage" broken/usage
+ test_i18ngrep "[Uu]sage" broken/usage
'
test_done
test_expect_success 'update-index --nonsense dumps usage' '
test_expect_code 129 git update-index --nonsense 2>err &&
- grep "[Uu]sage: git update-index" err
+ test_i18ngrep "[Uu]sage: git update-index" err
'
test_expect_success 'update-index -h with corrupt index' '
>.git/index &&
test_expect_code 129 git update-index -h >usage 2>&1
) &&
- grep "[Uu]sage: git update-index" broken/usage
+ test_i18ngrep "[Uu]sage: git update-index" broken/usage
+'
+
+test_expect_success '--cacheinfo does not accept blob null sha1' '
+ echo content >file &&
+ git add file &&
+ git rev-parse :file >expect &&
+ test_must_fail git update-index --cacheinfo 100644 $_z40 file &&
+ git rev-parse :file >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--cacheinfo does not accept gitlink null sha1' '
+ git init submodule &&
+ (cd submodule && test_commit foo) &&
+ git add submodule &&
+ git rev-parse :submodule >expect &&
+ test_must_fail git update-index --cacheinfo 160000 $_z40 submodule &&
+ git rev-parse :submodule >actual &&
+ test_cmp expect actual
'
test_done
test_expect_success 'ls-files with nonsense option' '
test_expect_code 129 git ls-files --nonsense 2>actual &&
- grep "[Uu]sage: git ls-files" actual
+ test_i18ngrep "[Uu]sage: git ls-files" actual
'
test_expect_success 'ls-files -h in corrupt repository' '
>.git/index &&
test_expect_code 129 git ls-files -h >usage 2>&1
) &&
- grep "[Uu]sage: git ls-files " broken/usage
+ test_i18ngrep "[Uu]sage: git ls-files " broken/usage
'
test_done
>.git/refs/heads/master &&
test_expect_code 129 git branch -h >usage 2>&1
) &&
- grep "[Uu]sage" broken/usage
+ test_i18ngrep "[Uu]sage" broken/usage
'
test_expect_success \
test_expect_success \
'git branch -m dumps usage' \
'test_expect_code 129 git branch -m 2>err &&
- grep "[Uu]sage: git branch" err'
+ test_i18ngrep "[Uu]sage: git branch" err'
test_expect_success \
'git branch -m m m/m should work' \
git commit --allow-empty -m empty &&
test_commit D &&
git rebase C &&
- test $(git log --format=%s C..) = "D"
+ test "$(git log --format=%s C..)" = "D"
+'
+
+test_expect_success 'rebase --keep-empty' '
+ git reset --hard D &&
+ git rebase --keep-empty C &&
+ test "$(git log --format=%s C..)" = "D
+empty"
+'
+
+test_expect_success 'rebase --keep-empty keeps empty even if already in upstream' '
+ git reset --hard A &&
+ git commit --allow-empty -m also-empty &&
+ git rebase --keep-empty D &&
+ test "$(git log --format=%s A..)" = "also-empty
+D
+empty"
'
test_done
git diff --exit-code HEAD &&
test_must_fail git cherry-pick --nonsense 2>msg &&
git diff --exit-code HEAD "$pos" &&
- grep '[Uu]sage:' msg
+ test_i18ngrep '[Uu]sage:' msg
'
test_expect_success 'revert --nonsense' '
git diff --exit-code HEAD &&
test_must_fail git revert --nonsense 2>msg &&
git diff --exit-code HEAD "$pos" &&
- grep '[Uu]sage:' msg
+ test_i18ngrep '[Uu]sage:' msg
'
test_expect_success 'cherry-pick after renaming branch' '
'
+test_expect_success 'cherry-pick a commit with an empty message with --allow-empty-message' '
+ git checkout -f master &&
+ git cherry-pick --allow-empty-message empty-branch
+'
+
test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
git checkout master &&
echo fourth >>file2 &&
git stash apply
) |
sed -e 1,2d >actual && # drop "Saved..." and "HEAD is now..."
- test_cmp expect actual
+ test_i18ncmp expect actual
'
cat > expect << EOF
. ./test-lib.sh
+if ! test_have_prereq UTF8_NFD_TO_NFC
+then
+ skip_all="filesystem does not corrupt utf-8"
+ test_done
+fi
+
+# create utf-8 variables
Adiarnfc=`printf '\303\204'`
Adiarnfd=`printf 'A\314\210'`
-# check if the feature is compiled in
-mkdir junk &&
->junk/"$Adiarnfc" &&
-case "$(cd junk && echo *)" in
- "$Adiarnfd")
- test_nfd=1
- ;;
- *) ;;
-esac
-rm -rf junk
+Odiarnfc=`printf '\303\226'`
+Odiarnfd=`printf 'O\314\210'`
+AEligatu=`printf '\303\206'`
+Invalidu=`printf '\303\377'`
-if test "$test_nfd"
-then
- # create more utf-8 variables
- Odiarnfc=`printf '\303\226'`
- Odiarnfd=`printf 'O\314\210'`
- AEligatu=`printf '\303\206'`
- Invalidu=`printf '\303\377'`
+#Create a string with 255 bytes (decomposed)
+Alongd=$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd #21 Byte
+Alongd=$Alongd$Alongd$Alongd #63 Byte
+Alongd=$Alongd$Alongd$Alongd$Alongd$Adiarnfd #255 Byte
+#Create a string with 254 bytes (precomposed)
+Alongc=$AEligatu$AEligatu$AEligatu$AEligatu$AEligatu #10 Byte
+Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc #50 Byte
+Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc #250 Byte
+Alongc=$Alongc$AEligatu$AEligatu #254 Byte
- #Create a string with 255 bytes (decomposed)
- Alongd=$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd #21 Byte
- Alongd=$Alongd$Alongd$Alongd #63 Byte
- Alongd=$Alongd$Alongd$Alongd$Alongd$Adiarnfd #255 Byte
-
- #Create a string with 254 bytes (precomposed)
- Alongc=$AEligatu$AEligatu$AEligatu$AEligatu$AEligatu #10 Byte
- Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc #50 Byte
- Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc #250 Byte
- Alongc=$Alongc$AEligatu$AEligatu #254 Byte
-
- test_expect_success "detect if nfd needed" '
- precomposeunicode=`git config core.precomposeunicode` &&
- test "$precomposeunicode" = false &&
- git config core.precomposeunicode true
- '
- test_expect_success "setup" '
- >x &&
- git add x &&
- git commit -m "1st commit" &&
- git rm x &&
- git commit -m "rm x"
- '
- test_expect_success "setup case mac" '
- git checkout -b mac_os
- '
- # This will test nfd2nfc in readdir()
- test_expect_success "add file Adiarnfc" '
- echo f.Adiarnfc >f.$Adiarnfc &&
- git add f.$Adiarnfc &&
- git commit -m "add f.$Adiarnfc"
- '
- # This will test nfd2nfc in git stage()
- test_expect_success "stage file d.Adiarnfd/f.Adiarnfd" '
- mkdir d.$Adiarnfd &&
- echo d.$Adiarnfd/f.$Adiarnfd >d.$Adiarnfd/f.$Adiarnfd &&
- git stage d.$Adiarnfd/f.$Adiarnfd &&
- git commit -m "add d.$Adiarnfd/f.$Adiarnfd"
- '
- test_expect_success "add link Adiarnfc" '
- ln -s d.$Adiarnfd/f.$Adiarnfd l.$Adiarnfc &&
- git add l.$Adiarnfc &&
- git commit -m "add l.Adiarnfc"
- '
- # This will test git log
- test_expect_success "git log f.Adiar" '
- git log f.$Adiarnfc > f.Adiarnfc.log &&
- git log f.$Adiarnfd > f.Adiarnfd.log &&
- test -s f.Adiarnfc.log &&
- test -s f.Adiarnfd.log &&
- test_cmp f.Adiarnfc.log f.Adiarnfd.log &&
- rm f.Adiarnfc.log f.Adiarnfd.log
- '
- # This will test git ls-files
- test_expect_success "git lsfiles f.Adiar" '
- git ls-files f.$Adiarnfc > f.Adiarnfc.log &&
- git ls-files f.$Adiarnfd > f.Adiarnfd.log &&
- test -s f.Adiarnfc.log &&
- test -s f.Adiarnfd.log &&
- test_cmp f.Adiarnfc.log f.Adiarnfd.log &&
- rm f.Adiarnfc.log f.Adiarnfd.log
- '
- # This will test git mv
- test_expect_success "git mv" '
- git mv f.$Adiarnfd f.$Odiarnfc &&
- git mv d.$Adiarnfd d.$Odiarnfc &&
- git mv l.$Adiarnfd l.$Odiarnfc &&
- git commit -m "mv Adiarnfd Odiarnfc"
- '
- # Files can be checked out as nfc
- # And the link has been corrected from nfd to nfc
- test_expect_success "git checkout nfc" '
- rm f.$Odiarnfc &&
- git checkout f.$Odiarnfc
- '
- # Make it possible to checkout files with their NFD names
- test_expect_success "git checkout file nfd" '
- rm -f f.* &&
- git checkout f.$Odiarnfd
- '
- # Make it possible to checkout links with their NFD names
- test_expect_success "git checkout link nfd" '
- rm l.* &&
- git checkout l.$Odiarnfd
- '
- test_expect_success "setup case mac2" '
- git checkout master &&
- git reset --hard &&
- git checkout -b mac_os_2
- '
- # This will test nfd2nfc in git commit
- test_expect_success "commit file d2.Adiarnfd/f.Adiarnfd" '
- mkdir d2.$Adiarnfd &&
- echo d2.$Adiarnfd/f.$Adiarnfd >d2.$Adiarnfd/f.$Adiarnfd &&
- git add d2.$Adiarnfd/f.$Adiarnfd &&
- git commit -m "add d2.$Adiarnfd/f.$Adiarnfd" -- d2.$Adiarnfd/f.$Adiarnfd
- '
- test_expect_success "setup for long decomposed filename" '
- git checkout master &&
- git reset --hard &&
- git checkout -b mac_os_long_nfd_fn
- '
- test_expect_success "Add long decomposed filename" '
- echo longd >$Alongd &&
- git add * &&
- git commit -m "Long filename"
- '
- test_expect_success "setup for long precomposed filename" '
- git checkout master &&
- git reset --hard &&
- git checkout -b mac_os_long_nfc_fn
- '
- test_expect_success "Add long precomposed filename" '
- echo longc >$Alongc &&
- git add * &&
- git commit -m "Long filename"
- '
- # Test if the global core.precomposeunicode stops autosensing
- # Must be the last test case
- test_expect_success "respect git config --global core.precomposeunicode" '
- git config --global core.precomposeunicode true &&
- rm -rf .git &&
- git init &&
- precomposeunicode=`git config core.precomposeunicode` &&
- test "$precomposeunicode" = "true"
- '
-else
- say "Skipping nfc/nfd tests"
-fi
+test_expect_success "detect if nfd needed" '
+ precomposeunicode=`git config core.precomposeunicode` &&
+ test "$precomposeunicode" = false &&
+ git config core.precomposeunicode true
+'
+test_expect_success "setup" '
+ >x &&
+ git add x &&
+ git commit -m "1st commit" &&
+ git rm x &&
+ git commit -m "rm x"
+'
+test_expect_success "setup case mac" '
+ git checkout -b mac_os
+'
+# This will test nfd2nfc in readdir()
+test_expect_success "add file Adiarnfc" '
+ echo f.Adiarnfc >f.$Adiarnfc &&
+ git add f.$Adiarnfc &&
+ git commit -m "add f.$Adiarnfc"
+'
+# This will test nfd2nfc in git stage()
+test_expect_success "stage file d.Adiarnfd/f.Adiarnfd" '
+ mkdir d.$Adiarnfd &&
+ echo d.$Adiarnfd/f.$Adiarnfd >d.$Adiarnfd/f.$Adiarnfd &&
+ git stage d.$Adiarnfd/f.$Adiarnfd &&
+ git commit -m "add d.$Adiarnfd/f.$Adiarnfd"
+'
+test_expect_success "add link Adiarnfc" '
+ ln -s d.$Adiarnfd/f.$Adiarnfd l.$Adiarnfc &&
+ git add l.$Adiarnfc &&
+ git commit -m "add l.Adiarnfc"
+'
+# This will test git log
+test_expect_success "git log f.Adiar" '
+ git log f.$Adiarnfc > f.Adiarnfc.log &&
+ git log f.$Adiarnfd > f.Adiarnfd.log &&
+ test -s f.Adiarnfc.log &&
+ test -s f.Adiarnfd.log &&
+ test_cmp f.Adiarnfc.log f.Adiarnfd.log &&
+ rm f.Adiarnfc.log f.Adiarnfd.log
+'
+# This will test git ls-files
+test_expect_success "git lsfiles f.Adiar" '
+ git ls-files f.$Adiarnfc > f.Adiarnfc.log &&
+ git ls-files f.$Adiarnfd > f.Adiarnfd.log &&
+ test -s f.Adiarnfc.log &&
+ test -s f.Adiarnfd.log &&
+ test_cmp f.Adiarnfc.log f.Adiarnfd.log &&
+ rm f.Adiarnfc.log f.Adiarnfd.log
+'
+# This will test git mv
+test_expect_success "git mv" '
+ git mv f.$Adiarnfd f.$Odiarnfc &&
+ git mv d.$Adiarnfd d.$Odiarnfc &&
+ git mv l.$Adiarnfd l.$Odiarnfc &&
+ git commit -m "mv Adiarnfd Odiarnfc"
+'
+# Files can be checked out as nfc
+# And the link has been corrected from nfd to nfc
+test_expect_success "git checkout nfc" '
+ rm f.$Odiarnfc &&
+ git checkout f.$Odiarnfc
+'
+# Make it possible to checkout files with their NFD names
+test_expect_success "git checkout file nfd" '
+ rm -f f.* &&
+ git checkout f.$Odiarnfd
+'
+# Make it possible to checkout links with their NFD names
+test_expect_success "git checkout link nfd" '
+ rm l.* &&
+ git checkout l.$Odiarnfd
+'
+test_expect_success "setup case mac2" '
+ git checkout master &&
+ git reset --hard &&
+ git checkout -b mac_os_2
+'
+# This will test nfd2nfc in git commit
+test_expect_success "commit file d2.Adiarnfd/f.Adiarnfd" '
+ mkdir d2.$Adiarnfd &&
+ echo d2.$Adiarnfd/f.$Adiarnfd >d2.$Adiarnfd/f.$Adiarnfd &&
+ git add d2.$Adiarnfd/f.$Adiarnfd &&
+ git commit -m "add d2.$Adiarnfd/f.$Adiarnfd" -- d2.$Adiarnfd/f.$Adiarnfd
+'
+test_expect_success "setup for long decomposed filename" '
+ git checkout master &&
+ git reset --hard &&
+ git checkout -b mac_os_long_nfd_fn
+'
+test_expect_success "Add long decomposed filename" '
+ echo longd >$Alongd &&
+ git add * &&
+ git commit -m "Long filename"
+'
+test_expect_success "setup for long precomposed filename" '
+ git checkout master &&
+ git reset --hard &&
+ git checkout -b mac_os_long_nfc_fn
+'
+test_expect_success "Add long precomposed filename" '
+ echo longc >$Alongc &&
+ git add * &&
+ git commit -m "Long filename"
+'
+# Test if the global core.precomposeunicode stops autosensing
+# Must be the last test case
+test_expect_success "respect git config --global core.precomposeunicode" '
+ git config --global core.precomposeunicode true &&
+ rm -rf .git &&
+ git init &&
+ precomposeunicode=`git config core.precomposeunicode` &&
+ test "$precomposeunicode" = "true"
+'
test_done
test_chmod -x rezrov &&
echo " 0 files changed" >expect &&
git diff HEAD --stat >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_expect_success '--shortstat output after text chmod' '
git diff HEAD --shortstat >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_expect_success '--stat output after binary chmod' '
test_chmod +x binbin &&
echo " 0 files changed" >expect &&
git diff HEAD --stat >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_expect_success '--shortstat output after binary chmod' '
git diff HEAD --shortstat >actual &&
- test_cmp expect actual
+ test_i18ncmp expect actual
'
test_done
# apply needs to be able to skip the binary material correctly
# in order to report the line number of a corrupt patch.
-test_expect_success 'apply detecting corrupt patch correctly' '
+test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' '
git diff >output &&
sed -e "s/-CIT/xCIT/" <output >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
test "$detected" = xCIT
'
-test_expect_success 'apply detecting corrupt patch correctly' '
+test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' '
git diff --binary | sed -e "s/-CIT/xCIT/" >broken &&
test_must_fail git apply --stat --summary broken 2>detected &&
detected=`cat detected` &&
grep -v "Linus Torvalds" actual
'
+test_expect_success 'prepare a file that ends with an incomplete line' '
+ test_seq 1 99 >seq &&
+ printf 100 >>seq &&
+ git add seq &&
+ git commit seq -m seq
+'
+
+test_expect_success 'rewrite the middle 90% of sequence file and terminate with newline' '
+ test_seq 1 5 >seq &&
+ test_seq 9331 9420 >>seq &&
+ test_seq 96 100 >>seq
+'
+
+test_expect_success 'confirm that sequence file is considered a rewrite' '
+ git diff -B seq >res &&
+ grep "dissimilarity index" res
+'
+
+test_expect_success 'no newline at eof is on its own line without -B' '
+ git diff seq >res &&
+ grep "^\\\\ " res &&
+ ! grep "^..*\\\\ " res
+'
+
+test_expect_success 'no newline at eof is on its own line with -B' '
+ git diff -B seq >res &&
+ grep "^\\\\ " res &&
+ ! grep "^..*\\\\ " res
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test diff with a bogus tree containing the null sha1'
+. ./test-lib.sh
+
+empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+
+test_expect_success 'create bogus tree' '
+ bogus_tree=$(
+ printf "100644 fooQQQQQQQQQQQQQQQQQQQQQ" |
+ q_to_nul |
+ git hash-object -w --stdin -t tree
+ )
+'
+
+test_expect_success 'create tree with matching file' '
+ echo bar >foo &&
+ git add foo &&
+ good_tree=$(git write-tree)
+ blob=$(git rev-parse :foo)
+'
+
+test_expect_success 'raw diff shows null sha1 (addition)' '
+ echo ":000000 100644 $_z40 $_z40 A foo" >expect &&
+ git diff-tree $empty_tree $bogus_tree >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'raw diff shows null sha1 (removal)' '
+ echo ":100644 000000 $_z40 $_z40 D foo" >expect &&
+ git diff-tree $bogus_tree $empty_tree >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'raw diff shows null sha1 (modification)' '
+ echo ":100644 100644 $blob $_z40 M foo" >expect &&
+ git diff-tree $good_tree $bogus_tree >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'raw diff shows null sha1 (other direction)' '
+ echo ":100644 100644 $_z40 $blob M foo" >expect &&
+ git diff-tree $bogus_tree $good_tree >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'raw diff shows null sha1 (reverse)' '
+ echo ":100644 100644 $_z40 $blob M foo" >expect &&
+ git diff-tree -R $good_tree $bogus_tree >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'raw diff shows null sha1 (index)' '
+ echo ":100644 100644 $_z40 $blob M foo" >expect &&
+ git diff-index $bogus_tree >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'patch fails due to bogus sha1 (addition)' '
+ test_must_fail git diff-tree -p $empty_tree $bogus_tree
+'
+
+test_expect_success 'patch fails due to bogus sha1 (removal)' '
+ test_must_fail git diff-tree -p $bogus_tree $empty_tree
+'
+
+test_expect_success 'patch fails due to bogus sha1 (modification)' '
+ test_must_fail git diff-tree -p $good_tree $bogus_tree
+'
+
+test_expect_success 'patch fails due to bogus sha1 (other direction)' '
+ test_must_fail git diff-tree -p $bogus_tree $good_tree
+'
+
+test_expect_success 'patch fails due to bogus sha1 (reverse)' '
+ test_must_fail git diff-tree -R -p $good_tree $bogus_tree
+'
+
+test_expect_success 'patch fails due to bogus sha1 (index)' '
+ test_must_fail git diff-index -p $bogus_tree
+'
+
+test_done
test_expect_success 'apply with too large -p' '
cp file1.saved file1 &&
test_must_fail git apply --stat -p3 patch.file 2>err &&
- grep "removing 3 leading" err
+ test_i18ngrep "removing 3 leading" err
'
test_expect_success 'apply (-p2) traditional diff with funny filenames' '
test_expect_success 'apply with too large -p and fancy filename' '
cp file1.saved file1 &&
test_must_fail git apply --stat -p3 patch.escaped 2>err &&
- grep "removing 3 leading" err
+ test_i18ngrep "removing 3 leading" err
'
test_expect_success 'apply (-p2) diff, mode change only' '
test_expect_success 'apply diff with inconsistent filenames in headers' '
test_must_fail git apply bad1.patch 2>err &&
- grep "inconsistent new filename" err &&
+ test_i18ngrep "inconsistent new filename" err &&
test_must_fail git apply bad2.patch 2>err &&
- grep "inconsistent old filename" err
+ test_i18ngrep "inconsistent old filename" err
'
test_done
git update-index --index-info <failedmerge &&
cp file3.conflict file3 &&
test_must_fail git rerere --no-no-rerere-autoupdate 2>err &&
- grep [Uu]sage err &&
+ test_i18ngrep [Uu]sage err &&
test_must_fail git update-index --refresh
'
test_expect_success 'rerere -h' '
test_must_fail git rerere -h >help &&
- grep [Uu]sage help
+ test_i18ngrep [Uu]sage help
'
test_done
test_expect_success 'log --graph with diff and stats' '
git log --graph --pretty=short --stat -p >actual &&
sanitize_output >actual.sanitized <actual &&
- test_cmp expect actual.sanitized
+ test_i18ncmp expect actual.sanitized
'
test_done
stat1_part=$(git diff --stat --root HEAD^) &&
printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n" >expected &&
git log -z --stat --pretty="format:%s" >actual &&
- test_cmp expected actual
+ test_i18ncmp expected actual
'
test_expect_failure 'NUL termination with --stat' '
stat1_part=$(git diff --stat --root HEAD^) &&
printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n\0" >expected &&
git log -z --stat --pretty="tformat:%s" >actual &&
- test_cmp expected actual
+ test_i18ncmp expected actual
'
test_done
test_expect_success \
'make sure index-pack detects the SHA1 collision' \
'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
- grep "SHA1 COLLISION FOUND" msg'
+ test_i18ngrep "SHA1 COLLISION FOUND" msg'
test_expect_success \
'make sure index-pack detects the SHA1 collision (large blobs)' \
'test_must_fail git -c core.bigfilethreshold=1 index-pack -o bad.idx test-3.pack 2>msg &&
- grep "SHA1 COLLISION FOUND" msg'
+ test_i18ngrep "SHA1 COLLISION FOUND" msg'
test_done
)
'
+test_expect_success 'receive-pack runs auto-gc in remote repo' '
+ rm -rf parent child &&
+ git init parent &&
+ (
+ # Setup a repo with 2 packs
+ cd parent &&
+ echo "Some text" >file.txt &&
+ git add . &&
+ git commit -m "Initial commit" &&
+ git repack -adl &&
+ echo "Some more text" >>file.txt &&
+ git commit -a -m "Second commit" &&
+ git repack
+ ) &&
+ cp -a parent child &&
+ (
+ # Set the child to auto-pack if more than one pack exists
+ cd child &&
+ git config gc.autopacklimit 1 &&
+ git branch test_auto_gc &&
+ # And create a file that follows the temporary object naming
+ # convention for the auto-gc to remove
+ : >.git/objects/tmp_test_object &&
+ test-chmtime =-1209601 .git/objects/tmp_test_object
+ ) &&
+ (
+ cd parent &&
+ echo "Even more text" >>file.txt &&
+ git commit -a -m "Third commit" &&
+ git send-pack ../child HEAD:refs/heads/test_auto_gc >output 2>&1 &&
+ grep "Auto packing the repository for optimum performance." output
+ ) &&
+ test ! -e child/.git/objects/tmp_test_object
+'
+
rewound_push_setup() {
rm -rf parent child &&
mkdir parent &&
'
-test_expect_success 'remote information for the origin' '
+test_expect_success C_LOCALE_OUTPUT 'remote information for the origin' '
(
cd test &&
tokens_match origin "$(git remote)" &&
cd test &&
git remote add -f second ../two &&
tokens_match "origin second" "$(git remote)" &&
- check_remote_track origin master side &&
- check_remote_track second master side another &&
check_tracking_branch second master side another &&
git for-each-ref "--format=%(refname)" refs/remotes |
sed -e "/^refs\/remotes\/origin\//d" \
)
'
+test_expect_success C_LOCALE_OUTPUT 'check remote tracking' '
+(
+ cd test &&
+ check_remote_track origin master side &&
+ check_remote_track second master side another
+)
+'
+
test_expect_success 'remote forces tracking branches' '
(
cd test &&
)
'
-test_expect_success 'remove remote' '
+test_expect_success C_LOCALE_OUTPUT 'remove remote' '
(
cd test &&
tokens_match origin "$(git remote)" &&
git remote rm oops 2>actual2 &&
git branch -d foobranch &&
git tag -d footag &&
- test_cmp expect1 actual1 &&
- test_cmp expect2 actual2
+ test_i18ncmp expect1 actual1 &&
+ test_i18ncmp expect2 actual2
)
'
git config --add remote.two.push refs/heads/master:refs/heads/another &&
git remote show origin two > output &&
git branch -d rebase octopus &&
- test_cmp expect output)
+ test_i18ncmp expect output)
'
cat > test/expect << EOF
cd test &&
git remote show -n origin > output &&
mv ../one.unreachable ../one &&
- test_cmp expect output)
+ test_i18ncmp expect output)
'
test_expect_success 'prune' '
test_expect_success 'set-head --auto fails w/multiple HEADs' '
(cd test &&
test_must_fail git remote set-head --auto two >output 2>&1 &&
- test_cmp expect output)
+ test_i18ncmp expect output)
'
cat >test/expect <<EOF
test_must_fail git rev-parse refs/remotes/origin/side &&
(cd ../one &&
git branch -m side side2) &&
- test_cmp expect output)
+ test_i18ncmp expect output)
'
test_expect_success 'add --mirror && prune' '
cd seven &&
git remote prune origin
) >err 2>&1 &&
- grep "has become dangling" err &&
+ test_i18ngrep "has become dangling" err &&
: And the dangling symref will not cause other annoying errors &&
(
printf "0032want %s\n00000009done\n0000" \
$(git rev-parse HEAD) >input &&
test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
- grep "unable to read" output.err &&
- grep "pack-objects died" output.err
+ test_i18ngrep "unable to read" output.err &&
+ test_i18ngrep "pack-objects died" output.err
'
test_expect_success 'corrupt repo differently' '
test_expect_success 'clone remote repository' '
rm -rf test_repo_clone &&
- git clone $HTTPD_URL/smart/test_repo.git test_repo_clone
+ git clone $HTTPD_URL/smart/test_repo.git test_repo_clone &&
+ (
+ cd test_repo_clone && git config push.default matching
+ )
'
test_expect_success 'push to remote repository (standard)' '
test_expect_success EXPENSIVE 'create 50,000 tags in the repo' '
(
cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
- for i in `seq 50000`
+ for i in `test_seq 50000`
do
echo "commit refs/heads/too-many-refs"
echo "mark :$i"
test_expect_success 'gc --gobbledegook' '
test_expect_code 129 git gc --nonsense 2>err &&
- grep "[Uu]sage: git gc" err
+ test_i18ngrep "[Uu]sage: git gc" err
'
test_expect_success 'gc -h with invalid configuration' '
echo "[gc] pruneexpire = CORRUPT" >>.git/config &&
test_expect_code 129 git gc -h >usage 2>&1
) &&
- grep "[Uu]sage" broken/usage
+ test_i18ngrep "[Uu]sage" broken/usage
'
test_done
test_cmp expect url
'
+test_failure_with_unknown_submodule () {
+ test_must_fail git submodule $1 no-such-submodule 2>output.err &&
+ grep "^error: .*no-such-submodule" output.err
+}
+
+test_expect_success 'init should fail with unknown submodule' '
+ test_failure_with_unknown_submodule init
+'
+
+test_expect_success 'update should fail with unknown submodule' '
+ test_failure_with_unknown_submodule update
+'
+
+test_expect_success 'status should fail with unknown submodule' '
+ test_failure_with_unknown_submodule status
+'
+
+test_expect_success 'sync should fail with unknown submodule' '
+ test_failure_with_unknown_submodule sync
+'
+
test_expect_success 'update should fail when path is used by a file' '
echo hello >expect &&
'
test_expect_success 'submodule <invalid-path> warns' '
-
- git submodule no-such-submodule 2> output.err &&
- grep "^error: .*no-such-submodule" output.err
-
+ test_failure_with_unknown_submodule
'
test_expect_success 'add submodules without specifying an explicit path' '
)
'
+test_expect_success 'submodule update --force forcibly checks out submodules' '
+ (cd super &&
+ (cd submodule &&
+ rm -f file
+ ) &&
+ git submodule update --force submodule &&
+ (cd submodule &&
+ test "$(git status -s file)" = ""
+ )
+ )
+'
+
test_expect_success 'submodule update --rebase staying on master' '
(cd super/submodule &&
git checkout master
git submodule init &&
git commit -am "new_submodule" &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../expect
+ git rev-parse --verify HEAD >../expect
) &&
(cd submodule &&
test_commit "update_submodule" file
git checkout HEAD^ &&
test_must_fail git submodule update &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../actual
+ git rev-parse --verify HEAD >../actual
) &&
test_cmp expect actual
)
test_commit "update_submodule_again_again" file
) &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../expect &&
+ git rev-parse --verify HEAD >../expect &&
test_commit "update_submodule2_again" file
) &&
git add submodule &&
) &&
test_must_fail git submodule update --recursive &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../actual
+ git rev-parse --verify HEAD >../actual
) &&
test_cmp expect actual
)
) &&
git checkout HEAD^ &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../expect
+ git rev-parse --verify HEAD >../expect
) &&
git config submodule.submodule.update merge &&
test_must_fail git submodule update &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../actual
+ git rev-parse --verify HEAD >../actual
) &&
test_cmp expect actual
)
) &&
git checkout HEAD^ &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../expect
+ git rev-parse --verify HEAD >../expect
) &&
git config submodule.submodule.update rebase &&
test_must_fail git submodule update &&
(cd submodule2 &&
- git rev-parse --max-count=1 HEAD > ../actual
+ git rev-parse --verify HEAD >../actual
) &&
test_cmp expect actual
)
# dir1/untracked dir2/untracked untracked
# dir2/modified output
EOF
- test_cmp expect output
+ test_i18ncmp expect output
'
cat >expect <<\EOF
>.git/index &&
test_expect_code 129 git merge -h 2>usage
) &&
- grep "[Uu]sage: git merge" broken/usage
+ test_i18ngrep "[Uu]sage: git merge" broken/usage
'
test_expect_success 'reject non-strategy with a git-merge-foo name' '
git rm file12 &&
git commit -m "branch1 changes" &&
+ git checkout -b stash1 master &&
+ echo stash1 change file11 >file11 &&
+ git add file11 &&
+ git commit -m "stash1 changes" &&
+
+ git checkout -b stash2 master &&
+ echo stash2 change file11 >file11 &&
+ git add file11 &&
+ git commit -m "stash2 changes" &&
+
git checkout master &&
git submodule update -N &&
echo master updated >file1 &&
git reset --hard
'
+test_expect_success 'conflicted stash sets up rerere' '
+ git config rerere.enabled true &&
+ git checkout stash1 &&
+ echo "Conflicting stash content" >file11 &&
+ git stash &&
+
+ git checkout --detach stash2 &&
+ test_must_fail git stash apply &&
+
+ test -n "$(git ls-files -u)" &&
+ conflicts="$(git rerere remaining)" &&
+ test "$conflicts" = "file11" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" != "No files need merging" &&
+
+ git commit -am "save the stash resolution" &&
+
+ git reset --hard stash2 &&
+ test_must_fail git stash apply &&
+
+ test -n "$(git ls-files -u)" &&
+ conflicts="$(git rerere remaining)" &&
+ test -z "$conflicts" &&
+ output="$(git mergetool --no-prompt)" &&
+ test "$output" = "No files need merging"
+'
+
test_expect_success 'mergetool takes partial path' '
+ git reset --hard
git config rerere.enabled false &&
git checkout -b test12 branch1 &&
git submodule update -N &&
git -c grep.extendedRegexp=true grep "a+b*c" ab >actual &&
test_cmp expected actual
'
+
+ test_expect_success "grep $L with grep.patterntype=basic" '
+ echo "ab:a+bc" >expected &&
+ git -c grep.patterntype=basic grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep $L with grep.patterntype=extended" '
+ echo "ab:abc" >expected &&
+ git -c grep.patterntype=extended grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep $L with grep.patterntype=fixed" '
+ echo "ab:a+b*c" >expected &&
+ git -c grep.patterntype=fixed grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success LIBPCRE "grep $L with grep.patterntype=perl" '
+ echo "ab:a+b*c" >expected &&
+ git -c grep.patterntype=perl grep "a\x{2b}b\x{2a}c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep $L with grep.patternType=default and grep.extendedRegexp=true" '
+ echo "ab:abc" >expected &&
+ git \
+ -c grep.patternType=default \
+ -c grep.extendedRegexp=true \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success "grep $L with grep.extendedRegexp=true and grep.patternType=default" '
+ echo "ab:abc" >expected &&
+ git \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=default \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success 'grep $L with grep.patternType=extended and grep.extendedRegexp=false' '
+ echo "ab:abc" >expected &&
+ git \
+ -c grep.patternType=extended \
+ -c grep.extendedRegexp=false \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success 'grep $L with grep.patternType=basic and grep.extendedRegexp=true' '
+ echo "ab:a+bc" >expected &&
+ git \
+ -c grep.patternType=basic \
+ -c grep.extendedRegexp=true \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success 'grep $L with grep.extendedRegexp=false and grep.patternType=extended' '
+ echo "ab:abc" >expected &&
+ git \
+ -c grep.extendedRegexp=false \
+ -c grep.patternType=extended \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success 'grep $L with grep.extendedRegexp=true and grep.patternType=basic' '
+ echo "ab:a+bc" >expected &&
+ git \
+ -c grep.extendedRegexp=true \
+ -c grep.patternType=basic \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+ '
done
cat >expected <<EOF
test_must_fail git grep -G "a["
'
+test_expect_success 'grep invalidpattern properly dies with grep.patternType=basic' '
+ test_must_fail git -c grep.patterntype=basic grep "a["
+'
+
test_expect_success 'grep -E invalidpattern properly dies ' '
test_must_fail git grep -E "a["
'
+test_expect_success 'grep invalidpattern properly dies with grep.patternType=extended' '
+ test_must_fail git -c grep.patterntype=extended grep "a["
+'
+
test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' '
test_must_fail git grep -P "a["
'
+test_expect_success LIBPCRE 'grep invalidpattern properly dies with grep.patternType=perl' '
+ test_must_fail git -c grep.patterntype=perl grep "a["
+'
+
test_expect_success 'grep -G -E -F pattern' '
echo "ab:a+b*c" >expected &&
git grep -G -E -F "a+b*c" ab >actual &&
test_cmp expected actual
'
+test_expect_success 'grep pattern with grep.patternType=basic, =extended, =fixed' '
+ echo "ab:a+b*c" >expected &&
+ git \
+ -c grep.patterntype=basic \
+ -c grep.patterntype=extended \
+ -c grep.patterntype=fixed \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'grep -E -F -G pattern' '
echo "ab:a+bc" >expected &&
git grep -E -F -G "a+b*c" ab >actual &&
test_cmp expected actual
'
+test_expect_success 'grep pattern with grep.patternType=extended, =fixed, =basic' '
+ echo "ab:a+bc" >expected &&
+ git \
+ -c grep.patterntype=extended \
+ -c grep.patterntype=fixed \
+ -c grep.patterntype=basic \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'grep -F -G -E pattern' '
echo "ab:abc" >expected &&
git grep -F -G -E "a+b*c" ab >actual &&
test_cmp expected actual
'
+test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =extended' '
+ echo "ab:abc" >expected &&
+ git \
+ -c grep.patterntype=fixed \
+ -c grep.patterntype=basic \
+ -c grep.patterntype=extended \
+ grep "a+b*c" ab >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'grep -G -F -P -E pattern' '
>empty &&
test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual &&
test_cmp empty actual
'
+test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =perl, =extended' '
+ >empty &&
+ test_must_fail git \
+ -c grep.patterntype=fixed \
+ -c grep.patterntype=basic \
+ -c grep.patterntype=perl \
+ -c grep.patterntype=extended \
+ grep "a\x{2b}b\x{2a}c" ab >actual &&
+ test_cmp empty actual
+'
+
test_expect_success LIBPCRE 'grep -G -F -E -P pattern' '
echo "ab:a+b*c" >expected &&
git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual &&
test_cmp expected actual
'
+test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' '
+ echo "ab:a+b*c" >expected &&
+ git \
+ -c grep.patterntype=fixed \
+ -c grep.patterntype=basic \
+ -c grep.patterntype=extended \
+ -c grep.patterntype=perl \
+ grep "a\x{2b}b\x{2a}c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success LIBPCRE 'grep -P pattern with grep.patternType=fixed' '
+ echo "ab:a+b*c" >expected &&
+ git \
+ -c grep.patterntype=fixed \
+ grep -P "a\x{2b}b\x{2a}c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -F pattern with grep.patternType=basic' '
+ echo "ab:a+b*c" >expected &&
+ git \
+ -c grep.patterntype=basic \
+ grep -F "*c" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -G pattern with grep.patternType=fixed' '
+ {
+ echo "ab:a+b*c"
+ echo "ab:a+bc"
+ } >expected &&
+ git \
+ -c grep.patterntype=fixed \
+ grep -G "a+b" ab >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'grep -E pattern with grep.patternType=fixed' '
+ {
+ echo "ab:a+b*c"
+ echo "ab:a+bc"
+ echo "ab:abc"
+ } >expected &&
+ git \
+ -c grep.patterntype=fixed \
+ grep -E "a+" ab >actual &&
+ test_cmp expected actual
+'
+
test_config() {
git config "$1" "$2" &&
test_when_finished "git config --unset $1"
grep "^Subject: =?UTF-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
'
+test_expect_success $PREREQ 'utf8 author is correctly passed on' '
+ clean_fake_sendmail &&
+ test_commit weird_author &&
+ test_when_finished "git reset --hard HEAD^" &&
+ git commit --amend --author "Füñný Nâmé <odd_?=mail@example.com>" &&
+ git format-patch --stdout -1 >funny_name.patch &&
+ git send-email --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ funny_name.patch &&
+ grep "^From: Füñný Nâmé <odd_?=mail@example.com>" msgtxt1
+'
+
test_expect_success $PREREQ 'detects ambiguous reference/file conflict' '
echo master > master &&
git add master &&
head=`git rev-parse --verify refs/heads/git-svn-HEAD^0`
test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'"
+svnrepo_escaped=`echo $svnrepo | sed 's/ /%20/'`
+
test_expect_success 'initialize old-style (v0) git svn layout' '
mkdir -p "$GIT_DIR"/git-svn/info "$GIT_DIR"/svn/info &&
echo "$svnrepo" > "$GIT_DIR"/git-svn/info/url &&
echo "$svnrepo" > "$GIT_DIR"/svn/info/url &&
git svn migrate &&
- ! test -d "$GIT_DIR"/git svn &&
+ ! test -d "$GIT_DIR"/git-svn &&
git rev-parse --verify refs/${remotes_git_svn}^0 &&
git rev-parse --verify refs/remotes/svn^0 &&
- test "$(git config --get svn-remote.svn.url)" = "$svnrepo" &&
+ test "$(git config --get svn-remote.svn.url)" = "$svnrepo_escaped" &&
test `git config --get svn-remote.svn.fetch` = \
":refs/${remotes_git_svn}"
'
start_httpd
'
+# SVN 1.7 will truncate "not-a%40{0]" to just "not-a".
+# Look at what SVN wound up naming the branch and use that.
+# Be sure to escape the @ if it shows up.
+non_reflog=`svn_cmd ls "$svnrepo/pr ject/branches" | grep not-a | sed 's/\///' | sed 's/@/%40/'`
+
test_expect_success 'test clone with funky branch names' '
git svn clone -s "$svnrepo/pr ject" project &&
(
git rev-parse "refs/remotes/%2Eleading_dot" &&
git rev-parse "refs/remotes/trailing_dot%2E" &&
git rev-parse "refs/remotes/trailing_dotlock%2Elock" &&
- git rev-parse "refs/remotes/not-a%40{0}reflog"
+ git rev-parse "refs/remotes/$non_reflog"
)
'
# capital letters by convention).
test_set_prereq () {
- satisfied="$satisfied$1 "
+ satisfied_prereq="$satisfied_prereq$1 "
+}
+satisfied_prereq=" "
+lazily_testable_prereq= lazily_tested_prereq=
+
+# Usage: test_lazy_prereq PREREQ 'script'
+test_lazy_prereq () {
+ lazily_testable_prereq="$lazily_testable_prereq$1 "
+ eval test_prereq_lazily_$1=\$2
+}
+
+test_run_lazy_prereq_ () {
+ script='
+mkdir -p "$TRASH_DIRECTORY/prereq-test-dir" &&
+(
+ cd "$TRASH_DIRECTORY/prereq-test-dir" &&'"$2"'
+)'
+ say >&3 "checking prerequisite: $1"
+ say >&3 "$script"
+ test_eval_ "$script"
+ eval_ret=$?
+ rm -rf "$TRASH_DIRECTORY/prereq-test-dir"
+ if test "$eval_ret" = 0; then
+ say >&3 "prerequisite $1 ok"
+ else
+ say >&3 "prerequisite $1 not satisfied"
+ fi
+ return $eval_ret
}
-satisfied=" "
test_have_prereq () {
# prerequisites can be concatenated with ','
for prerequisite
do
+ case " $lazily_tested_prereq " in
+ *" $prerequisite "*)
+ ;;
+ *)
+ case " $lazily_testable_prereq " in
+ *" $prerequisite "*)
+ eval "script=\$test_prereq_lazily_$prerequisite" &&
+ if test_run_lazy_prereq_ "$prerequisite" "$script"
+ then
+ test_set_prereq $prerequisite
+ fi
+ lazily_tested_prereq="$lazily_tested_prereq$prerequisite "
+ esac
+ ;;
+ esac
+
total_prereq=$(($total_prereq + 1))
- case $satisfied in
+ case "$satisfied_prereq" in
*" $prerequisite "*)
ok_prereq=$(($ok_prereq + 1))
;;
$GIT_TEST_CMP "$@"
}
+# Print a sequence of numbers or letters in increasing order. This is
+# similar to GNU seq(1), but the latter might not be available
+# everywhere (and does not do letters). It may be used like:
+#
+# for i in `test_seq 100`; do
+# for j in `test_seq 10 20`; do
+# for k in `test_seq a z`; do
+# echo $i-$j-$k
+# done
+# done
+# done
+
+test_seq () {
+ case $# in
+ 1) set 1 "$@" ;;
+ 2) ;;
+ *) error "bug in the test script: not 1 or 2 parameters to test_seq" ;;
+ esac
+ "$PERL_PATH" -le 'print for $ARGV[0]..$ARGV[1]' -- "$@"
+}
+
# This function can be used to schedule some commands to be run
# unconditionally at the end of the test to restore sanity:
#
fi
}
-# test whether the filesystem supports symbolic links
-ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
-rm -f y
+test_lazy_prereq SYMLINKS '
+ # test whether the filesystem supports symbolic links
+ ln -s x y && test -h y
+'
+
+test_lazy_prereq CASE_INSENSITIVE_FS '
+ echo good >CamelCase &&
+ echo bad >camelcase &&
+ test "$(cat CamelCase)" != good
+'
+
+test_lazy_prereq UTF8_NFD_TO_NFC '
+ # check whether FS converts nfd unicode to nfc
+ auml=$(printf "\303\244")
+ aumlcdiar=$(printf "\141\314\210")
+ >"$auml" &&
+ case "$(echo *)" in
+ "$aumlcdiar")
+ true ;;
+ *)
+ false ;;
+ esac
+'
# When the tests are run as root, permission tests will report that
# things are writable when they shouldn't be.
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
opt->change(opt, mode1, mode2,
- sha1, sha2, base->buf, 0, 0);
+ sha1, sha2, 1, 1, base->buf, 0, 0);
}
strbuf_addch(base, '/');
diff_tree_sha1(sha1, sha2, base->buf, opt);
} else {
- opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0);
+ opt->change(opt, mode1, mode2, sha1, sha2, 1, 1, base->buf, 0, 0);
}
strbuf_setlen(base, old_baselen);
return 0;
die("corrupt tree sha %s", sha1_to_hex(sha1));
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
- opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0);
+ opt->add_remove(opt, *prefix, mode, sha1, 1, base->buf, 0);
strbuf_addch(base, '/');
show_tree(opt, prefix, &inner, base);
free(tree);
} else
- opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0);
+ opt->add_remove(opt, prefix[0], mode, sha1, 1, base->buf, 0);
strbuf_setlen(base, old_baselen);
}
diff_opts.rename_score = opt->rename_score;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("unable to set up diff options to follow renames");
+ diff_setup_done(&diff_opts);
diff_tree(t1, t2, base, &diff_opts);
diffcore_std(&diff_opts);
diff_tree_release_paths(&diff_opts);
#include "list-objects.h"
#include "run-command.h"
#include "sigchain.h"
+#include "version.h"
static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>";
}
if (capabilities)
- packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname_nons,
+ packet_write(1, "%s %s%c%s%s agent=%s\n",
+ sha1_to_hex(sha1), refname_nons,
0, capabilities,
- stateless_rpc ? " no-done" : "");
+ stateless_rpc ? " no-done" : "",
+ git_user_agent_sanitized());
else
packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname_nons);
capabilities = NULL;
#include "git-compat-util.h"
#include "version.h"
+#include "strbuf.h"
const char git_version_string[] = GIT_VERSION;
return agent;
}
+
+const char *git_user_agent_sanitized(void)
+{
+ static const char *agent = NULL;
+
+ if (!agent) {
+ struct strbuf buf = STRBUF_INIT;
+ int i;
+
+ strbuf_addstr(&buf, git_user_agent());
+ strbuf_trim(&buf);
+ for (i = 0; i < buf.len; i++) {
+ if (buf.buf[i] <= 32 || buf.buf[i] >= 127)
+ buf.buf[i] = '.';
+ }
+ agent = buf.buf;
+ }
+
+ return agent;
+}
extern const char git_version_string[];
const char *git_user_agent(void);
+const char *git_user_agent_sanitized(void);
#endif /* VERSION_H */