author | Junio C Hamano <gitster@pobox.com> | |
Sat, 28 Feb 2009 22:39:56 +0000 (14:39 -0800) | ||
committer | Junio C Hamano <gitster@pobox.com> | |
Sat, 28 Feb 2009 22:39:56 +0000 (14:39 -0800) |
* maint:
Documentation: minor grammatical fixes.
added missing backtick in git-apply.txt
Documentation: minor grammatical fixes.
added missing backtick in git-apply.txt
308 files changed:
diff --git a/.gitignore b/.gitignore
index d9adce585af99e617a2906a89029a92045a79538..1c57d4c958bc5e8ff539c5f5ddb1c784d923271b 100644 (file)
--- a/.gitignore
+++ b/.gitignore
gitk-wish
gitweb/gitweb.cgi
test-chmtime
+test-ctype
test-date
test-delta
test-dump-cache-tree
test-parse-options
test-path-utils
test-sha1
+test-sigchain
common-cmds.h
*.tar.gz
*.dsc
index f628c1f3b7b706f9d585b96041e5a4b12bc0f62c..0d7fa9cca9e5c3ec8a11cd2c878f5a7afe353abe 100644 (file)
As for more concrete guidelines, just imitate the existing code
(this is a good guideline, no matter which project you are
-contributing to). But if you must have a list of rules,
-here they are.
+contributing to). It is always preferable to match the _local_
+convention. New code added to git suite is expected to match
+the overall style of existing code. Modifications to existing
+code is expected to match the style the surrounding code already
+uses (even if it doesn't match the overall style of existing code).
+
+But if you must have a list of rules, here they are.
For shell scripts specifically (not exhaustive):
diff --git a/Documentation/Makefile b/Documentation/Makefile
index c34c1cae2072fa27de32f553866426bbc01436d5..144ec32f12a7950746dd71e2ddde5b3f0cab57b0 100644 (file)
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
prefix?=$(HOME)
bindir?=$(prefix)/bin
htmldir?=$(prefix)/share/doc/git-doc
+pdfdir?=$(prefix)/share/doc/git-doc
mandir?=$(prefix)/share/man
man1dir=$(mandir)/man1
man5dir=$(mandir)/man5
MAKEINFO=makeinfo
INSTALL_INFO=install-info
DOCBOOK2X_TEXI=docbook2x-texi
+DBLATEX=dblatex
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
info: git.info gitman.info
+pdf: user-manual.pdf
+
install: install-man
install-man: man
echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
fi
+install-pdf: pdf
+ $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir)
+ $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir)
+
install-html: html
sh ./install-webdoc.sh $(DESTDIR)$(htmldir)
user-manual.texi: user-manual.xml
$(RM) $@+ $@
- $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout | $(PERL_PATH) fix-texi.perl >$@+
+ $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout | \
+ $(PERL_PATH) fix-texi.perl >$@+
+ mv $@+ $@
+
+user-manual.pdf: user-manual.xml
+ $(RM) $@+ $@
+ $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $<
mv $@+ $@
gitman.texi: $(MAN_XML) cat-texi.perl
$(RM) $@+ $@
- ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --to-stdout $(xml);)) | \
- $(PERL_PATH) cat-texi.perl $@ >$@+
+ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
+ --to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+
mv $@+ $@
gitman.info: gitman.texi
- $(MAKEINFO) --no-split $*.texi
+ $(MAKEINFO) --no-split --no-validate $*.texi
$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
$(RM) $@+ $@
diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt
--- /dev/null
@@ -0,0 +1,170 @@
+GIT v1.6.2 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default. You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing. Please refer to:
+
+ http://git.or.cz/gitwiki/GitFaq#non-bare
+ http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning. You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.1
+--------------------
+
+(subsystems)
+
+* git-svn updates.
+
+* gitweb updates, including a new patch view and RSS/Atom feed
+ improvements.
+
+* (contrib/emacs) git.el now has commands for checking out a branch,
+ creating a branch, cherry-picking and reverting commits; vc-git.el
+ is not shipped with git anymore (it is part of official Emacs).
+
+(performance)
+
+* pack-objects autodetects the number of CPUs available and uses threaded
+ version.
+
+(usability, bells and whistles)
+
+* automatic typo correction works on aliases as well
+
+* @{-1} is a way to refer to the last branch you were on. This is
+ accepted not only where an object name is expected, but anywhere
+ a branch name is expected and acts as if you typed the branch name.
+ E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and
+ "git rev-parse --symbolic-full-name @{-1}" would work as expected.
+
+* When refs/remotes/origin/HEAD points at a remote tracking branch that
+ has been pruned away, many git operations issued warning when they
+ internally enumerated the refs. We now warn only when you say "origin"
+ to refer to that pruned branch.
+
+* The location of .mailmap file can be configured, and its file format was
+ enhanced to allow mapping an incorrect e-mail field as well.
+
+* "git add -p" learned 'g'oto action to jump directly to a hunk.
+
+* "git add -p" learned to find a hunk with given text with '/'.
+
+* "git add -p" optionally can be told to work with just the command letter
+ without Enter.
+
+* when "git am" stops upon a patch that does not apply, it shows the
+ title of the offending patch.
+
+* "git am --directory=<dir>" and "git am --reject" passes these options
+ to underlying "git apply".
+
+* "git am" learned --ignore-date option.
+
+* "git blame" aligns author names better when they are spelled in
+ non US-ASCII encoding.
+
+* "git clone" now makes its best effort when cloning from an empty
+ repository to set up configuration variables to refer to the remote
+ repository.
+
+* "git checkout -" is a shorthand for "git checkout @{-1}".
+
+* "git cherry" defaults to whatever the current branch is tracking (if
+ exists) when the <upstream> argument is not given.
+
+* "git cvsserver" can be told not to add extra "via git-CVS emulator" to
+ the commit log message it serves via gitcvs.commitmsgannotation
+ configuration.
+
+* "git cvsserver" learned to handle 'noop' command some CVS clients seem
+ to expect to work.
+
+* "git diff" learned a new option --inter-hunk-context to coalesce close
+ hunks together and show context between them.
+
+* The definition of what constitutes a word for "git diff --color-words"
+ can be customized via gitattributes, command line or a configuration.
+
+* "git diff" learned --patience to run "patience diff" algorithm.
+
+* "git filter-branch" learned --prune-empty option that discards commits
+ that do not change the contents.
+
+* "git fsck" now checks loose objects in alternate object stores, instead
+ of misreporting them as missing.
+
+* "git gc --prune" was resurrected to allow "git gc --no-prune" and
+ giving non-default expiration period e.g. "git gc --prune=now".
+
+* "git grep -w" and "git grep" for fixed strings have been optimized.
+
+* "git mergetool" learned -y(--no-prompt) option to disable prompting.
+
+* "git rebase -i" can transplant a history down to root to elsewhere
+ with --root option.
+
+* "git reset --merge" is a new mode that works similar to the way
+ "git checkout" switches branches, taking the local changes while
+ switching to another commit.
+
+* "git submodule update" learned --no-fetch option.
+
+* "git tag" learned --contains that works the same way as the same option
+ from "git branch".
+
+
+Fixes since v1.6.1
+------------------
+
+All of the fixes in v1.6.1.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.1.X series.
+
+* "git-add sub/file" when sub is a submodule incorrectly added the path to
+ the superproject.
+
+* "git bundle" did not exclude annotated tags even when a range given
+ from the command line wanted to.
+
+* "git filter-branch" unnecessarily refused to work when you had
+ checked out a different commit from what is recorded in the superproject
+ index in a submodule.
+
+* "git filter-branch" incorrectly tried to update a nonexistent work tree
+ at the end when it is run in a bare repository.
+
+* "git gc" did not work if your repository was created with an ancient git
+ and never had any pack files in it before.
+
+* "git mergetool" used to ignore autocrlf and other attributes
+ based content rewriting.
+
+* branch switching and merges had a silly bug that did not validate
+ the correct directory when making sure an existing subdirectory is
+ clean.
+
+* "git -p cmd" when cmd is not a built-in one left the display in funny state
+ when killed in the middle.
+
+--
+exec >/var/tmp/1
+O=v1.6.2-rc2
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
index ba07c8c57107a8438d5b3ce1a49761dba7182d25..9b559adefce5c9a89e58d30ae79820a9ef2e9072 100644 (file)
(A Large Angry SCM)
+By default, Thunderbird will both wrap emails as well as flag them as
+being 'format=flowed', both of which will make the resulting email unusable
+by git.
+
Here are some hints on how to successfully submit patches inline using
Thunderbird.
+There are two different approaches. One approach is to configure
+Thunderbird to not mangle patches. The second approach is to use
+an external editor to keep Thunderbird from mangling the patches.
+
+Approach #1 (configuration):
+
+This recipe is current as of Thunderbird 2.0.0.19. Three steps:
+ 1. Configure your mail server composition as plain text
+ Edit...Account Settings...Composition & Addressing,
+ uncheck 'Compose Messages in HTML'.
+ 2. Configure your general composition window to not wrap
+ Edit..Preferences..Composition, wrap plain text messages at 0
+ 3. Disable the use of format=flowed
+ Edit..Preferences..Advanced..Config Editor. Search for:
+ mailnews.send_plaintext_flowed
+ toggle it to make sure it is set to 'false'.
+
+After that is done, you should be able to compose email as you
+otherwise would (cut + paste, git-format-patch | git-imap-send, etc),
+and the patches should not be mangled.
+
+Approach #2 (external editor):
+
This recipe appears to work with the current [*1*] Thunderbird from Suse.
The following Thunderbird extensions are needed:
index dbc133cd3c1f19dd507014477e68b8ada78eab5e..828ec62554fe927eb16a03f835448e6db0c303a1 100755 (executable)
printf '\input texinfo
@setfilename gitman.info
-@documentencoding us-ascii
-@node Top,,%s
+@documentencoding UTF-8
+@dircategory Development
+@direntry
+* Git Man Pages: (gitman). Manual pages for Git revision control system
+@end direntry
+@node Top,,, (dir)
@top Git Manual Pages
@documentlanguage en
@menu
index 2ed868c81a09af9176bd805f819c0b312556891a..f5152c5038b49ab5ebe3804e8e9b314d4a752690 100644 (file)
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
color.interactive.<slot>::
Use customized color for 'git-add --interactive'
- output. `<slot>` may be `prompt`, `header`, or `help`, for
- three distinct types of normal output from interactive
+ output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
+ four distinct types of normal output from interactive
programs. The values of these variables may be specified as
in color.branch.<slot>.
A boolean to inhibit the standard behavior of printing a space
before each empty output line. Defaults to false.
+diff.wordRegex::
+ A POSIX Extended Regular Expression used to determine what is a "word"
+ when performing word-by-word difference calculations. Character
+ sequences that match the regular expression are "words", all other
+ characters are *ignorable* whitespace.
+
fetch.unpackLimit::
If the number of objects fetched over the git native
transfer is below this
kept for this many days when 'git-rerere gc' is run.
The default is 15 days. See linkgit:git-rerere[1].
+gitcvs.commitmsgannotation::
+ Append this string to each commit message. Set to empty string
+ to disable this feature. Defaults to "via git-CVS emulator".
+
gitcvs.enabled::
Whether the CVS server interface is enabled for this repository.
See linkgit:git-cvsserver[1].
The port number to bind the gitweb httpd to. See
linkgit:git-instaweb[1].
+interactive.singlekey::
+ In interactive programs, allow the user to provide one-letter
+ input with a single key (i.e., without hitting enter).
+ Currently this is used only by the `\--patch` mode of
+ linkgit:git-add[1]. Note that this setting is silently
+ ignored if portable keystroke input is not available.
+
log.date::
Set default date-time mode for the log command. Setting log.date
value is similar to using 'git-log'\'s --date option. The value is one of the
Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
normally hide the root commit will now show it. True by default.
+mailmap.file::
+ The location of an augmenting mailmap file. The default
+ mailmap, located in the root of the repository, is loaded
+ first, then the mailmap file pointed to by this variable.
+ The location of the mailmap file may be in a repository
+ subdirectory, or somewhere outside of the repository itself.
+ See linkgit:git-shortlog[1] and linkgit:git-blame[1].
+
man.viewer::
Specify the programs that may be used to display help in the
'man' format. See linkgit:git-help[1].
is set to `false` then this file is not preserved. Defaults to
`true` (i.e. keep the backup files).
+mergetool.keepTemporaries::
+ When invoking a custom merge tool, git uses a set of temporary
+ files to pass to the tool. If the tool returns an error and this
+ variable is set to `true`, then these temporary files will be
+ preserved, otherwise they will be removed after the tool has
+ exited. Defaults to `false`.
+
+mergetool.prompt::
+ Prompt before each invocation of the merge resolution program.
+
pack.window::
The size of the window used by linkgit:git-pack-objects[1] when no
window size is given on the command line. Defaults to 10.
index b432d2518aba3d1be9d86fb1613bbe2f5fa9da4d..813a7b11b99d51d3014f854770384ed4fc247c9f 100644 (file)
--patch-with-raw::
Synonym for "-p --raw".
+--patience::
+ Generate a diff using the "patience diff" algorithm.
+
--stat[=width[,name-width]]::
Generate a diffstat. You can override the default
output width for 80-column terminal by "--stat=width".
Turn off colored diff, even when the configuration file
gives the default to color output.
---color-words::
- Show colored word diff, i.e. color words which have changed.
+--color-words[=<regex>]::
+ Show colored word diff, i.e., color words which have changed.
+ By default, words are separated by whitespace.
++
+When a <regex> is specified, every non-overlapping match of the
+<regex> is considered a word. Anything between these matches is
+considered whitespace and ignored(!) for the purposes of finding
+differences. You may want to append `|[^[:space:]]` to your regular
+expression to make sure that it matches all non-whitespace characters.
+A match that contains a newline is silently truncated(!) at the
+newline.
++
+The regex can also be set via a diff driver or configuration option, see
+linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly
+overrides any diff driver or configuration setting. Diff drivers
+override configuration settings.
--no-renames::
Turn off rename detection, even when the configuration
--abbrev[=<n>]::
Instead of showing the full 40-byte hexadecimal object
name in diff-raw format output and diff-tree header
- lines, show only handful hexdigits prefix. This is
+ lines, show only a partial prefix. This is
independent of --full-index option above, which controls
the diff-patch output format. Non default number of
digits can be specified with --abbrev=<n>.
differences even if one line has whitespace where the other
line has none.
+--inter-hunk-context=<lines>::
+ Show the context between diff hunks, up to the specified number
+ of lines, thereby fusing hunks that are close to each other.
+
--exit-code::
Make the program exit with codes similar to diff(1).
That is, it exits with 1 if there were differences and
index b9c6fac7483dbefba0afb60a76ac0362aa390a6d..ff307eb27098fd89f718055f2211164da3e0be30 100644 (file)
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
--------
[verse]
'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
- [--3way] [--interactive]
- [--whitespace=<option>] [-C<n>] [-p<n>]
+ [--3way] [--interactive] [--committer-date-is-author-date]
+ [--ignore-date]
+ [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
+ [--reject]
[<mbox> | <Maildir>...]
'git am' (--skip | --resolved | --abort)
available locally.
--whitespace=<option>::
- This flag is passed to the 'git-apply' (see linkgit:git-apply[1])
- program that applies
- the patch.
-
-C<n>::
-p<n>::
+--directory=<dir>::
+--reject::
These flags are passed to the 'git-apply' (see linkgit:git-apply[1])
program that applies
the patch.
--interactive::
Run interactively.
+--committer-date-is-author-date::
+ By default the command records the date from the e-mail
+ message as the commit author date, and uses the time of
+ commit creation as the committer date. This allows the
+ user to lie about the committer date by using the same
+ timestamp as the author date.
+
+--ignore-date::
+ By default the command records the date from the e-mail
+ message as the commit author date, and uses the time of
+ commit creation as the committer date. This allows the
+ user to lie about author timestamp by using the same
+ timestamp as the committer date.
+
--skip::
Skip the current patch. This is only meaningful when
restarting an aborted patch.
index 32f2b85a105e684c7c04f8825b8b74c511271cef..05663768d75aea42054d6b869e08a3c1e7e0850e 100644 (file)
--------
[verse]
'git apply' [--stat] [--numstat] [--summary] [--check] [--index]
- [--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
+ [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
[--allow-binary-replacement | --binary] [--reject] [-z]
[-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
[--whitespace=<nowarn|warn|fix|error|error-all>]
cached data, apply the patch, and store the result in the index,
without using the working tree. This implies '--index'.
---build-fake-ancestor <file>::
+--build-fake-ancestor=<file>::
Newer 'git-diff' output has embedded 'index information'
for each blob to help identify the original version that
the patch applies to. When this flag is given, and if
index cc934e55c38902e1bac0964e134640c910b9de96..4ef54d660280c921913a61afe117968d51960ec3 100644 (file)
commit commentary), a blame viewer won't ever care.
+MAPPING AUTHORS
+---------------
+
+include::mailmap.txt[]
+
+
SEE ALSO
--------
linkgit:git-annotate[1]
index 1b66ab743c64d980a43a028d57ca2f6505d97845..57590b148011c44485d28d93d31c6a75a681bfb0 100644 (file)
than one basis can be specified. The objects packaged are those not
contained in the union of the given bases. Each basis can be
specified explicitly (e.g., ^master~10), or implicitly (e.g.,
-master~10..master, master --since=10.days.ago).
+master~10..master, --since=10.days.ago master).
It is very important that the basis used be held by the destination.
It is okay to err on the side of conservatism, causing the bundle file
EXAMPLE
-------
-Assume two repositories exist as R1 on machine A, and R2 on machine B.
+Assume you want to transfer the history from a repository R1 on machine A
+to another repository R2 on machine B.
For whatever reason, direct connection between A and B is not allowed,
but we can move data from A to B via some mechanism (CD, email, etc).
We want to update R2 with developments made on branch master in R1.
-To create the bundle you have to specify the basis. You have some options:
+To bootstrap the process, you can first create a bundle that doesn't have
+any basis. You can use a tag to remember up to what commit you sent out
+in order to make it easy to later update the other repository with
+incremental bundle,
-- Without basis.
-+
-This is useful when sending the whole history.
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle master
+machineA$ git tag -f lastR2bundle master
+----------------
-------------
-$ git bundle create mybundle master
-------------
+Then you sneakernet file.bundle to the target machine B. Because you don't
+have to have any object to extract objects from such a bundle, not only
+you can fetch/pull from a bundle, you can clone from it as if it was a
+remote repository.
-- Using temporally tags.
-+
-We set a tag in R1 (lastR2bundle) after the previous such transport,
-and move it afterwards to help build the bundle.
+----------------
+machineB$ git clone /home/me/tmp/file.bundle R2
+----------------
-------------
-$ git bundle create mybundle master ^lastR2bundle
-$ git tag -f lastR2bundle master
-------------
+This will define a remote called "origin" in the resulting repository that
+lets you fetch and pull from the bundle. $GIT_DIR/config file in R2 may
+have an entry like this:
-- Using a tag present in both repositories
+------------------------
+[remote "origin"]
+ url = /home/me/tmp/file.bundle
+ fetch = refs/heads/*:refs/remotes/origin/*
+------------------------
+
+You can fetch/pull to update the resulting mine.git repository after
+replacing the bundle you store at /home/me/tmp/file.bundle with incremental
+updates from here on.
+
+After working more in the original repository, you can create an
+incremental bundle to update the other:
+
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle lastR2bundle..master
+machineA$ git tag -f lastR2bundle master
+----------------
+
+and sneakernet it to the other machine to replace /home/me/tmp/file.bundle,
+and pull from it.
+
+----------------
+machineB$ cd R2
+machineB$ git pull
+----------------
-------------
-$ git bundle create mybundle master ^v1.0.0
-------------
+If you know up to what commit the intended recipient repository should
+have the necessary objects for, you can use that knowledge to specify the
+basis, giving a cut-off point to limit the revisions and objects that go
+in the resulting bundle. The previous example used lastR2bundle tag
+for this purpose, but you can use other options you would give to
+the linkgit:git-log[1] command. Here are more examples:
-- A basis based on time.
+You can use a tag that is present in both.
-------------
-$ git bundle create mybundle master --since=10.days.ago
-------------
+----------------
+$ git bundle create mybundle v1.0.0..master
+----------------
-- With a limit on the number of commits
+You can use a basis based on time.
-------------
-$ git bundle create mybundle master -n 10
-------------
+----------------
+$ git bundle create mybundle --since=10.days master
+----------------
-Then you move mybundle from A to B, and in R2 on B:
+Or you can use the number of commits.
-------------
+----------------
+$ git bundle create mybundle -10 master
+----------------
+
+You can run `git-bundle verify` to see if you can extract from a bundle
+that was created with a basis.
+
+----------------
$ git bundle verify mybundle
-$ git fetch mybundle master:localRef
-------------
+----------------
-With something like this in the config in R2:
+This will list what commits you must have in order to extract from the
+bundle and will error out if you don't have them.
-------------------------
-[remote "bundle"]
- url = /home/me/tmp/file.bdl
- fetch = refs/heads/*:refs/remotes/origin/*
-------------------------
+A bundle from a recipient repository's point of view is just like a
+regular repository it fetches/pulls from. You can for example map
+refs, like this example, when fetching:
-You can first sneakernet the bundle file to ~/tmp/file.bdl and
-then these commands on machine B:
+----------------
+$ git fetch mybundle master:localRef
+----------------
-------------
-$ git ls-remote bundle
-$ git fetch bundle
-$ git pull bundle
-------------
+Or see what refs it offers.
-would treat it as if it is talking with a remote side over the
-network.
+----------------
+$ git ls-remote mybundle
+----------------
Author
------
index 9cd51514dbfd987db4423e254e8bab7284352525..3bccffae628e065a6cd92155a9283d5710991fd7 100644 (file)
+
When this parameter names a non-branch (but still a valid commit object),
your HEAD becomes 'detached'.
++
+As a special case, the "`@\{-N\}`" syntax for the N-th last branch
+checks out the branch (instead of detaching). You may also specify
+"`-`" which is synonymous with "`@\{-1\}`".
Detached HEAD
index 74d14c4e7fc88e702e4781b22eb8ed5ce0480c6f..7deefdae8f995d843971f6beff24af8325b99f1f 100644 (file)
SYNOPSIS
--------
-'git cherry' [-v] <upstream> [<head>] [<limit>]
+'git cherry' [-v] [<upstream> [<head> [<limit>]]]
DESCRIPTION
-----------
<upstream>::
Upstream branch to compare against.
+ Defaults to the first tracked remote branch, if available.
<head>::
Working branch; defaults to HEAD.
index a30c5ac96618f5010388edd66815ec97971a92e0..b231dbb947791bb4fc5cde552e8c736b3558ca0a 100644 (file)
v1.0.4-14-g2414721
i.e. the current head of my "parent" branch is based on v1.0.4,
-but since it has a handful commits on top of that,
+but since it has a few commits on top of that,
describe has added the number of additional commits ("14") and
an abbreviated object name for the commit itself ("2414721")
at the end.
index fed6de6a7fa0e720994f7094be2f464818af289a..7ffe03f4279a8ca8110806260f33a58f849351e4 100644 (file)
convenience functions, too. For example, calling 'skip_commit "$@"'
will leave out the current commit (but not its changes! If you want
that, use 'git-rebase' instead).
++
+You can also use the 'git_commit_non_empty_tree "$@"' instead of
+'git commit-tree "$@"' if you don't wish to keep commits with a single parent
+and that makes no change to the tree.
--tag-name-filter <command>::
This is the filter for rewriting tag names. When passed,
The result will contain that directory (and only that) as its
project root.
+--prune-empty::
+ Some kind of filters will generate empty commits, that left the tree
+ untouched. This switch allow git-filter-branch to ignore such
+ commits. Though, this switch only applies for commits that have one
+ and only one parent, it will hence keep merges points. Also, this
+ option is not compatible with the use of '--commit-filter'. Though you
+ just need to use the function 'git_commit_non_empty_tree "$@"' instead
+ of the 'git commit-tree "$@"' idiom in your commit filter to make that
+ happen.
+
--original <namespace>::
Use this option to set the namespace where the original commits
will be stored. The default value is 'refs/original'.
Now, you will get the rewritten history saved in HEAD.
+As with using `rm filename`, `git rm --cached filename` will fail
+if the file is absent from the tree of a commit. If it is not important
+whether the file is already absent from the tree, you can use
+`git rm --cached --ignore-unmatch filename` instead.
+
To rewrite the repository to look as if `foodir/` had been its project
root, and discard all other history:
---------------------------------------------------------------
+
+Checklist for Shrinking a Repository
+------------------------------------
+
+git-filter-branch is often used to get rid of a subset of files,
+usually with some combination of `\--index-filter` and
+`\--subdirectory-filter`. People expect the resulting repository to
+be smaller than the original, but you need a few more steps to
+actually make it smaller, because git tries hard not to lose your
+objects until you tell it to. First make sure that:
+
+* You really removed all variants of a filename, if a blob was moved
+ over its lifetime. `git log \--name-only \--follow \--all \--
+ filename` can help you find renames.
+
+* You really filtered all refs: use `\--tag-name-filter cat \--
+ \--all` when calling git-filter-branch.
+
+Then there are two ways to get a smaller repository. A safer way is
+to clone, that keeps your original intact.
+
+* Clone it with `git clone +++file:///path/to/repo+++`. The clone
+ will not have the removed objects. See linkgit:git-clone[1]. (Note
+ that cloning with a plain path just hardlinks everything!)
+
+If you really don't want to clone it, for whatever reasons, check the
+following points instead (in this order). This is a very destructive
+approach, so *make a backup* or go back to cloning it. You have been
+warned.
+
+* Remove the original refs backed up by git-filter-branch: say `git
+ for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git
+ update-ref -d`.
+
+* Expire all reflogs with `git reflog expire \--expire=now \--all`.
+
+* Garbage collect all unreferenced objects with `git gc \--prune=now`
+ (or if your git-gc is not new enough to support arguments to
+ `\--prune`, use `git repack -ad; git prune` instead).
+
+
Author
------
Written by Petr "Pasky" Baudis <pasky@suse.cz>,
index 7086eea74a38b036130f61db362bae209a065e47..b292e9843aa9da86cd44bd07d3ce35053be32177 100644 (file)
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
SYNOPSIS
--------
-'git gc' [--aggressive] [--auto] [--quiet]
+'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune]
DESCRIPTION
-----------
'git-repack'. Setting `gc.autopacklimit` to 0 disables
automatic consolidation of packs.
+--prune=<date>::
+ Prune loose objects older than date (default is 2 weeks ago,
+ overrideable by the config variable `gc.pruneExpire`). This
+ option is on by default.
+
+--no-prune::
+ Do not prune any loose objects.
+
--quiet::
Suppress all progress reports.
index bd49a0aee8881f983077d74bd8d0fbe5764dfea5..1685f04efea3d162377325406e08a762dad94d40 100644 (file)
..........................
+CAUTION
+-------
+It is still your responsibility to make sure that the email message
+sent by your email program meets the standards of your project.
+Many projects do not like patches to be attached. Some mail
+agents will transform patches (e.g. wrap lines, send them as
+format=flowed) in ways that make them fail. You will get angry
+flames ridiculing you if you don't check this.
+
+Thunderbird in particular is known to be problematic. Thunderbird
+users may wish to visit this web page for more information:
+ http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email
+
+
BUGS
----
Doesn't handle lines starting with "From " in the message body.
index 9f85d60b5fb6d6ae1b4d8c2e65a6131cbe21450b..057a021eb50899ed5fe1ee5a7b6c59e7fc27becf 100644 (file)
--abbrev[=<n>]::
Instead of showing the full 40-byte hexadecimal object
- lines, show only handful hexdigits prefix.
+ lines, show only a partial prefix.
Non default number of digits can be specified with --abbrev=<n>.
\--::
index db6ebccd6dc51d9f1463710f29a8f821f13fd412..f68e5c5c1abd15350e679eebf976f0ac8516123b 100644 (file)
--abbrev[=<n>]::
Instead of showing the full 40-byte hexadecimal object
- lines, show only handful hexdigits prefix.
+ lines, show only a partial prefix.
Non default number of digits can be specified with --abbrev=<n>.
--full-name::
index 602e7c6d3b497aa4f891915305d507ae88910a54..5d3c6328726ba1af829026dd97840fc43335cfe0 100644 (file)
SYNOPSIS
--------
-'git mergetool' [--tool=<tool>] [<file>]...
+'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>]...
DESCRIPTION
-----------
OPTIONS
-------
--t or --tool=<tool>::
+-t <tool>::
+--tool=<tool>::
Use the merge resolution program specified by <tool>.
Valid merge tools are:
kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
Otherwise, 'git-mergetool' will prompt the user to indicate the
success of the resolution after the custom tool has exited.
+-y::
+--no-prompt::
+ Don't prompt before each invocation of the merge resolution
+ program.
+
+--prompt::
+ Prompt before each invocation of the merge resolution program.
+ This is the default behaviour; the option is provided to
+ override any configuration settings.
+
Author
------
Written by Theodore Y Ts'o <tytso@mit.edu>
index ac6421178c1a62ca62fb335d9939ec36368c7e82..4e7e5a719a4b0447159213466d46aa2360b7408e 100644 (file)
be named. If `:`<dst> is omitted, the same ref as <src> will be
updated.
+
-The object referenced by <src> is used to fast forward the ref <dst>
-on the remote side. If the optional leading plus `{plus}` is used, the
-remote ref is updated even if it does not result in a fast forward
-update.
+The object referenced by <src> is used to update the <dst> reference
+on the remote side, but by default this is only allowed if the
+update can fast forward <dst>. By having the optional leading `{plus}`,
+you can tell git to update the <dst> ref even when the update is not a
+fast forward. This does *not* attempt to merge <src> into <dst>. See
+EXAMPLES below for details.
+
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
+
Pushing an empty <src> allows you to delete the <dst> ref from
the remote repository.
+
-The special refspec `:` (or `+:` to allow non-fast forward updates)
+The special refspec `:` (or `{plus}:` to allow non-fast forward updates)
directs git to push "matching" branches: for every branch that exists on
the local side, the remote side is updated if a branch of the same name
already exists on the remote side. This is the default operation mode
Find a ref that matches `experimental` in the `origin` repository
(e.g. `refs/heads/experimental`), and delete it.
+git push origin {plus}dev:master::
+ Update the origin repository's master branch with the dev branch,
+ allowing non-fast forward updates. *This can leave unreferenced
+ commits dangling in the origin repository.* Consider the
+ following situation, where a fast forward is not possible:
++
+----
+ o---o---o---A---B origin/master
+ \
+ X---Y---Z dev
+----
++
+The above command would change the origin repository to
++
+----
+ A---B (unnamed branch)
+ /
+ o---o---o---X---Y---Z master
+----
++
+Commits A and B would no longer belong to a branch with a symbolic name,
+and so would be unreachable. As such, these commits would be removed by
+a `git gc` command on the origin repository.
+
Author
------
index c8ad86a56fc6bff70cb6e7c74cc8ef10ef2da73e..da3c38cd60dc0d143fe91417b742335cfff27809 100644 (file)
SYNOPSIS
--------
[verse]
-'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
- [-s <strategy> | --strategy=<strategy>] [--no-verify]
- [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
- [--onto <newbase>] <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+ <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] --onto <newbase>
+ --root [<branch>]
+
'git rebase' --continue | --skip | --abort
DESCRIPTION
All changes made by commits in the current branch but that are not
in <upstream> are saved to a temporary area. This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`.
+of commits that would be shown by `git log <upstream>..HEAD` (or
+`git log HEAD`, if --root is specified).
The current branch is reset to <upstream>, or <newbase> if the
--onto option was supplied. This has the exact same effect as
context exist they all must match. By default no context is
ever ignored.
---whitespace=<nowarn|warn|error|error-all|strip>::
+--whitespace=<option>::
This flag is passed to the 'git-apply' program
(see linkgit:git-apply[1]) that applies the patch.
+ Incompatible with the --interactive option.
-i::
--interactive::
--preserve-merges::
Instead of ignoring merges, try to recreate them.
+--root::
+ Rebase all commits reachable from <branch>, instead of
+ limiting them with an <upstream>. This allows you to rebase
+ the root commit(s) on a branch. Must be used with --onto, and
+ will skip changes already contained in <newbase> (instead of
+ <upstream>). When used together with --preserve-merges, 'all'
+ root commits will be rewritten to have <newbase> as parent
+ instead.
+
include::merge-strategies.txt[]
NOTES
index 2049f3d97b67adc8fa93ca462875db6e522e1923..abb25d1c00c97144b1f3709e408fe9cad613e623 100644 (file)
SYNOPSIS
--------
[verse]
-'git reset' [--mixed | --soft | --hard] [-q] [<commit>]
+'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>]
'git reset' [-q] [<commit>] [--] <paths>...
DESCRIPTION
switched to. Any changes to tracked files in the working tree
since <commit> are lost.
+--merge::
+ Resets the index to match the tree recorded by the named commit,
+ and updates the files that are different between the named commit
+ and the current commit in the working tree.
+
-q::
Be quiet, only report errors.
brings your index file and the working tree back to that state,
and resets the tip of the branch to that commit.
+Undo a merge or pull inside a dirty work tree::
++
+------------
+$ git pull <1>
+Auto-merging nitfol
+Merge made by recursive.
+ nitfol | 20 +++++----
+ ...
+$ git reset --merge ORIG_HEAD <2>
+------------
++
+<1> Even if you may have local modifications in your
+working tree, you can safely say "git pull" when you know
+that the change in the other branch does not overlap with
+them.
+<2> After inspecting the result of the merge, you may find
+that the change in the other branch is unsatisfactory. Running
+"git reset --hard ORIG_HEAD" will let you go back to where you
+were, but it will discard your local changes, which you do not
+want. "git reset --merge" keeps your local changes.
+
+
Interrupted workflow::
+
Suppose you are interrupted by an urgent fix request while you
index 2921da320d2b84df4d15ec2745e6d94093dd6907..3ccef2f2b3295c6d5e14b8f3a9d354d7b57598da 100644 (file)
reflog of the current branch. For example, if you are on the
branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
+* The special construct '@\{-<n>\}' means the <n>th branch checked out
+ before the current one.
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
index 8f7c0e226df8f58712e6a0d78ecec58c96aa2453..42463a955dd3f5dad25f3a45f0eec62d8712f731 100644 (file)
and subsequent lines are indented by `indent2` spaces. `width`,
`indent1`, and `indent2` default to 76, 6 and 9 respectively.
-FILES
------
-
-If a file `.mailmap` exists at the toplevel of the repository,
-it is used to map an author email address to a canonical real name. This
-can be used to coalesce together commits by the same person where their
-name was spelled differently (whether with the same email address or
-not).
-
-Each line in the file consists, in this order, of the canonical real name
-of an author, whitespace, and an email address (enclosed by '<' and '>')
-to map to the name. Use hash '#' for comments, either on their own line,
-or after the email address.
-
-A canonical name may appear in more than one line, associated with
-different email addresses, but it doesn't make sense for a given address
-to appear more than once (if that happens, a later line overrides the
-earlier ones).
-
-So, for example, if your history contains commits by two authors, Jane
-and Joe, whose names appear in the repository under several forms:
-
-------------
-Joe Developer <joe@example.com>
-Joe R. Developer <joe@example.com>
-Jane Doe <jane@example.com>
-Jane Doe <jane@laptop.(none)>
-Jane D. <jane@desktop.(none)>
-------------
-
-Then, supposing Joe wants his middle name initial used, and Jane prefers
-her family name fully spelled out, a proper `.mailmap` file would look like:
-
-------------
-# Note how we don't need an entry for <jane@laptop.(none)>, because the
-# real name of that author is correct already, and coalesced directly.
-Jane Doe <jane@desktop.(none)>
-Joe R. Developer <joe@random.com>
-------------
+
+MAPPING AUTHORS
+---------------
+
+The `.mailmap` feature is used to coalesce together commits by the same
+person in the shortlog, where their name and/or email address was
+spelled differently.
+
+include::mailmap.txt[]
+
Author
------
index 8277577a6f896f019793382735a8c0f4a6d5e1c3..7e9ff3762b111d8740a99b676ea516435018f3a8 100644 (file)
will show the revisions given by "git rev-list {caret}master
topic1 topic2"
+-g::
--reflog[=<n>[,<base>]] [<ref>]::
Shows <n> most recent ref-log entries for the given
ref. If <base> is given, <n> entries going back from
that entry. <base> can be specified as count or date.
- `-g` can be used as a short-hand for this option. When
- no explicit <ref> parameter is given, it defaults to the
+ When no explicit <ref> parameter is given, it defaults to the
current branch (or `HEAD` if it is detached).
Note that --more, --list, --independent and --merge-base options
index 2f207fbbda04c176cedd5a88c88f74e2ac97cf2e..3b8df4467377d73d613f76875c725cbf8544ee77 100644 (file)
'git submodule' [--quiet] add [-b branch] [--] <repository> <path>
'git submodule' [--quiet] status [--cached] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
-'git submodule' [--quiet] update [--init] [--] [<path>...]
+'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [<path>...]
'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
'git submodule' [--quiet] foreach <command>
'git submodule' [--quiet] sync [--] [<path>...]
(the default). This limit only applies to modified submodules. The
size is always limited to 1 for added/deleted/typechanged submodules.
+-N::
+--no-fetch::
+ This option is only valid for the update command.
+ Don't fetch new objects from the remote site.
+
<path>...::
Paths to submodule(s). When specified this will restrict the command
to only operate on the submodules found at the specified paths.
index 8d0c421b80b5ff110d33583cb1bc5a3b417cad90..cda3389331edd615cab316574e4dedc5cc30bd31 100644 (file)
.git/config file may be specified as an optional command-line
argument.
+--localtime;;
+ Store Git commit times in the local timezone instead of UTC. This
+ makes 'git-log' (even without --date=local) show the same times
+ that `svn log` would in the local timezone.
+
+This doesn't interfere with interoperating with the Subversion
+repository you cloned from, but if you wish for your local Git
+repository to be able to interoperate with someone else's local Git
+repository, either don't use this option or you should both use it in
+the same local timezone.
+
+--ignore-paths=<regex>;;
+ This allows one to specify Perl regular expression that will
+ cause skipping of all matching paths from checkout from SVN.
+ Examples:
+
+ --ignore-paths="^doc" - skip "doc*" directory for every fetch.
+
+ --ignore-paths="^[^/]+/(?:branches|tags)" - skip "branches"
+ and "tags" of first level directories.
+
+ Regular expression is not persistent, you should specify
+ it every time when fetching.
+
'clone'::
Runs 'init' and 'fetch'. It will automatically create a
directory based on the basename of the URL passed to it;
reused if a user is later given access to an alternate transport
method (e.g. `svn+ssh://` or `https://`) for commit.
+config key: svn-remote.<name>.commiturl
+
+config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
+
Using this option for any other purpose (don't ask)
is very strongly discouraged.
--
the repository with a public http:// or svn:// URL in the
metadata so users of it will see the public URL.
+svn.brokenSymlinkWorkaround::
+This disables potentially expensive checks to workaround broken symlinks
+checked into SVN by broken clients. Set this option to "false" if you
+track a SVN repository with many empty blobs that are not symlinks.
+This option may be changed while "git-svn" is running and take effect on
+the next revision fetched. If unset, git-svn assumes this option to be
+"true".
+
--
Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
index e44f54302500172257fd9ea394f45707779cbac8..533d18bbd5634bf6197960f546a20772386c824a 100644 (file)
'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
<name> [<commit> | <object>]
'git tag' -d <name>...
-'git tag' [-n[<num>]] -l [<pattern>]
+'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
'git tag' -v <name>...
DESCRIPTION
List tags with names that match the given pattern (or all if no pattern is given).
Typing "git tag" without arguments, also lists all tags.
+--contains <commit>::
+ Only list tags which contain the specified commit.
+
-m <msg>::
Use the given tag message (instead of prompting).
If multiple `-m` options are given, their values are
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 17dc8b20192f2558775250436152d0e8acfde41e..0c7bba3fa98ed10c9c4857ed14e45fab5bba10cf 100644 (file)
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.6.1/git.html[documentation for release 1.6.1]
+* link:v1.6.1.3/git.html[documentation for release 1.6.1.3]
* release notes for
+ link:RelNotes-1.6.1.3.txt[1.6.1.3],
+ link:RelNotes-1.6.1.2.txt[1.6.1.2],
+ link:RelNotes-1.6.1.1.txt[1.6.1.1],
link:RelNotes-1.6.1.txt[1.6.1].
* link:v1.6.0.6/git.html[documentation for release 1.6.0.6]
index 8af22eccac85492416d36259baa60594c9b8382f..55668e345f9dd43f2f9850463503cda50f656641 100644 (file)
Each line in `gitattributes` file is of form:
- glob attr1 attr2 ...
+ pattern attr1 attr2 ...
-That is, a glob pattern followed by an attributes list,
-separated by whitespaces. When the glob pattern matches the
+That is, a pattern followed by an attributes list,
+separated by whitespaces. When the pattern matches the
path in question, the attributes listed on the line are given to
the path.
Unspecified::
- No glob pattern matches the path, and nothing says if
+ No pattern matches the path, and nothing says if
the path has or does not have the attribute, the
attribute for the path is said to be Unspecified.
-When more than one glob pattern matches the path, a later line
+When more than one pattern matches the path, a later line
overrides an earlier line. This overriding is done per
-attribute.
+attribute. The rules how the pattern matches paths are the
+same as in `.gitignore` files; see linkgit:gitignore[5].
When deciding what attributes are assigned to a path, git
consults `$GIT_DIR/info/attributes` file (which has the highest
- `bibtex` suitable for files with BibTeX coded references.
+- `cpp` suitable for source code in the C and C++ languages.
+
- `html` suitable for HTML/XHTML documents.
- `java` suitable for source code in the Java language.
- `tex` suitable for source code for LaTeX documents.
+Customizing word diff
+^^^^^^^^^^^^^^^^^^^^^
+
+You can customize the rules that `git diff --color-words` uses to
+split words in a line, by specifying an appropriate regular expression
+in the "diff.*.wordRegex" configuration variable. For example, in TeX
+a backslash followed by a sequence of letters forms a command, but
+several such commands can be run together without intervening
+whitespace. To separate them, use a regular expression such as
+
+------------------------
+[diff "tex"]
+ wordRegex = "\\\\[a-zA-Z]+|[{}]|\\\\.|[^\\{}[:space:]]+"
+------------------------
+
+A built-in pattern is provided for all languages listed in the
+previous section.
+
+
Performing text diffs of binary files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
index e4dd5518c81ac98ef3da9cbb1cea4a9e1fe6e62c..7ba5e589d7e824c526482c9707a5c26ac730cc9e 100644 (file)
------------
In our example of only two files, we did not have unchanged
-files so only 'example' resulted in collapsing, but in real-life
-large projects, only small number of files change in one commit,
-and this 'collapsing' tends to trivially merge most of the paths
-fairly quickly, leaving only a handful the real changes in non-zero
+files so only 'example' resulted in collapsing. But in real-life
+large projects, when only a small number of files change in one commit,
+this 'collapsing' tends to trivially merge most of the paths
+fairly quickly, leaving only a handful of real changes in non-zero
stages.
To look at only non-zero stages, use `\--unmerged` flag:
index 28a8abcf52b0c4180a132d4093b6b8a59c63c6fd..1fd512bca2e12f5eb0abac0edb0dfb2a86949cec 100644 (file)
Hooks are little scripts you can place in `$GIT_DIR/hooks`
directory to trigger action at certain points. When
-'git-init' is run, a handful example hooks are copied in the
+'git-init' is run, a handful of example hooks are copied into the
`hooks` directory of the new repository, but by default they are
all disabled. To enable a hook, rename it by removing its `.sample`
suffix.
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
index 4673a75a98da1f778c1323d8e5a4f7faff388589..bd005bc5c8c7d877ea9de79e2f6452d5703d0892 100644 (file)
--- a/Documentation/gitk.txt
+++ b/Documentation/gitk.txt
After an attempt to merge stops with conflicts, show the commits on
the history between two branches (i.e. the HEAD and the MERGE_HEAD)
- that modify the conflicted files.
+ that modify the conflicted files and do not exist on all the heads
+ being merged.
--argscmd=<command>::
Command to be run each time gitk has to determine the list of
index a057b50b2bbfe9740cbd3dc5f112f0d8c3d7d60b..dc8fc3a18a5e613da910c6bd5a1dda9c85c3f5b1 100644 (file)
$ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
-[master (root-commit)] created 54196cc: "initial commit"
+[master (root-commit) 54196cc] initial commit
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
-[master] created c4d59f3: "add emphasis"
+[master c4d59f3] add emphasis
1 files changed, 1 insertions(+), 1 deletions(-)
------------------------------------------------
index 458fafdb2cc7e54c5e2f49ca5854e6ec9fe581ee..c5d5596d895755d69c8060bc38a800d7c70eda26 100644 (file)
This merges the changes from Bob's "master" branch into Alice's
current branch. If Alice has made her own changes in the meantime,
-then she may need to manually fix any conflicts. (Note that the
-"master" argument in the above command is actually unnecessary, as it
-is the default.)
+then she may need to manually fix any conflicts.
The "pull" command thus performs two operations: it fetches changes
from a remote branch, then merges them into the current branch.
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
index d214d4bf9d0e539c6bf58ba24dcd12aabbaea1d8..74a1c0c4ba3a03ba02cbbabcf0ee6cff22e4b099 100644 (file)
I just have done a simpler one, this time using only the core
GIT tools.
-I had a handful commits that were ahead of master in pu, and I
+I had a handful of commits that were ahead of master in pu, and I
wanted to add some documentation bypassing my usual habit of
placing new things in pu first. At the beginning, the commit
ancestry graph looked like this:
diff --git a/Documentation/mailmap.txt b/Documentation/mailmap.txt
--- /dev/null
@@ -0,0 +1,75 @@
+If the file `.mailmap` exists at the toplevel of the repository, or at
+the location pointed to by the mailmap.file configuration option, it
+is used to map author and committer names and email addresses to
+canonical real names and email addresses.
+
+In the simple form, each line in the file consists of the canonical
+real name of an author, whitespace, and an email address used in the
+commit (enclosed by '<' and '>') to map to the name. Thus, looks like
+this
+--
+ Proper Name <commit@email.xx>
+--
+
+The more complex forms are
+--
+ <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace only the email part of a commit, and
+--
+ Proper Name <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a
+commit matching the specified commit email address, and
+--
+ Proper Name <proper@email.xx> Commit Name <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a
+commit matching both the specified commit name and email address.
+
+Example 1: Your history contains commits by two authors, Jane
+and Joe, whose names appear in the repository under several forms:
+
+------------
+Joe Developer <joe@example.com>
+Joe R. Developer <joe@example.com>
+Jane Doe <jane@example.com>
+Jane Doe <jane@laptop.(none)>
+Jane D. <jane@desktop.(none)>
+------------
+
+Now suppose that Joe wants his middle name initial used, and Jane
+prefers her family name fully spelled out. A proper `.mailmap` file
+would look like:
+
+------------
+Jane Doe <jane@desktop.(none)>
+Joe R. Developer <joe@example.com>
+------------
+
+Note how we don't need an entry for <jane@laptop.(none)>, because the
+real name of that author is correct already.
+
+Example 2: Your repository contains commits from the following
+authors:
+
+------------
+nick1 <bugs@company.xx>
+nick2 <bugs@company.xx>
+nick2 <nick2@company.xx>
+santa <me@company.xx>
+claus <me@company.xx>
+CTO <cto@coompany.xx>
+------------
+
+Then, you might want a `.mailmap` file looking like:
+------------
+<cto@company.xx> <cto@coompany.xx>
+Some Dude <some@dude.xx> nick1 <bugs@company.xx>
+Other Author <other@author.xx> nick2 <bugs@company.xx>
+Other Author <other@author.xx> <nick2@company.xx>
+Santa Claus <santa.claus@northpole.xx> <me@company.xx>
+------------
+
+Use hash '#' for comments that are either on their own line, or after
+the email address.
\ No newline at end of file
index 0a8a948e6ffba9d15b5db03399f74ed0139394ea..159390c35a9413590b83f512c3f9f59e6a275e77 100644 (file)
- '%P': parent hashes
- '%p': abbreviated parent hashes
- '%an': author name
-- '%aN': author name (respecting .mailmap)
+- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%ae': author email
+- '%aE': author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%ad': author date (format respects --date= option)
- '%aD': author date, RFC2822 style
- '%ar': author date, relative
- '%at': author date, UNIX timestamp
- '%ai': author date, ISO 8601 format
- '%cn': committer name
-- '%cN': committer name (respecting .mailmap)
+- '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%ce': committer email
+- '%cE': committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
- '%cd': committer date
- '%cD': committer date, RFC2822 style
- '%cr': committer date, relative
- '%Cgreen': switch color to green
- '%Cblue': switch color to blue
- '%Creset': reset color
+- '%C(...)': color specification, as described in color.branch.* config option
- '%m': left, right or boundary mark
- '%n': newline
- '%x00': print a byte from a hex code
index 6d66c74cc11e6622892061f8328d04dfe38f87bf..5f21efe40745a98eafff7df67a916b1f1193fae4 100644 (file)
--abbrev-commit::
Instead of showing the full 40-byte hexadecimal commit object
- name, show only handful hexdigits prefix. Non default number of
+ name, show only a partial prefix. Non default number of
digits can be specified with "--abbrev=<n>" (which also modifies
diff output, if it is displayed).
+
index b9f6e4d1b7564480fac9c4e355fdc4936f6fa3db..507632287795ed7e63a1311b9cd783e71572ba3e 100644 (file)
Synonym for `--date=relative`.
---date={relative,local,default,iso,rfc,short}::
+--date={relative,local,default,iso,rfc,short,raw}::
Only takes effect for dates shown in human-readable format, such
as when using "--pretty". `log.date` config variable sets a default
+
`--date=short` shows only date but not time, in `YYYY-MM-DD` format.
+
+`--date=raw` shows the date in the internal raw git format `%s %z` format.
++
`--date=default` shows timestamps in the original timezone
(either committer's or author's).
diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt
index 82e9e831b6a97dfaa62c02d30447415ef65e4fa3..2efe7a40be641bc2532c139637fe02e534ea1152 100644 (file)
Wait for the completion of an asynchronous function that was
started with start_async().
+`run_hook`::
+
+ Run a hook.
+ The first argument is a pathname to an index file, or NULL
+ if the hook uses the default index file or no index is needed.
+ The second argument is the name of the hook.
+ The further arguments correspond to the hook arguments.
+ The last argument has to be NULL to terminate the arguments list.
+ If the hook does not exist or is not executable, the return
+ value will be zero.
+ If it is executable, the hook will be executed and the exit
+ status of the hook is returned.
+ On execution, .stdout_to_stderr and .no_stdin will be set.
+ (See below.)
+
Data structures
---------------
index 985800e43a9b91256c35df60f67c36994142b94c..ac56d1c477b2f33b7f9b07d197f70c8f9ed05163 100644 (file)
* Adding data to the buffer
-NOTE: All of these functions in this section will grow the buffer as
- necessary.
+NOTE: All of the functions in this section will grow the buffer as necessary.
+If they fail for some reason other than memory shortage and the buffer hadn't
+been allocated before (i.e. the `struct strbuf` was set to `STRBUF_INIT`),
+then they will free() it.
`strbuf_addch`::
Read the contents of a file, specified by its path. The third argument
can be used to give a hint about the file size, to avoid reallocs.
+`strbuf_readlink`::
+
+ Read the target of a symbolic link, specified by its path. The third
+ argument can be used to give a hint about the size, to avoid reallocs.
+
`strbuf_getline`::
Read a line from a FILE* pointer. The second argument specifies the line
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 550a0ae3710dd1d4a831185c1cffadd65a9f2c47..0baa79d798d4ab63c3605b6c002dec80f563f095 100755 (executable)
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.1.3.GIT
+DEF_VER=v1.6.2-rc2.GIT
LF='
'
index d1deb0b3c7a9aba961d40fe541490562fee486ac..ae7f7508f8e8cffeb930c820e068ba70dabff7bd 100644 (file)
--- a/INSTALL
+++ b/INSTALL
Building and installing the info file additionally requires
makeinfo and docbook2X. Version 0.8.3 is known to work.
+ Building and installing the pdf file additionally requires
+ dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work.
+
The documentation is written for AsciiDoc 7, but "make
ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
diff --git a/Makefile b/Makefile
index 01242889eb888d35e28249a78d54584ebca4bb24..0675c43e73813800d616c3971a450157da707269 100644 (file)
--- a/Makefile
+++ b/Makefile
# Define NO_EXPAT if you do not have expat installed. git-http-push is
# not built, and you cannot push using http:// and https:// transports.
#
+# Define EXPATDIR=/foo/bar if your expat header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
#
# Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
# Among the variables below, these:
# gitexecdir
# template_dir
+# mandir
+# infodir
# htmldir
# ETC_GITCONFIG (but not sysconfdir)
-# can be specified as a relative path ../some/where/else (which must begin
-# with ../); this is interpreted as relative to $(bindir) and "git" at
+# can be specified as a relative path some/where/else;
+# this is interpreted as relative to $(prefix) and "git" at
# runtime figures out where they are based on the path to the executable.
# This can help installing the suite in a relocatable way.
prefix = $(HOME)
-bindir = $(prefix)/bin
-mandir = $(prefix)/share/man
-infodir = $(prefix)/share/info
-gitexecdir = $(prefix)/libexec/git-core
+bindir_relative = bin
+bindir = $(prefix)/$(bindir_relative)
+mandir = share/man
+infodir = share/info
+gitexecdir = libexec/git-core
sharedir = $(prefix)/share
-template_dir = $(sharedir)/git-core/templates
-htmldir=$(sharedir)/doc/git-doc
+template_dir = share/git-core/templates
+htmldir = share/doc/git-doc
ifeq ($(prefix),/usr)
sysconfdir = /etc
+ETC_GITCONFIG = $(sysconfdir)/gitconfig
else
sysconfdir = $(prefix)/etc
+ETC_GITCONFIG = etc/gitconfig
endif
lib = lib
-ETC_GITCONFIG = $(sysconfdir)/gitconfig
# DESTDIR=
# default configuration for gitweb
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
-export prefix bindir sharedir htmldir sysconfdir
+export prefix bindir sharedir sysconfdir
CC = gcc
AR = ar
# builtin-$C.o but is linked in as part of some other command.
BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
-BUILT_INS += git-cherry-pick$X
BUILT_INS += git-cherry$X
+BUILT_INS += git-cherry-pick$X
BUILT_INS += git-format-patch$X
BUILT_INS += git-fsck-objects$X
BUILT_INS += git-get-tar-commit-id$X
LIB_H += cache.h
LIB_H += cache-tree.h
LIB_H += commit.h
-LIB_H += compat/mingw.h
LIB_H += compat/cygwin.h
+LIB_H += compat/mingw.h
LIB_H += csum-file.h
LIB_H += decorate.h
LIB_H += delta.h
LIB_H += pack-revindex.h
LIB_H += parse-options.h
LIB_H += patch-ids.h
-LIB_H += string-list.h
LIB_H += pkt-line.h
LIB_H += progress.h
LIB_H += quote.h
LIB_H += run-command.h
LIB_H += sha1-lookup.h
LIB_H += sideband.h
+LIB_H += sigchain.h
LIB_H += strbuf.h
+LIB_H += string-list.h
LIB_H += tag.h
LIB_H += transport.h
LIB_H += tree.h
LIB_OBJS += diffcore-pickaxe.o
LIB_OBJS += diffcore-rename.o
LIB_OBJS += diff-delta.o
-LIB_OBJS += diff-no-index.o
LIB_OBJS += diff-lib.o
+LIB_OBJS += diff-no-index.o
LIB_OBJS += diff.o
LIB_OBJS += dir.o
LIB_OBJS += editor.o
LIB_OBJS += parse-options.o
LIB_OBJS += patch-delta.o
LIB_OBJS += patch-ids.o
-LIB_OBJS += string-list.o
LIB_OBJS += path.o
LIB_OBJS += pkt-line.o
+LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o
LIB_OBJS += progress.o
LIB_OBJS += quote.o
LIB_OBJS += run-command.o
LIB_OBJS += server-info.o
LIB_OBJS += setup.o
-LIB_OBJS += sha1_file.o
LIB_OBJS += sha1-lookup.o
+LIB_OBJS += sha1_file.o
LIB_OBJS += sha1_name.o
LIB_OBJS += shallow.o
LIB_OBJS += sideband.o
+LIB_OBJS += sigchain.o
LIB_OBJS += strbuf.o
+LIB_OBJS += string-list.o
LIB_OBJS += symlinks.o
LIB_OBJS += tag.o
LIB_OBJS += trace.o
LIB_OBJS += tree.o
LIB_OBJS += tree-walk.o
LIB_OBJS += unpack-trees.o
-LIB_OBJS += userdiff.o
LIB_OBJS += usage.o
+LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o
LIB_OBJS += walker.o
LIB_OBJS += wrapper.o
LIB_OBJS += ws.o
LIB_OBJS += wt-status.o
LIB_OBJS += xdiff-interface.o
-LIB_OBJS += preload-index.o
BUILTIN_OBJS += builtin-add.o
BUILTIN_OBJS += builtin-annotate.o
ifeq ($(uname_S),Darwin)
NEEDS_SSL_WITH_CRYPTO = YesPlease
NEEDS_LIBICONV = YesPlease
- ifneq ($(shell expr "$(uname_R)" : '9\.'),2)
+ ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2)
OLD_ICONV = UnfortunatelyYes
endif
- NO_STRLCPY = YesPlease
+ ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2)
+ NO_STRLCPY = YesPlease
+ endif
NO_MEMMEM = YesPlease
THREADED_DELTA_SEARCH = YesPlease
endif
SNPRINTF_RETURNS_BOGUS = YesPlease
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
+ RUNTIME_PREFIX = YesPlease
NO_POSIX_ONLY_PROGRAMS = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o
EXTLIBS += -lws2_32
X = .exe
- gitexecdir = ../libexec/git-core
- template_dir = ../share/git-core/templates/
- ETC_GITCONFIG = ../etc/gitconfig
endif
ifneq (,$(findstring arm,$(uname_M)))
ARM_SHA1 = YesPlease
BASIC_LDFLAGS += -L/opt/local/lib
endif
endif
+ PTHREAD_LIBS =
endif
ifndef CC_LD_DYNPATH
endif
endif
ifndef NO_EXPAT
- EXPAT_LIBEXPAT = -lexpat
+ ifdef EXPATDIR
+ BASIC_CFLAGS += -I$(EXPATDIR)/include
+ EXPAT_LIBEXPAT = -L$(EXPATDIR)/$(lib) $(CC_LD_DYNPATH)$(EXPATDIR)/$(lib) -lexpat
+ else
+ EXPAT_LIBEXPAT = -lexpat
+ endif
endif
endif
COMPAT_CFLAGS += -DINTERNAL_QSORT
COMPAT_OBJS += compat/qsort.o
endif
+ifdef RUNTIME_PREFIX
+ COMPAT_CFLAGS += -DRUNTIME_PREFIX
+endif
ifdef NO_PTHREADS
THREADED_DELTA_SEARCH =
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
bindir_SQ = $(subst ','\'',$(bindir))
+bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
mandir_SQ = $(subst ','\'',$(mandir))
infodir_SQ = $(subst ','\'',$(infodir))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
exec_cmd.o: exec_cmd.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
+ $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
+ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
+ '-DBINDIR="$(bindir_relative_SQ)"' \
+ '-DPREFIX="$(prefix_SQ)"' \
+ $<
+
builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
- xdiff/xmerge.o
+ xdiff/xmerge.o xdiff/xpatience.o
$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
info:
$(MAKE) -C Documentation info
+pdf:
+ $(MAKE) -C Documentation pdf
+
TAGS:
$(RM) TAGS
$(FIND) . -name '*.[hcS]' -print | xargs etags -a
### Testing rules
-TEST_PROGRAMS = test-chmtime$X test-dump-cache-tree$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X
+TEST_PROGRAMS += test-chmtime$X
+TEST_PROGRAMS += test-ctype$X
+TEST_PROGRAMS += test-date$X
+TEST_PROGRAMS += test-delta$X
+TEST_PROGRAMS += test-dump-cache-tree$X
+TEST_PROGRAMS += test-genrandom$X
+TEST_PROGRAMS += test-match-trees$X
+TEST_PROGRAMS += test-parse-options$X
+TEST_PROGRAMS += test-path-utils$X
+TEST_PROGRAMS += test-sha1$X
+TEST_PROGRAMS += test-sigchain$X
all:: $(TEST_PROGRAMS)
test: all
$(MAKE) -C t/ all
+test-ctype$X: ctype.o
+
test-date$X: date.o ctype.o
test-delta$X: diff-delta.o patch-delta.o
### Installation rules
-ifeq ($(firstword $(subst /, ,$(template_dir))),..)
-template_instdir = $(bindir)/$(template_dir)
-else
+ifneq ($(filter /%,$(firstword $(template_dir))),)
template_instdir = $(template_dir)
+else
+template_instdir = $(prefix)/$(template_dir)
endif
export template_instdir
-ifeq ($(firstword $(subst /, ,$(gitexecdir))),..)
-gitexec_instdir = $(bindir)/$(gitexecdir)
-else
+ifneq ($(filter /%,$(firstword $(gitexecdir))),)
gitexec_instdir = $(gitexecdir)
+else
+gitexec_instdir = $(prefix)/$(gitexecdir)
endif
gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
export gitexec_instdir
bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
{ $(RM) "$$execdir/git-add$X" && \
- ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \
- cp git-add$X "$$execdir/git-add$X"; } && \
- { $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
- ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \
- ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \
- cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \
+ ln "$$bindir/git$X" "$$execdir/git-add$X" 2>/dev/null || \
+ cp "$$bindir/git$X" "$$execdir/git-add$X"; } && \
+ { for p in $(filter-out git-add$X,$(BUILT_INS)); do \
+ $(RM) "$$execdir/$$p" && \
+ ln "$$execdir/git-add$X" "$$execdir/$$p" 2>/dev/null || \
+ ln -s "git-add$X" "$$execdir/$$p" 2>/dev/null || \
+ cp "$$execdir/git-add$X" "$$execdir/$$p" || exit; \
+ done } && \
./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
install-doc:
install-info:
$(MAKE) -C Documentation install-info
+install-pdf:
+ $(MAKE) -C Documentation install-pdf
+
quick-install-doc:
$(MAKE) -C Documentation quick-install
index 5fa41b7a18942a68bacb7b488984bdf98f6dfd1a..c932ab310510c84c3e69107b458a228db37cb1f6 100644 (file)
--- a/README
+++ b/README
hackers around the net. It is currently maintained by Junio C Hamano.
Please read the file INSTALL for installation instructions.
+
See Documentation/gittutorial.txt to get started, then see
-Documentation/everyday.txt for a useful minimum set of commands,
-and "man git-commandname" for documentation of each command.
-CVS users may also want to read Documentation/cvs-migration.txt.
+Documentation/everyday.txt for a useful minimum set of commands, and
+Documentation/git-commandname.txt for documentation of each command.
+If git has been correctly installed, then the tutorial can also be
+read with "man gittutorial" or "git help tutorial", and the
+documentation of each command with "man git-commandname" or "git help
+commandname".
+
+CVS users may also want to read Documentation/gitcvs-migration.txt
+("man gitcvs-migration" or "git help cvs-migration" if git is
+installed).
Many Git online resources are accessible from http://git.or.cz/
including full documentation and Git related tools.
diff --git a/RelNotes b/RelNotes
index 56936add354167e7dac8eb4f8b053572c92c47f8..ecced08731fe83882f94171b14ec18012a07621e 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
-Documentation/RelNotes-1.6.1.4.txt
\ No newline at end of file
+Documentation/RelNotes-1.6.2.txt
\ No newline at end of file
diff --git a/archive.c b/archive.c
index 9ac455d889b72deba8c949da1d9efe2be3a50244..e6de0397cc82ae97018cbf4fc82d5697ec4d915d 100644 (file)
--- a/archive.c
+++ b/archive.c
err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0);
if (err)
return err;
- return READ_TREE_RECURSIVE;
+ return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
}
buffer = sha1_file_to_archive(path_without_prefix, sha1, mode,
diff --git a/branch.c b/branch.c
index b1ac837f3d30c826ddbe29492b906bf2d0de0a1a..1f00e44deb28afe74ae4f0b85b23039476032fe5 100644 (file)
--- a/branch.c
+++ b/branch.c
struct ref_lock *lock;
struct commit *commit;
unsigned char sha1[20];
- char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
+ char *real_ref, msg[PATH_MAX + 20];
+ struct strbuf ref = STRBUF_INIT;
int forcing = 0;
+ int len;
- snprintf(ref, sizeof ref, "refs/heads/%s", name);
- if (check_ref_format(ref))
+ len = strlen(name);
+ if (interpret_nth_last_branch(name, &ref) != len) {
+ strbuf_reset(&ref);
+ strbuf_add(&ref, name, len);
+ }
+ strbuf_splice(&ref, 0, 0, "refs/heads/", 11);
+
+ if (check_ref_format(ref.buf))
die("'%s' is not a valid branch name.", name);
- if (resolve_ref(ref, sha1, 1, NULL)) {
+ if (resolve_ref(ref.buf, sha1, 1, NULL)) {
if (!force)
die("A branch named '%s' already exists.", name);
else if (!is_bare_repository() && !strcmp(head, name))
die("Not a valid branch point: '%s'.", start_name);
hashcpy(sha1, commit->object.sha1);
- lock = lock_any_ref_for_update(ref, NULL, 0);
+ lock = lock_any_ref_for_update(ref.buf, NULL, 0);
if (!lock)
die("Failed to lock ref for update: %s.", strerror(errno));
if (write_ref_sha1(lock, sha1, msg) < 0)
die("Failed to write ref: %s.", strerror(errno));
+ strbuf_release(&ref);
free(real_ref);
}
diff --git a/builtin-add.c b/builtin-add.c
index ac98c8354d84a7f556c22c53fbe9007832ac4346..08443f2f1ecf7d9edd21cec11fa74548c3326df5 100644 (file)
--- a/builtin-add.c
+++ b/builtin-add.c
"git add [options] [--] <filepattern>...",
NULL
};
-static int patch_interactive = 0, add_interactive = 0;
+static int patch_interactive, add_interactive;
static int take_worktree_changes;
static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
diff --git a/builtin-apply.c b/builtin-apply.c
index 58d998577e6d4148ddead9e57bdb0999ff026f73..f312798af38553e0badeda9732736a62460eae05 100644 (file)
--- a/builtin-apply.c
+++ b/builtin-apply.c
#include "builtin.h"
#include "string-list.h"
#include "dir.h"
+#include "parse-options.h"
/*
* --check turns on checking that the working tree matches the
static int no_add;
static const char *fake_ancestor;
static int line_termination = '\n';
-static unsigned long p_context = ULONG_MAX;
-static const char apply_usage[] =
-"git apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>...";
+static unsigned int p_context = UINT_MAX;
+static const char * const apply_usage[] = {
+ "git apply [options] [<patch>...]",
+ NULL
+};
static enum ws_error_action {
nowarn_ws_error,
static const char *patch_input_file;
static const char *root;
static int root_len;
+static int read_stdin = 1;
+static int options;
static void parse_whitespace_option(const char *option)
{
return git_default_config(var, value, cb);
}
+static int option_parse_exclude(const struct option *opt,
+ const char *arg, int unset)
+{
+ add_name_limit(arg, 1);
+ return 0;
+}
+
+static int option_parse_include(const struct option *opt,
+ const char *arg, int unset)
+{
+ add_name_limit(arg, 0);
+ has_include = 1;
+ return 0;
+}
+
+static int option_parse_p(const struct option *opt,
+ const char *arg, int unset)
+{
+ p_value = atoi(arg);
+ p_value_known = 1;
+ return 0;
+}
+
+static int option_parse_z(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ line_termination = '\n';
+ else
+ line_termination = 0;
+ return 0;
+}
+
+static int option_parse_whitespace(const struct option *opt,
+ const char *arg, int unset)
+{
+ const char **whitespace_option = opt->value;
+
+ *whitespace_option = arg;
+ parse_whitespace_option(arg);
+ return 0;
+}
+
+static int option_parse_directory(const struct option *opt,
+ const char *arg, int unset)
+{
+ root_len = strlen(arg);
+ if (root_len && arg[root_len - 1] != '/') {
+ char *new_root;
+ root = new_root = xmalloc(root_len + 2);
+ strcpy(new_root, arg);
+ strcpy(new_root + root_len++, "/");
+ } else
+ root = arg;
+ return 0;
+}
int cmd_apply(int argc, const char **argv, const char *unused_prefix)
{
int i;
- int read_stdin = 1;
- int options = 0;
int errs = 0;
int is_not_gitdir;
+ int binary;
+ int force_apply = 0;
const char *whitespace_option = NULL;
+ struct option builtin_apply_options[] = {
+ { OPTION_CALLBACK, 0, "exclude", NULL, "path",
+ "don´t apply changes matching the given path",
+ 0, option_parse_exclude },
+ { OPTION_CALLBACK, 0, "include", NULL, "path",
+ "apply changes matching the given path",
+ 0, option_parse_include },
+ { OPTION_CALLBACK, 'p', NULL, NULL, "num",
+ "remove <num> leading slashes from traditional diff paths",
+ 0, option_parse_p },
+ OPT_BOOLEAN(0, "no-add", &no_add,
+ "ignore additions made by the patch"),
+ OPT_BOOLEAN(0, "stat", &diffstat,
+ "instead of applying the patch, output diffstat for the input"),
+ OPT_BOOLEAN(0, "allow-binary-replacement", &binary,
+ "now no-op"),
+ OPT_BOOLEAN(0, "binary", &binary,
+ "now no-op"),
+ OPT_BOOLEAN(0, "numstat", &numstat,
+ "shows number of added and deleted lines in decimal notation"),
+ OPT_BOOLEAN(0, "summary", &summary,
+ "instead of applying the patch, output a summary for the input"),
+ OPT_BOOLEAN(0, "check", &check,
+ "instead of applying the patch, see if the patch is applicable"),
+ OPT_BOOLEAN(0, "index", &check_index,
+ "make sure the patch is applicable to the current index"),
+ OPT_BOOLEAN(0, "cached", &cached,
+ "apply a patch without touching the working tree"),
+ OPT_BOOLEAN(0, "apply", &force_apply,
+ "also apply the patch (use with --stat/--summary/--check)"),
+ OPT_STRING(0, "build-fake-ancestor", &fake_ancestor, "file",
+ "build a temporary index based on embedded index information"),
+ { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+ "paths are separated with NUL character",
+ PARSE_OPT_NOARG, option_parse_z },
+ OPT_INTEGER('C', NULL, &p_context,
+ "ensure at least <n> lines of context match"),
+ { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
+ "detect new or modified lines that have whitespace errors",
+ 0, option_parse_whitespace },
+ OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
+ "apply the patch in reverse"),
+ OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
+ "don't expect at least one line of context"),
+ OPT_BOOLEAN(0, "reject", &apply_with_reject,
+ "leave the rejected hunks in corresponding *.rej files"),
+ OPT__VERBOSE(&apply_verbosely),
+ OPT_BIT(0, "inaccurate-eof", &options,
+ "tolerate incorrectly detected missing new-line at the end of file",
+ INACCURATE_EOF),
+ OPT_BIT(0, "recount", &options,
+ "do not trust the line counts in the hunk headers",
+ RECOUNT),
+ { OPTION_CALLBACK, 0, "directory", NULL, "root",
+ "prepend <root> to all filenames",
+ 0, option_parse_directory },
+ OPT_END()
+ };
+
prefix = setup_git_directory_gently(&is_not_gitdir);
prefix_length = prefix ? strlen(prefix) : 0;
git_config(git_apply_config, NULL);
if (apply_default_whitespace)
parse_whitespace_option(apply_default_whitespace);
- for (i = 1; i < argc; i++) {
+ argc = parse_options(argc, argv, builtin_apply_options,
+ apply_usage, 0);
+ if (apply_with_reject)
+ apply = apply_verbosely = 1;
+ if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
+ apply = 0;
+ if (check_index && is_not_gitdir)
+ die("--index outside a repository");
+ if (cached) {
+ if (is_not_gitdir)
+ die("--cached outside a repository");
+ check_index = 1;
+ }
+ for (i = 0; i < argc; i++) {
const char *arg = argv[i];
- char *end;
int fd;
if (!strcmp(arg, "-")) {
errs |= apply_patch(0, "<stdin>", options);
read_stdin = 0;
continue;
- }
- if (!prefixcmp(arg, "--exclude=")) {
- add_name_limit(arg + 10, 1);
- continue;
- }
- if (!prefixcmp(arg, "--include=")) {
- add_name_limit(arg + 10, 0);
- has_include = 1;
- continue;
- }
- if (!prefixcmp(arg, "-p")) {
- p_value = atoi(arg + 2);
- p_value_known = 1;
- continue;
- }
- if (!strcmp(arg, "--no-add")) {
- no_add = 1;
- continue;
- }
- if (!strcmp(arg, "--stat")) {
- apply = 0;
- diffstat = 1;
- continue;
- }
- if (!strcmp(arg, "--allow-binary-replacement") ||
- !strcmp(arg, "--binary")) {
- continue; /* now no-op */
- }
- if (!strcmp(arg, "--numstat")) {
- apply = 0;
- numstat = 1;
- continue;
- }
- if (!strcmp(arg, "--summary")) {
- apply = 0;
- summary = 1;
- continue;
- }
- if (!strcmp(arg, "--check")) {
- apply = 0;
- check = 1;
- continue;
- }
- if (!strcmp(arg, "--index")) {
- if (is_not_gitdir)
- die("--index outside a repository");
- check_index = 1;
- continue;
- }
- if (!strcmp(arg, "--cached")) {
- if (is_not_gitdir)
- die("--cached outside a repository");
- check_index = 1;
- cached = 1;
- continue;
- }
- if (!strcmp(arg, "--apply")) {
- apply = 1;
- continue;
- }
- if (!strcmp(arg, "--build-fake-ancestor")) {
- apply = 0;
- if (++i >= argc)
- die ("need a filename");
- fake_ancestor = argv[i];
- continue;
- }
- if (!strcmp(arg, "-z")) {
- line_termination = 0;
- continue;
- }
- if (!prefixcmp(arg, "-C")) {
- p_context = strtoul(arg + 2, &end, 0);
- if (*end != '\0')
- die("unrecognized context count '%s'", arg + 2);
- continue;
- }
- if (!prefixcmp(arg, "--whitespace=")) {
- whitespace_option = arg + 13;
- parse_whitespace_option(arg + 13);
- continue;
- }
- if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) {
- apply_in_reverse = 1;
- continue;
- }
- if (!strcmp(arg, "--unidiff-zero")) {
- unidiff_zero = 1;
- continue;
- }
- if (!strcmp(arg, "--reject")) {
- apply = apply_with_reject = apply_verbosely = 1;
- continue;
- }
- if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
- apply_verbosely = 1;
- continue;
- }
- if (!strcmp(arg, "--inaccurate-eof")) {
- options |= INACCURATE_EOF;
- continue;
- }
- if (!strcmp(arg, "--recount")) {
- options |= RECOUNT;
- continue;
- }
- if (!prefixcmp(arg, "--directory=")) {
- arg += strlen("--directory=");
- root_len = strlen(arg);
- if (root_len && arg[root_len - 1] != '/') {
- char *new_root;
- root = new_root = xmalloc(root_len + 2);
- strcpy(new_root, arg);
- strcpy(new_root + root_len++, "/");
- } else
- root = arg;
- continue;
- }
- if (0 < prefix_length)
+ } else if (0 < prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
fd = open(arg, O_RDONLY);
diff --git a/builtin-blame.c b/builtin-blame.c
index aae14ef8bb63abc598e2e3ce99bf64d5c07e0067..114a214ed3fef40ae5cc13737d037f29d6f8acfd 100644 (file)
--- a/builtin-blame.c
+++ b/builtin-blame.c
#include "string-list.h"
#include "mailmap.h"
#include "parse-options.h"
+#include "utf8.h"
static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
* Parse author/committer line in the commit object buffer
*/
static void get_ac_line(const char *inbuf, const char *what,
- int bufsz, char *person, const char **mail,
+ int person_len, char *person,
+ int mail_len, char *mail,
unsigned long *time, const char **tz)
{
int len, tzlen, maillen;
- char *tmp, *endp, *timepos;
+ char *tmp, *endp, *timepos, *mailpos;
tmp = strstr(inbuf, what);
if (!tmp)
len = strlen(tmp);
else
len = endp - tmp;
- if (bufsz <= len) {
+ if (person_len <= len) {
error_out:
/* Ugh */
- *mail = *tz = "(unknown)";
+ *tz = "(unknown)";
+ strcpy(mail, *tz);
*time = 0;
return;
}
*tmp = 0;
while (*tmp != ' ')
tmp--;
- *mail = tmp + 1;
+ mailpos = tmp + 1;
*tmp = 0;
maillen = timepos - tmp;
+ memcpy(mail, mailpos, maillen);
if (!mailmap.nr)
return;
* mailmap expansion may make the name longer.
* make room by pushing stuff down.
*/
- tmp = person + bufsz - (tzlen + 1);
+ tmp = person + person_len - (tzlen + 1);
memmove(tmp, *tz, tzlen);
tmp[tzlen] = 0;
*tz = tmp;
- tmp = tmp - (maillen + 1);
- memmove(tmp, *mail, maillen);
- tmp[maillen] = 0;
- *mail = tmp;
-
/*
- * Now, convert e-mail using mailmap
+ * Now, convert both name and e-mail using mailmap
*/
- map_email(&mailmap, tmp + 1, person, tmp-person-1);
+ if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+ /* Add a trailing '>' to email, since map_user returns plain emails
+ Note: It already has '<', since we replace from mail+1 */
+ mailpos = memchr(mail, '\0', mail_len);
+ if (mailpos && mailpos-mail < mail_len - 1) {
+ *mailpos = '>';
+ *(mailpos+1) = '\0';
+ }
+ }
}
static void get_commit_info(struct commit *commit,
{
int len;
char *tmp, *endp, *reencoded, *message;
- static char author_buf[1024];
- static char committer_buf[1024];
+ static char author_name[1024];
+ static char author_mail[1024];
+ static char committer_name[1024];
+ static char committer_mail[1024];
static char summary_buf[1024];
/*
}
reencoded = reencode_commit_message(commit, NULL);
message = reencoded ? reencoded : commit->buffer;
- ret->author = author_buf;
+ ret->author = author_name;
+ ret->author_mail = author_mail;
get_ac_line(message, "\nauthor ",
- sizeof(author_buf), author_buf, &ret->author_mail,
+ sizeof(author_name), author_name,
+ sizeof(author_mail), author_mail,
&ret->author_time, &ret->author_tz);
if (!detailed) {
return;
}
- ret->committer = committer_buf;
+ ret->committer = committer_name;
+ ret->committer_mail = committer_mail;
get_ac_line(message, "\ncommitter ",
- sizeof(committer_buf), committer_buf, &ret->committer_mail,
+ sizeof(committer_name), committer_name,
+ sizeof(committer_mail), committer_mail,
&ret->committer_time, &ret->committer_tz);
ret->summary = summary_buf;
@@ -1618,13 +1631,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
printf(" %*d", max_orig_digits,
ent->s_lno + 1 + cnt);
- if (!(opt & OUTPUT_NO_AUTHOR))
- printf(" (%-*.*s %10s",
- longest_author, longest_author,
- ci.author,
+ if (!(opt & OUTPUT_NO_AUTHOR)) {
+ int pad = longest_author - utf8_strwidth(ci.author);
+ printf(" (%s%*s %10s",
+ ci.author, pad, "",
format_time(ci.author_time,
ci.author_tz,
show_raw_time));
+ }
printf(" %*d) ",
max_digits, ent->lno + 1 + cnt);
}
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- num = strlen(ci.author);
+ num = utf8_strwidth(ci.author);
if (longest_author < num)
longest_author = num;
}
die("reading graft file %s failed: %s",
revs_file, strerror(errno));
- read_mailmap(&mailmap, ".mailmap", NULL);
+ read_mailmap(&mailmap, NULL);
if (!incremental)
setup_pager();
diff --git a/builtin-branch.c b/builtin-branch.c
index 02fa38fd3b13b5c17aa23edbfd79f91a052adf76..504a981ad56f73a547c11bf3e18185f67111d000 100644 (file)
--- a/builtin-branch.c
+++ b/builtin-branch.c
const char *fmt, *remote;
int i;
int ret = 0;
+ struct strbuf bname = STRBUF_INIT;
switch (kinds) {
case REF_REMOTE_BRANCH:
if (!head_rev)
die("Couldn't look up commit object for HEAD");
}
- for (i = 0; i < argc; i++) {
- if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
+ for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+ int len = strlen(argv[i]);
+
+ if (interpret_nth_last_branch(argv[i], &bname) != len)
+ strbuf_add(&bname, argv[i], len);
+
+ if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
error("Cannot delete the branch '%s' "
- "which you are currently on.", argv[i]);
+ "which you are currently on.", bname.buf);
ret = 1;
continue;
}
free(name);
- name = xstrdup(mkpath(fmt, argv[i]));
+ name = xstrdup(mkpath(fmt, bname.buf));
if (!resolve_ref(name, sha1, 1, NULL)) {
error("%sbranch '%s' not found.",
- remote, argv[i]);
+ remote, bname.buf);
ret = 1;
continue;
}
if (!force &&
!in_merge_bases(rev, &head_rev, 1)) {
error("The branch '%s' is not an ancestor of "
- "your current HEAD.\n"
- "If you are sure you want to delete it, "
- "run 'git branch -D %s'.", argv[i], argv[i]);
+ "your current HEAD.\n"
+ "If you are sure you want to delete it, "
+ "run 'git branch -D %s'.", bname.buf, bname.buf);
ret = 1;
continue;
}
if (delete_ref(name, sha1, 0)) {
error("Error deleting %sbranch '%s'", remote,
- argv[i]);
+ bname.buf);
ret = 1;
} else {
struct strbuf buf = STRBUF_INIT;
- printf("Deleted %sbranch %s (%s).\n", remote, argv[i],
- find_unique_abbrev(sha1, DEFAULT_ABBREV));
- strbuf_addf(&buf, "branch.%s", argv[i]);
+ printf("Deleted %sbranch %s (%s).\n", remote,
+ bname.buf,
+ find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ strbuf_addf(&buf, "branch.%s", bname.buf);
if (git_config_rename_section(buf.buf, NULL) < 0)
warning("Update of config-file failed");
strbuf_release(&buf);
int kinds;
};
-static int has_commit(struct commit *commit, struct commit_list *with_commit)
-{
- if (!with_commit)
- return 1;
- while (with_commit) {
- struct commit *other;
-
- other = with_commit->item;
- with_commit = with_commit->next;
- if (in_merge_bases(other, &commit, 1))
- return 1;
- }
- return 0;
-}
-
static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{
struct ref_list *ref_list = (struct ref_list*)(cb_data);
@@ -231,7 +223,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
return error("branch '%s' does not point at a commit", refname);
/* Filter with with_commit if specified */
- if (!has_commit(commit, ref_list->with_commit))
+ if (!is_descendant_of(commit, ref_list->with_commit))
return 0;
/* Don't add types the caller doesn't want */
@@ -401,7 +393,8 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
detached = (detached && (kinds & REF_LOCAL_BRANCH));
- if (detached && head_commit && has_commit(head_commit, with_commit)) {
+ if (detached && head_commit &&
+ is_descendant_of(head_commit, with_commit)) {
struct ref_item item;
item.name = xstrdup("(no branch)");
item.kind = REF_LOCAL_BRANCH;
strbuf_release(&newsection);
}
-static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset)
-{
- unsigned char sha1[20];
- struct commit *commit;
-
- if (!arg)
- return -1;
- if (get_sha1(arg, sha1))
- die("malformed object name %s", arg);
- commit = lookup_commit_reference(sha1);
- if (!commit)
- die("no such commit %s", arg);
- commit_list_insert(commit, opt->value);
- return 0;
-}
-
static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
{
merge_filter = ((opt->long_name[0] == 'n')
OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
"print only branches that contain the commit",
PARSE_OPT_LASTARG_DEFAULT,
- opt_parse_with_commit, (intptr_t)"HEAD",
+ parse_opt_with_commit, (intptr_t)"HEAD",
},
{
OPTION_CALLBACK, 0, "with", &with_commit, "commit",
"print only branches that contain the commit",
PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
- opt_parse_with_commit, (intptr_t) "HEAD",
+ parse_opt_with_commit, (intptr_t) "HEAD",
},
OPT__ABBREV(&abbrev),
diff --git a/builtin-cat-file.c b/builtin-cat-file.c
index 30d00a66649f749c8cf6e657734045382bc29e13..8fad19daedef8a38674ee35cd543983bad610857 100644 (file)
--- a/builtin-cat-file.c
+++ b/builtin-cat-file.c
break;
default:
- die("git cat-file: unknown option: %s\n", exp_type);
+ die("git cat-file: unknown option: %s", exp_type);
}
if (!buf)
diff --git a/builtin-checkout.c b/builtin-checkout.c
index b5dd9c07b42e0130384259715730f52bc8c4e6c3..20b34ce6e10d9b863226b501cf5a35178b898995 100644 (file)
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
static int post_checkout_hook(struct commit *old, struct commit *new,
int changed)
{
- struct child_process proc;
- const char *name = git_path("hooks/post-checkout");
- const char *argv[5];
+ return run_hook(NULL, "post-checkout",
+ sha1_to_hex(old ? old->object.sha1 : null_sha1),
+ sha1_to_hex(new ? new->object.sha1 : null_sha1),
+ changed ? "1" : "0", NULL);
+ /* "new" can be NULL when checking out from the index before
+ a commit exists. */
- if (access(name, X_OK) < 0)
- return 0;
-
- memset(&proc, 0, sizeof(proc));
- argv[0] = name;
- argv[1] = xstrdup(sha1_to_hex(old ? old->object.sha1 : null_sha1));
- argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
- argv[3] = changed ? "1" : "0";
- argv[4] = NULL;
- proc.argv = argv;
- proc.no_stdin = 1;
- proc.stdout_to_stderr = 1;
- return run_command(&proc);
}
static int update_some(const unsigned char *sha1, const char *base, int baselen,
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
- pathspec_match(pathspec, ps_matched, ce->name, 0);
+ match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
}
if (report_path_error(ps_matched, pathspec, 0))
/* Any unmerged paths? */
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
- if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+ if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
if (!ce_stage(ce))
continue;
if (opts->force) {
state.refresh_cache = 1;
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
- if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+ if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
if (!ce_stage(ce)) {
errs |= checkout_entry(ce, &state, NULL);
continue;
static void setup_branch_path(struct branch_info *branch)
{
struct strbuf buf = STRBUF_INIT;
- strbuf_addstr(&buf, "refs/heads/");
- strbuf_addstr(&buf, branch->name);
+ int ret;
+
+ if ((ret = interpret_nth_last_branch(branch->name, &buf))
+ && ret == strlen(branch->name)) {
+ branch->name = xstrdup(buf.buf);
+ strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+ } else {
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, branch->name);
+ }
branch->path = strbuf_detach(&buf, NULL);
}
arg = argv[0];
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
if (get_sha1(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
diff --git a/builtin-clone.c b/builtin-clone.c
index 2feac9c5cb8e85ae2b25c5a7a0a602e84e210f4a..c338910b1c76f3c994e4a22c9c0a1da38843e050 100644 (file)
--- a/builtin-clone.c
+++ b/builtin-clone.c
#include "strbuf.h"
#include "dir.h"
#include "pack-refs.h"
+#include "sigchain.h"
/*
* Overall FIXMEs:
dir = opendir(src->buf);
if (!dir)
- die("failed to open %s\n", src->buf);
+ die("failed to open %s", src->buf);
if (mkdir(dest->buf, 0777)) {
if (errno != EEXIST)
- die("failed to create directory %s\n", dest->buf);
+ die("failed to create directory %s", dest->buf);
else if (stat(dest->buf, &buf))
- die("failed to stat %s\n", dest->buf);
+ die("failed to stat %s", dest->buf);
else if (!S_ISDIR(buf.st_mode))
- die("%s exists and is not a directory\n", dest->buf);
+ die("%s exists and is not a directory", dest->buf);
}
strbuf_addch(src, '/');
}
if (unlink(dest->buf) && errno != ENOENT)
- die("failed to unlink %s\n", dest->buf);
+ die("failed to unlink %s", dest->buf);
if (!option_no_hardlinks) {
if (!link(src->buf, dest->buf))
continue;
if (option_local)
- die("failed to create link %s\n", dest->buf);
+ die("failed to create link %s", dest->buf);
option_no_hardlinks = 1;
}
if (copy_file(dest->buf, src->buf, 0666))
- die("failed to copy file to %s\n", dest->buf);
+ die("failed to copy file to %s", dest->buf);
}
closedir(dir);
}
static void remove_junk_on_signal(int signo)
{
remove_junk();
- signal(SIGINT, SIG_DFL);
+ sigchain_pop(signo);
raise(signo);
}
return local_refs;
}
+static void install_branch_config(const char *local,
+ const char *origin,
+ const char *remote)
+{
+ struct strbuf key = STRBUF_INIT;
+ strbuf_addf(&key, "branch.%s.remote", local);
+ git_config_set(key.buf, origin);
+ strbuf_reset(&key);
+ strbuf_addf(&key, "branch.%s.merge", local);
+ git_config_set(key.buf, remote);
+ strbuf_release(&key);
+}
+
int cmd_clone(int argc, const char **argv, const char *prefix)
{
int use_local_hardlinks = 1;
struct stat buf;
const char *repo_name, *repo, *work_tree, *git_dir;
char *path, *dir;
+ int dest_exists;
const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
dir = guess_dir_name(repo_name, is_bundle, option_bare);
strip_trailing_slashes(dir);
- if (!stat(dir, &buf))
- die("destination directory '%s' already exists.", dir);
+ dest_exists = !stat(dir, &buf);
+ if (dest_exists && !is_empty_dir(dir))
+ die("destination path '%s' already exists and is not "
+ "an empty directory.", dir);
strbuf_addf(&reflog_msg, "clone: from %s", repo);
if (safe_create_leading_directories_const(work_tree) < 0)
die("could not create leading directories of '%s': %s",
work_tree, strerror(errno));
- if (mkdir(work_tree, 0755))
+ if (!dest_exists && mkdir(work_tree, 0755))
die("could not create work tree dir '%s': %s.",
work_tree, strerror(errno));
set_git_work_tree(work_tree);
}
junk_git_dir = git_dir;
atexit(remove_junk);
- signal(SIGINT, remove_junk_on_signal);
+ sigchain_push_common(remove_junk_on_signal);
setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1);
option_upload_pack);
refs = transport_get_remote_refs(transport);
- transport_fetch_refs(transport, refs);
+ if(refs)
+ transport_fetch_refs(transport, refs);
}
- clear_extra_refs();
+ if (refs) {
+ clear_extra_refs();
- mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
+ mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
- head_points_at = locate_head(refs, mapped_refs, &remote_head);
+ head_points_at = locate_head(refs, mapped_refs, &remote_head);
+ }
+ else {
+ warning("You appear to have cloned an empty repository.");
+ head_points_at = NULL;
+ remote_head = NULL;
+ option_no_checkout = 1;
+ if (!option_bare)
+ install_branch_config("master", option_origin,
+ "refs/heads/master");
+ }
if (head_points_at) {
/* Local default branch link */
head_points_at->peer_ref->name,
reflog_msg.buf);
- strbuf_addf(&key, "branch.%s.remote", head);
- git_config_set(key.buf, option_origin);
- strbuf_reset(&key);
- strbuf_addf(&key, "branch.%s.merge", head);
- git_config_set(key.buf, head_points_at->name);
+ install_branch_config(head, option_origin,
+ head_points_at->name);
}
} else if (remote_head) {
/* Source had detached HEAD pointing somewhere. */
diff --git a/builtin-commit.c b/builtin-commit.c
index 2f0b00a174ff970939d2758334cb8d1dca41cb42..46e649cd7ca0a2ede8f010a6d3bf294f81b85d55 100644 (file)
--- a/builtin-commit.c
+++ b/builtin-commit.c
struct cache_entry *ce = active_cache[i];
if (ce->ce_flags & CE_UPDATE)
continue;
- if (!pathspec_match(pattern, m, ce->name, 0))
+ if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
continue;
string_list_insert(ce->name, list);
}
@@ -361,40 +361,6 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
return s.commitable;
}
-static int run_hook(const char *index_file, const char *name, ...)
-{
- struct child_process hook;
- const char *argv[10], *env[2];
- char index[PATH_MAX];
- va_list args;
- int i;
-
- va_start(args, name);
- argv[0] = git_path("hooks/%s", name);
- i = 0;
- do {
- if (++i >= ARRAY_SIZE(argv))
- die ("run_hook(): too many arguments");
- argv[i] = va_arg(args, const char *);
- } while (argv[i]);
- va_end(args);
-
- snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
- env[0] = index;
- env[1] = NULL;
-
- if (access(argv[0], X_OK) < 0)
- return 0;
-
- memset(&hook, 0, sizeof(hook));
- hook.argv = argv;
- hook.no_stdin = 1;
- hook.stdout_to_stderr = 1;
- hook.env = env;
-
- return run_command(&hook);
-}
-
static int is_a_merge(const unsigned char *sha1)
{
struct commit *commit = lookup_commit(sha1);
commitable = run_status(fp, index_file, prefix, 1);
wt_status_use_color = saved_color_setting;
} else {
- struct rev_info rev;
unsigned char sha1[20];
const char *parent = "HEAD";
if (get_sha1(parent, sha1))
commitable = !!active_nr;
- else {
- init_revisions(&rev, "");
- rev.abbrev = 0;
- setup_revisions(0, NULL, &rev, parent);
- DIFF_OPT_SET(&rev.diffopt, QUIET);
- DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
- run_diff_index(&rev, 1 /* cached */);
-
- commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
- }
+ else
+ commitable = index_differs_from(parent, 0);
}
fclose(fp);
{
struct rev_info rev;
struct commit *commit;
- static const char *format = "format:%h: \"%s\"";
+ static const char *format = "format:%h] %s";
unsigned char junk_sha1[20];
const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- printf("[%s%s]: created ",
+ printf("[%s%s ",
!prefixcmp(head, "refs/heads/") ?
head + 11 :
!strcmp(head, "HEAD") ?
index ab35b65b073e9bc089fcc96f295ca3bc457c992a..62fd1f0961a29cc81032d20b3fe37fcc91293407 100644 (file)
--- a/builtin-count-objects.c
+++ b/builtin-count-objects.c
*/
#include "cache.h"
+#include "dir.h"
#include "builtin.h"
#include "parse-options.h"
const char *cp;
int bad = 0;
- if ((ent->d_name[0] == '.') &&
- (ent->d_name[1] == 0 ||
- ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
+ if (is_dot_or_dotdot(ent->d_name))
continue;
for (cp = ent->d_name; *cp; cp++) {
int ch = *cp;
diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
index 469b07e240953aa21fd67eb2563e094a7f0f3d42..29356d25db910c6d90df46da87aa374467611350 100644 (file)
--- a/builtin-fetch--tool.c
+++ b/builtin-fetch--tool.c
#include "cache.h"
#include "refs.h"
#include "commit.h"
+#include "sigchain.h"
static char *get_stdin(void)
{
static void remove_keep_on_signal(int signo)
{
remove_keep();
- signal(SIGINT, SIG_DFL);
+ sigchain_pop(signo);
raise(signo);
}
char buffer[1024];
int err = 0;
- signal(SIGINT, remove_keep_on_signal);
+ sigchain_push_common(remove_keep_on_signal);
atexit(remove_keep);
while (fgets(buffer, sizeof(buffer), stdin)) {
diff --git a/builtin-fetch.c b/builtin-fetch.c
index 7568163af24df630c215e05b6082ed764150a315..1e4a3d9c516c88d701819b7f4b73c722412d540f 100644 (file)
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
#include "transport.h"
#include "run-command.h"
#include "parse-options.h"
+#include "sigchain.h"
static const char * const builtin_fetch_usage[] = {
"git fetch [options] [<repository> <refspec>...]",
static void unlock_pack_on_signal(int signo)
{
unlock_pack();
- signal(SIGINT, SIG_DFL);
+ sigchain_pop(signo);
raise(signo);
}
{
int r = transport_set_option(transport, name, value);
if (r < 0)
- die("Option \"%s\" value \"%s\" is not valid for %s\n",
+ die("Option \"%s\" value \"%s\" is not valid for %s",
name, value, transport->url);
if (r > 0)
warning("Option \"%s\" is ignored for %s\n",
ref_nr = j;
}
- signal(SIGINT, unlock_pack_on_signal);
+ sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack);
exit_code = do_fetch(transport,
parse_fetch_refspec(ref_nr, refs), ref_nr);
diff --git a/builtin-fsck.c b/builtin-fsck.c
index 5c4c77adaaba36693e304c27d1f171f8e7e9f151..64dffa542170fcceedc766ae6551134f61779893 100644 (file)
--- a/builtin-fsck.c
+++ b/builtin-fsck.c
#include "tree-walk.h"
#include "fsck.h"
#include "parse-options.h"
+#include "dir.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
static int check_strict;
static int keep_cache_objects;
static unsigned char head_sha1[20];
+static const char *head_points_at;
static int errors_found;
static int write_lost_and_found;
static int verbose;
while ((de = readdir(dir)) != NULL) {
char name[100];
unsigned char sha1[20];
- int len = strlen(de->d_name);
- switch (len) {
- case 2:
- if (de->d_name[1] != '.')
- break;
- case 1:
- if (de->d_name[0] != '.')
- break;
+ if (is_dot_or_dotdot(de->d_name))
continue;
- case 38:
+ if (strlen(de->d_name) == 38) {
sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, len+1);
+ memcpy(name+2, de->d_name, 39);
if (get_sha1_hex(name, sha1) < 0)
break;
add_sha1_list(sha1, DIRENT_SORT_HINT(de));
@@ -479,6 +474,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
static void get_default_heads(void)
{
+ if (head_points_at && !is_null_sha1(head_sha1))
+ fsck_handle_ref("HEAD", head_sha1, 0, NULL);
for_each_ref(fsck_handle_ref, NULL);
if (include_reflogs)
for_each_reflog(fsck_handle_reflog, NULL);
static int fsck_head_link(void)
{
- unsigned char sha1[20];
int flag;
int null_is_error = 0;
- const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag);
if (verbose)
fprintf(stderr, "Checking HEAD link\n");
+ head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
if (!head_points_at)
return error("Invalid HEAD");
if (!strcmp(head_points_at, "HEAD"))
else if (prefixcmp(head_points_at, "refs/heads/"))
return error("HEAD points to something strange (%s)",
head_points_at);
- if (is_null_sha1(sha1)) {
+ if (is_null_sha1(head_sha1)) {
if (null_is_error)
return error("HEAD: detached HEAD points at nothing");
fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
int cmd_fsck(int argc, const char **argv, const char *prefix)
{
int i, heads;
+ struct alternate_object_database *alt;
errors_found = 0;
fsck_head_link();
fsck_object_dir(get_object_directory());
+
+ prepare_alt_odb();
+ for (alt = alt_odb_list; alt; alt = alt->next) {
+ char namebuf[PATH_MAX];
+ int namelen = alt->name - alt->base;
+ memcpy(namebuf, alt->base, namelen);
+ namebuf[namelen - 1] = 0;
+ fsck_object_dir(namebuf);
+ }
+
if (check_full) {
- struct alternate_object_database *alt;
struct packed_git *p;
- prepare_alt_odb();
- for (alt = alt_odb_list; alt; alt = alt->next) {
- char namebuf[PATH_MAX];
- int namelen = alt->name - alt->base;
- memcpy(namebuf, alt->base, namelen);
- namebuf[namelen - 1] = 0;
- fsck_object_dir(namebuf);
- }
+
prepare_packed_git();
for (p = packed_git; p; p = p->next)
/* verify gives error messages itself */
heads = 0;
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
- if (!get_sha1(arg, head_sha1)) {
- struct object *obj = lookup_object(head_sha1);
+ unsigned char sha1[20];
+ if (!get_sha1(arg, sha1)) {
+ struct object *obj = lookup_object(sha1);
/* Error is printed by lookup_object(). */
if (!obj)
diff --git a/builtin-gc.c b/builtin-gc.c
index f8eae4adb41d9c338c07aa161e7305bb16742a1e..8d990ed4935804591a17c864e280c0c9b4b7597f 100644 (file)
--- a/builtin-gc.c
+++ b/builtin-gc.c
return gc_auto_pack_limit <= cnt;
}
-static int run_hook(void)
-{
- const char *argv[2];
- struct child_process hook;
- int ret;
-
- argv[0] = git_path("hooks/pre-auto-gc");
- argv[1] = NULL;
-
- if (access(argv[0], X_OK) < 0)
- return 0;
-
- memset(&hook, 0, sizeof(hook));
- hook.argv = argv;
- hook.no_stdin = 1;
- hook.stdout_to_stderr = 1;
-
- ret = start_command(&hook);
- if (ret) {
- warning("Could not spawn %s", argv[0]);
- return ret;
- }
- ret = finish_command(&hook);
- if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
- warning("%s exited due to uncaught signal", argv[0]);
- return ret;
-}
-
static int need_to_gc(void)
{
/*
*/
if (too_many_packs())
append_option(argv_repack,
- !strcmp(prune_expire, "now") ? "-a" : "-A",
+ prune_expire && !strcmp(prune_expire, "now") ?
+ "-a" : "-A",
MAX_ADD);
else if (!too_many_loose_objects())
return 0;
- if (run_hook())
+ if (run_hook(NULL, "pre-auto-gc", NULL))
return 0;
return 1;
}
int cmd_gc(int argc, const char **argv, const char *prefix)
{
- int prune = 0;
int aggressive = 0;
int auto_gc = 0;
int quiet = 0;
char buf[80];
struct option builtin_gc_options[] = {
- OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects (deprecated)"),
+ { OPTION_STRING, 0, "prune", &prune_expire, "date",
+ "prune unreferenced objects",
+ PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
"\"git help gc\" for more information.\n");
} else
append_option(argv_repack,
- !strcmp(prune_expire, "now") ? "-a" : "-A",
+ prune_expire && !strcmp(prune_expire, "now")
+ ? "-a" : "-A",
MAX_ADD);
if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
return error(FAILED_RUN, argv_repack[0]);
- argv_prune[2] = prune_expire;
- if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_prune[0]);
+ if (prune_expire) {
+ argv_prune[2] = prune_expire;
+ if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_prune[0]);
+ }
if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
return error(FAILED_RUN, argv_rerere[0]);
diff --git a/builtin-help.c b/builtin-help.c
index f076efa9211ddcffdc81ffd7c0c56fdb197a11b0..9b57a746185719eb43c962c4adb96283657692e9 100644 (file)
--- a/builtin-help.c
+++ b/builtin-help.c
* old_path, the ':' at the end will let 'man' to try
* system-wide paths after ours to find the manual page. If
* there is old_path, we need ':' as delimiter. */
- strbuf_addstr(&new_path, GIT_MAN_PATH);
+ strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
strbuf_addch(&new_path, ':');
if (old_path)
strbuf_addstr(&new_path, old_path);
static void show_info_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
- setenv("INFOPATH", GIT_INFO_PATH, 1);
+ setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, NULL);
}
diff --git a/builtin-init-db.c b/builtin-init-db.c
index d30c3fe2ca542b061ab8b7a7696cdd5416e17147..ee3911f8eef5c00eeb4868c843f2b8c5c83d688d 100644 (file)
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
}
}
else if (share && adjust_shared_perm(dir))
- die("Could not make %s writable by group\n", dir);
+ die("Could not make %s writable by group", dir);
}
static void copy_templates_1(char *path, int baselen,
diff --git a/builtin-log.c b/builtin-log.c
index 60f8dd86048fd3a4ec51d7296b85984a0cefd602..2ae39afccdc1978141fe4cc4453befa632ecaadf 100644 (file)
--- a/builtin-log.c
+++ b/builtin-log.c
#include "patch-ids.h"
#include "run-command.h"
#include "shortlog.h"
+#include "remote.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
static void show_tagger(char *buf, int len, struct rev_info *rev)
{
- char *email_end, *p;
- unsigned long date;
- int tz;
+ struct strbuf out = STRBUF_INIT;
- email_end = memchr(buf, '>', len);
- if (!email_end)
- return;
- p = ++email_end;
- while (isspace(*p))
- p++;
- date = strtoul(p, &p, 10);
- while (isspace(*p))
- p++;
- tz = (int)strtol(p, NULL, 10);
- printf("Tagger: %.*s\nDate: %s\n", (int)(email_end - buf), buf,
- show_date(date, tz, rev->date_mode));
+ pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
+ git_log_output_encoding ?
+ git_log_output_encoding: git_commit_encoding);
+ printf("%s\n", out.buf);
+ strbuf_release(&out);
}
static int show_object(const unsigned char *sha1, int show_tag_object,
committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
endpos = strchr(committer, '>');
if (!endpos)
- die("bogus committer info %s\n", committer);
+ die("bogus committer info %s", committer);
add_signoff = xmemdupz(committer, endpos - committer + 1);
}
else if (!strcmp(argv[i], "--attach")) {
@@ -1099,13 +1091,14 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
}
static const char cherry_usage[] =
-"git cherry [-v] <upstream> [<head>] [<limit>]";
+"git cherry [-v] [<upstream> [<head> [<limit>]]]";
int cmd_cherry(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct patch_ids ids;
struct commit *commit;
struct commit_list *list = NULL;
+ struct branch *current_branch;
const char *upstream;
const char *head = "HEAD";
const char *limit = NULL;
upstream = argv[1];
break;
default:
- usage(cherry_usage);
+ current_branch = branch_get(NULL);
+ if (!current_branch || !current_branch->merge
+ || !current_branch->merge[0]
+ || !current_branch->merge[0]->dst) {
+ fprintf(stderr, "Could not find a tracked"
+ " remote branch, please"
+ " specify <upstream> manually.\n");
+ usage(cherry_usage);
+ }
+
+ upstream = current_branch->merge[0]->dst;
}
init_revisions(&revs, prefix);
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index f72eb854756f602e4d114964f4585bc5a8c55e20..9dec282fba6ba22e37d13bdc5e35fa4a031433b5 100644 (file)
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
static const char *tag_killed = "";
static const char *tag_modified = "";
-
-/*
- * Match a pathspec against a filename. The first "skiplen" characters
- * are the common prefix
- */
-int pathspec_match(const char **spec, char *ps_matched,
- const char *filename, int skiplen)
-{
- const char *m;
-
- while ((m = *spec++) != NULL) {
- int matchlen = strlen(m + skiplen);
-
- if (!matchlen)
- goto matched;
- if (!strncmp(m + skiplen, filename + skiplen, matchlen)) {
- if (m[skiplen + matchlen - 1] == '/')
- goto matched;
- switch (filename[skiplen + matchlen]) {
- case '/': case '\0':
- goto matched;
- }
- }
- if (!fnmatch(m + skiplen, filename + skiplen, 0))
- goto matched;
- if (ps_matched)
- ps_matched++;
- continue;
- matched:
- if (ps_matched)
- *ps_matched = 1;
- return 1;
- }
- return 0;
-}
-
static void show_dir_entry(const char *tag, struct dir_entry *ent)
{
int len = prefix_len;
if (len >= ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
- if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len))
+ if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
return;
fputs(tag, stdout);
if (len >= ce_namelen(ce))
die("git ls-files: internal error - cache entry not superset of prefix");
- if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len))
+ if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
return;
if (tag && *tag && show_valid_bit &&
return max ? xmemdupz(prev, max) : NULL;
}
+static void strip_trailing_slash_from_submodules(void)
+{
+ const char **p;
+
+ for (p = pathspec; *p != NULL; p++) {
+ int len = strlen(*p), pos;
+
+ if (len < 1 || (*p)[len - 1] != '/')
+ continue;
+ pos = cache_name_pos(*p, len - 1);
+ if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
+ *p = xstrndup(*p, len - 1);
+ }
+}
+
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
pathspec = get_pathspec(prefix, argv + i);
+ /* be nice with submodule patsh ending in a slash */
+ read_cache();
+ if (pathspec)
+ strip_trailing_slash_from_submodules();
+
/* Verify that the pathspec matches the prefix */
if (pathspec)
prefix = verify_pathspec(prefix);
show_killed | show_modified))
show_cached = 1;
- read_cache();
if (prefix)
prune_cache(prefix);
if (with_tree) {
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index 5b63e6eada5cd6de764acef694da624a70ce6dab..fca46312f681caf077565fb4bd5b59a70008eb59 100644 (file)
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
*
* Something similar to this incomplete example:
*
- if (show_subprojects(base, baselen, pathname)) {
- struct child_process ls_tree;
-
- ls_tree.dir = base;
- ls_tree.argv = ls-tree;
- start_command(&ls_tree);
- }
+ if (show_subprojects(base, baselen, pathname))
+ retval = READ_TREE_RECURSIVE;
*
*/
type = commit_type;
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index e890f7a6d1ff7248aed4f03ebbcdfafd7e472dad..2789ccdf7dd43a1170a1ca28a3e4d4802422e719 100644 (file)
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
#define MAX_HDR_PARSED 10
#define MAX_BOUNDARIES 5
+static void cleanup_space(struct strbuf *sb);
+
+
static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
{
struct strbuf *src = name;
strbuf_add(&email, at, el);
strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
- /* The remainder is name. It could be "John Doe <john.doe@xz>"
- * or "john.doe@xz (John Doe)", but we have removed the
- * email part, so trim from both ends, possibly removing
- * the () pair at the end.
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
*/
+ cleanup_space(&f);
strbuf_trim(&f);
if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
strbuf_remove(&f, 0, 1);
c -= 'a' - 26;
else if ('0' <= c && c <= '9')
c -= '0' - 52;
- else if (c == '=') {
- /* padding is almost like (c == 0), except we do
- * not output NUL resulting only from it;
- * for now we just trust the data.
- */
- c = 0;
- }
else
continue; /* garbage */
switch (pos++) {
return;
out = reencode_string(line->buf, metainfo_charset, charset);
if (!out)
- die("cannot convert from %s to %s\n",
+ die("cannot convert from %s to %s",
charset, metainfo_charset);
strbuf_attach(line, out, strlen(out), strlen(out));
}
rfc2047 = 1;
if (in != ep) {
- strbuf_add(&outbuf, in, ep - in);
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
in = ep;
}
/* E.g.
}
output_header_lines(fout, "Subject", hdr);
} else if (!memcmp(header[i], "From", 4)) {
+ cleanup_space(hdr);
handle_from(hdr);
fprintf(fout, "Author: %s\n", name.buf);
fprintf(fout, "Email: %s\n", email.buf);
index 6b534c1a66bf7a4abbe0f38add7585dee65a6a44..703045bfc84a804f5cf58537117fb17859951f7e 100644 (file)
}
if (argc < 4)
- die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
+ die("Usage: %s <base>... -- <head> <remote> ...", argv[0]);
for (i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "--"))
diff --git a/builtin-merge.c b/builtin-merge.c
index cf869751b41c256363bf5f0c465e46684e2920b8..6d2160d0a3db1705d1372029dfa24752d453c8ec 100644 (file)
--- a/builtin-merge.c
+++ b/builtin-merge.c
};
static const char * const builtin_merge_usage[] = {
- "git-merge [options] <remote>...",
- "git-merge [options] <msg> HEAD <remote>",
+ "git merge [options] <remote>...",
+ "git merge [options] <msg> HEAD <remote>",
NULL
};
strbuf_release(&out);
}
-static int run_hook(const char *name)
-{
- struct child_process hook;
- const char *argv[3], *env[2];
- char index[PATH_MAX];
-
- argv[0] = git_path("hooks/%s", name);
- if (access(argv[0], X_OK) < 0)
- return 0;
-
- snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
- env[0] = index;
- env[1] = NULL;
-
- if (squash)
- argv[1] = "1";
- else
- argv[1] = "0";
- argv[2] = NULL;
-
- memset(&hook, 0, sizeof(hook));
- hook.argv = argv;
- hook.no_stdin = 1;
- hook.stdout_to_stderr = 1;
- hook.env = env;
-
- return run_command(&hook);
-}
-
static void finish(const unsigned char *new_head, const char *msg)
{
struct strbuf reflog_message = STRBUF_INIT;
}
/* Run a post-merge hook */
- run_hook("post-merge");
+ run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
strbuf_release(&reflog_message);
}
struct object *remote_head;
unsigned char branch_head[20], buf_sha[20];
struct strbuf buf = STRBUF_INIT;
+ struct strbuf bname = STRBUF_INIT;
const char *ptr;
int len, early;
+ len = strlen(remote);
+ if (interpret_nth_last_branch(remote, &bname) == len)
+ remote = bname.buf;
+
memset(branch_head, 0, sizeof(branch_head));
remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
if (!remote_head)
if (!hashcmp(remote_head->sha1, branch_head)) {
strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
sha1_to_hex(branch_head), remote);
- return;
+ goto cleanup;
}
/* See if remote matches <name>^^^.. or <name>~<number> */
sha1_to_hex(remote_head->sha1),
truname.buf + 11,
(early ? " (early part)" : ""));
- return;
+ strbuf_release(&truname);
+ goto cleanup;
}
}
strbuf_remove(&line, ptr-line.buf+1, 13);
strbuf_addbuf(msg, &line);
strbuf_release(&line);
- return;
+ goto cleanup;
}
strbuf_addf(msg, "%s\t\tcommit '%s'\n",
sha1_to_hex(remote_head->sha1), remote);
+cleanup:
+ strbuf_release(&buf);
+ strbuf_release(&bname);
}
static int git_merge_config(const char *k, const char *v, void *cb)
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index b616994f4587b9a3d0133b7d6694020743ea0a4d..bcefa52c69481ceb301a96af6402ca2a029144fb 100644 (file)
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
static int window = 10;
static uint32_t pack_size_limit, pack_size_limit_cfg;
static int depth = 50;
-static int delta_search_threads = 1;
+static int delta_search_threads;
static int pack_to_stdout;
static int num_preferred_base;
static struct progress *progress_state;
} else {
char tmpname[PATH_MAX];
int fd;
- snprintf(tmpname, sizeof(tmpname),
- "%s/pack/tmp_pack_XXXXXX", get_object_directory());
- fd = xmkstemp(tmpname);
+ fd = odb_mkstemp(tmpname, sizeof(tmpname),
+ "pack/tmp_pack_XXXXXX");
pack_tmp_name = xstrdup(tmpname);
f = sha1fd(fd, pack_tmp_name);
}
find_deltas(list, &list_size, window, depth, processed);
return;
}
+ if (progress > pack_to_stdout)
+ fprintf(stderr, "Delta compression using %d threads.\n",
+ delta_search_threads);
/* Partition the work amongst work threads. */
for (i = 0; i < delta_search_threads; i++) {
unsigned sub_size = list_size / (delta_search_threads - i);
+ /* don't use too small segments or no deltas will be found */
+ if (sub_size < 2*window && i+1 < delta_search_threads)
+ sub_size = 0;
+
p[i].window = window;
p[i].depth = depth;
p[i].processed = processed;
diff --git a/builtin-prune.c b/builtin-prune.c
index 7b4ec80e62997fe70f53c92380cd4d66e80d62a0..545e9c1f9458ed936e2a99a208f75b70f368294c 100644 (file)
--- a/builtin-prune.c
+++ b/builtin-prune.c
#include "builtin.h"
#include "reachable.h"
#include "parse-options.h"
+#include "dir.h"
static const char * const prune_usage[] = {
"git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
while ((de = readdir(dir)) != NULL) {
char name[100];
unsigned char sha1[20];
- int len = strlen(de->d_name);
- switch (len) {
- case 2:
- if (de->d_name[1] != '.')
- break;
- case 1:
- if (de->d_name[0] != '.')
- break;
+ if (is_dot_or_dotdot(de->d_name))
continue;
- case 38:
+ if (strlen(de->d_name) == 38) {
sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, len+1);
+ memcpy(name+2, de->d_name, 39);
if (get_sha1_hex(name, sha1) < 0)
break;
diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c
index db67c3162c6b8ffaba793fd6082324aeae158f86..849f1fe6f9c703bd7717e54548300dfe6e495061 100644 (file)
--- a/builtin-receive-pack.c
+++ b/builtin-receive-pack.c
#include "remote.h"
#include "transport.h"
-static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
enum deny_action {
+ DENY_UNCONFIGURED,
DENY_IGNORE,
DENY_WARN,
DENY_REFUSE,
};
-static int deny_deletes = 0;
-static int deny_non_fast_forwards = 0;
-static enum deny_action deny_current_branch = DENY_WARN;
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
static int receive_fsck_objects;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int unpack_limit = 100;
static int report_status;
+static const char *head_name;
static char capabilities[] = " report-status delete-refs ";
static int capabilities_sent;
return 0;
}
+ if (strcmp(var, "receive.denydeletecurrent") == 0) {
+ deny_delete_current = parse_deny_action(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
}
}
-static int run_hook(const char *hook_name)
+static int run_receive_hook(const char *hook_name)
{
static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
struct command *cmd;
static int is_ref_checked_out(const char *ref)
{
- unsigned char sha1[20];
- const char *head;
-
if (is_bare_repository())
return 0;
- head = resolve_ref("HEAD", sha1, 0, NULL);
- if (!head)
+ if (!head_name)
return 0;
- return !strcmp(head, ref);
+ return !strcmp(head_name, ref);
+}
+
+static char *warn_unconfigured_deny_msg[] = {
+ "Updating the currently checked out branch may cause confusion,",
+ "as the index and work tree do not reflect changes that are in HEAD.",
+ "As a result, you may see the changes you just pushed into it",
+ "reverted when you run 'git diff' over there, and you may want",
+ "to run 'git reset --hard' before starting to work to recover.",
+ "",
+ "You can set 'receive.denyCurrentBranch' configuration variable to",
+ "'refuse' in the remote repository to forbid pushing into its",
+ "current branch."
+ "",
+ "To allow pushing into the current branch, you can set it to 'ignore';",
+ "but this is not recommended unless you arranged to update its work",
+ "tree to match what you pushed in some other way.",
+ "",
+ "To squelch this message, you can set it to 'warn'.",
+ "",
+ "Note that the default will change in a future version of git",
+ "to refuse updating the current branch unless you have the",
+ "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
+ warning("%s", warn_unconfigured_deny_msg[i]);
+}
+
+static char *warn_unconfigured_deny_delete_current_msg[] = {
+ "Deleting the current branch can cause confusion by making the next",
+ "'git clone' not check out any file.",
+ "",
+ "You can set 'receive.denyDeleteCurrent' configuration variable to",
+ "'refuse' in the remote repository to disallow deleting the current",
+ "branch.",
+ "",
+ "You can set it to 'ignore' to allow such a delete without a warning.",
+ "",
+ "To make this warning message less loud, you can set it to 'warn'.",
+ "",
+ "Note that the default will change in a future version of git",
+ "to refuse deleting the current branch unless you have the",
+ "configuration variable set to either 'ignore' or 'warn'."
+};
+
+static void warn_unconfigured_deny_delete_current(void)
+{
+ int i;
+ for (i = 0;
+ i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
+ i++)
+ warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
}
static const char *update(struct command *cmd)
return "funny refname";
}
- switch (deny_current_branch) {
- case DENY_IGNORE:
- break;
- case DENY_WARN:
- if (!is_ref_checked_out(name))
+ if (is_ref_checked_out(name)) {
+ switch (deny_current_branch) {
+ case DENY_IGNORE:
break;
- warning("updating the currently checked out branch; this may"
- " cause confusion,\n"
- "as the index and working tree do not reflect changes"
- " that are now in HEAD.");
- break;
- case DENY_REFUSE:
- if (!is_ref_checked_out(name))
+ case DENY_UNCONFIGURED:
+ case DENY_WARN:
+ warning("updating the current branch");
+ if (deny_current_branch == DENY_UNCONFIGURED)
+ warn_unconfigured_deny();
break;
- error("refusing to update checked out branch: %s", name);
- return "branch is currently checked out";
+ case DENY_REFUSE:
+ error("refusing to update checked out branch: %s", name);
+ return "branch is currently checked out";
+ }
}
if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
"but I can't find it!", sha1_to_hex(new_sha1));
return "bad pack";
}
- if (deny_deletes && is_null_sha1(new_sha1) &&
- !is_null_sha1(old_sha1) &&
- !prefixcmp(name, "refs/heads/")) {
- error("denying ref deletion for %s", name);
- return "deletion prohibited";
+
+ if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+ if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+ error("denying ref deletion for %s", name);
+ return "deletion prohibited";
+ }
+
+ if (!strcmp(name, head_name)) {
+ switch (deny_delete_current) {
+ case DENY_IGNORE:
+ break;
+ case DENY_WARN:
+ case DENY_UNCONFIGURED:
+ if (deny_delete_current == DENY_UNCONFIGURED)
+ warn_unconfigured_deny_delete_current();
+ warning("deleting the current branch");
+ break;
+ case DENY_REFUSE:
+ error("refusing to delete the current branch: %s", name);
+ return "deletion of the current branch prohibited";
+ }
+ }
}
+
if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
!is_null_sha1(old_sha1) &&
!prefixcmp(name, "refs/heads/")) {
static void execute_commands(const char *unpacker_error)
{
struct command *cmd = commands;
+ unsigned char sha1[20];
if (unpacker_error) {
while (cmd) {
return;
}
- if (run_hook(pre_receive_hook)) {
+ if (run_receive_hook(pre_receive_hook)) {
while (cmd) {
cmd->error_string = "pre-receive hook declined";
cmd = cmd->next;
return;
}
+ head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
while (cmd) {
cmd->error_string = update(cmd);
cmd = cmd->next;
unlink(pack_lockfile);
if (report_status)
report(unpack_status);
- run_hook(post_receive_hook);
+ run_receive_hook(post_receive_hook);
run_update_post_hook(commands);
}
return 0;
diff --git a/builtin-remote.c b/builtin-remote.c
index abc8dd8389be4a51b467b4f6d4f74e2037d65423..ac69d37c8af415a64cba88a888e9b80db95ef97a 100644 (file)
--- a/builtin-remote.c
+++ b/builtin-remote.c
struct branches_for_remote {
struct remote *remote;
- struct string_list *branches;
+ struct string_list *branches, *skipped;
struct known_remotes *keep;
};
return 0;
}
+ /* don't delete non-remote refs */
+ if (prefixcmp(refname, "refs/remotes")) {
+ /* advise user how to delete local branches */
+ if (!prefixcmp(refname, "refs/heads/"))
+ string_list_append(abbrev_branch(refname),
+ branches->skipped);
+ /* silently skip over other non-remote refs */
+ return 0;
+ }
+
/* make sure that symrefs are deleted */
if (flags & REF_ISSYMREF)
return unlink(git_path("%s", refname));
struct strbuf buf = STRBUF_INIT;
struct known_remotes known_remotes = { NULL, NULL };
struct string_list branches = { NULL, 0, 0, 1 };
- struct branches_for_remote cb_data = { NULL, &branches, &known_remotes };
- int i;
+ struct string_list skipped = { NULL, 0, 0, 1 };
+ struct branches_for_remote cb_data = {
+ NULL, &branches, &skipped, &known_remotes
+ };
+ int i, result;
if (argc != 2)
usage_with_options(builtin_remote_usage, options);
* refs, which are invalidated when deleting a branch.
*/
cb_data.remote = remote;
- i = for_each_ref(add_branch_for_removal, &cb_data);
+ result = for_each_ref(add_branch_for_removal, &cb_data);
strbuf_release(&buf);
- if (!i)
- i = remove_branches(&branches);
+ if (!result)
+ result = remove_branches(&branches);
string_list_clear(&branches, 1);
- return i;
+ if (skipped.nr) {
+ fprintf(stderr, skipped.nr == 1 ?
+ "Note: A non-remote branch was not removed; "
+ "to delete it, use:\n" :
+ "Note: Non-remote branches were not removed; "
+ "to delete them, use:\n");
+ for (i = 0; i < skipped.nr; i++)
+ fprintf(stderr, " git branch -d %s\n",
+ skipped.items[i].string);
+ }
+ string_list_clear(&skipped, 0);
+
+ return result;
}
static void show_list(const char *title, struct string_list *list,
OPT_END()
};
struct ref_states states;
+ const char *dangling_msg;
argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
if (argc < 1)
usage_with_options(builtin_remote_usage, options);
+ dangling_msg = (dry_run
+ ? " %s will become dangling!\n"
+ : " %s has become dangling!\n");
+
memset(&states, 0, sizeof(states));
for (; argc; argc--, argv++) {
int i;
printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
abbrev_ref(refname, "refs/remotes/"));
+ warn_dangling_symref(dangling_msg, refname);
}
/* NEEDSWORK: free remote */
diff --git a/builtin-rerere.c b/builtin-rerere.c
index d4dec6b7156b081bf63933d4de6d0b62ab2212c8..bd8fc77a7a65a21a401f917b75d36883cda634ed 100644 (file)
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
#include "builtin.h"
#include "cache.h"
+#include "dir.h"
#include "string-list.h"
#include "rerere.h"
#include "xdiff/xdiff.h"
git_config(git_rerere_gc_config, NULL);
dir = opendir(git_path("rr-cache"));
while ((e = readdir(dir))) {
- const char *name = e->d_name;
- if (name[0] == '.' &&
- (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+ if (is_dot_or_dotdot(e->d_name))
continue;
- then = rerere_created_at(name);
+ then = rerere_created_at(e->d_name);
if (!then)
continue;
- cutoff = (has_resolution(name)
+ cutoff = (has_resolution(e->d_name)
? cutoff_resolve : cutoff_noresolve);
if (then < now - cutoff * 86400)
- string_list_append(name, &to_remove);
+ string_list_append(e->d_name, &to_remove);
}
for (i = 0; i < to_remove.nr; i++)
unlink_rr_item(to_remove.items[i].string);
diff --git a/builtin-reset.c b/builtin-reset.c
index 9514b77f8c0b4e8a576e888df905b501e198df24..c0cb915c26b56a905807cefa4e246d99d199b297 100644 (file)
--- a/builtin-reset.c
+++ b/builtin-reset.c
#include "parse-options.h"
static const char * const git_reset_usage[] = {
- "git reset [--mixed | --soft | --hard] [-q] [<commit>]",
+ "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]",
"git reset [--mixed] <commit> [--] <paths>...",
NULL
};
+enum reset_type { MIXED, SOFT, HARD, MERGE, NONE };
+static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL };
+
static char *args_to_str(const char **argv)
{
char *buf = NULL;
return !access(git_path("MERGE_HEAD"), F_OK);
}
-static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int quiet)
+static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
{
int i = 0;
const char *args[6];
args[i++] = "read-tree";
if (!quiet)
args[i++] = "-v";
- args[i++] = "--reset";
- if (is_hard_reset)
+ switch (reset_type) {
+ case MERGE:
args[i++] = "-u";
+ args[i++] = "-m";
+ break;
+ case HARD:
+ args[i++] = "-u";
+ /* fallthrough */
+ default:
+ args[i++] = "--reset";
+ }
args[i++] = sha1_to_hex(sha1);
args[i] = NULL;
warning("Reflog action message too long: %.*s...", 50, buf);
}
-enum reset_type { MIXED, SOFT, HARD, NONE };
-static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
-
int cmd_reset(int argc, const char **argv, const char *prefix)
{
int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
OPT_SET_INT(0, "hard", &reset_type,
"reset HEAD, index and working tree", HARD),
+ OPT_SET_INT(0, "merge", &reset_type,
+ "reset HEAD, index and working tree", MERGE),
OPT_BOOLEAN('q', NULL, &quiet,
"disable showing new HEAD in hard reset and progress message"),
OPT_END()
if (is_merge() || read_cache() < 0 || unmerged_cache())
die("Cannot do a soft reset in the middle of a merge.");
}
- else if (reset_index_file(sha1, (reset_type == HARD), quiet))
+ else if (reset_index_file(sha1, reset_type, quiet))
die("Could not reset index file to revision '%s'.", rev);
/* Any resets update HEAD to the head being switched to,
diff --git a/builtin-revert.c b/builtin-revert.c
index d48313c7453c24e7ab4cbb3e024d05ec311edf7b..d210150671830c10f098463175e5a6e8d304c1a4 100644 (file)
--- a/builtin-revert.c
+++ b/builtin-revert.c
return helpbuf;
}
-static int index_is_dirty(void)
-{
- struct rev_info rev;
- init_revisions(&rev, NULL);
- setup_revisions(0, NULL, &rev, "HEAD");
- DIFF_OPT_SET(&rev.diffopt, QUIET);
- DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
- run_diff_index(&rev, 1);
- return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
-}
-
static struct tree *empty_tree(void)
{
struct tree *tree = xcalloc(1, sizeof(struct tree));
} else {
if (get_sha1("HEAD", head))
die ("You do not have a valid HEAD");
- if (index_is_dirty())
+ if (index_differs_from("HEAD", 0))
die ("Dirty index: cannot %s", me);
}
discard_cache();
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index e49290687f2a0c55317398ef8b04ac319018c91b..badd9120388569c79b137f679505b495cc1dbbe9 100644 (file)
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct string_list_item *item;
- struct string_list *onelines;
char namebuf[1024];
+ char emailbuf[1024];
size_t len;
const char *eol;
const char *boemail, *eoemail;
eoemail = strchr(boemail, '>');
if (!eoemail)
return;
- if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+
+ /* copy author name to namebuf, to support matching on both name and email */
+ memcpy(namebuf, author, boemail - author);
+ len = boemail - author;
+ while(len > 0 && isspace(namebuf[len-1]))
+ len--;
+ namebuf[len] = 0;
+
+ /* copy email name to emailbuf, to allow email replacement as well */
+ memcpy(emailbuf, boemail+1, eoemail - boemail);
+ emailbuf[eoemail - boemail - 1] = 0;
+
+ if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
while (author < boemail && isspace(*author))
author++;
for (len = 0;
if (log->email) {
size_t room = sizeof(namebuf) - len - 1;
- int maillen = eoemail - boemail + 1;
- snprintf(namebuf + len, room, " %.*s", maillen, boemail);
+ int maillen = strlen(emailbuf);
+ snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
}
- buffer = xstrdup(namebuf);
- item = string_list_insert(buffer, &log->list);
+ item = string_list_insert(namebuf, &log->list);
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list));
- else
- free(buffer);
/* Skip any leading whitespace, including any blank lines. */
while (*oneline && isspace(*oneline))
}
}
- onelines = item->util;
- if (onelines->nr >= onelines->alloc) {
- onelines->alloc = alloc_nr(onelines->nr);
- onelines->items = xrealloc(onelines->items,
- onelines->alloc
- * sizeof(struct string_list_item));
- }
-
- onelines->items[onelines->nr].util = NULL;
- onelines->items[onelines->nr++].string = buffer;
+ string_list_append(buffer, item->util);
}
static void read_from_stdin(struct shortlog *log)
{
memset(log, 0, sizeof(*log));
- read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+ read_mailmap(&log->mailmap, &log->common_repo_prefix);
log->list.strdup_strings = 1;
log->wrap = DEFAULT_WRAPLEN;
struct parse_opt_ctx_t ctx;
prefix = setup_git_directory_gently(&nongit);
+ git_config(git_default_config, NULL);
shortlog_init(&log);
init_revisions(&rev, prefix);
parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
}
onelines->strdup_strings = 1;
- string_list_clear(onelines, 1);
+ string_list_clear(onelines, 0);
free(onelines);
log->list.items[i].util = NULL;
}
log->list.strdup_strings = 1;
string_list_clear(&log->list, 1);
- log->mailmap.strdup_strings = 1;
- string_list_clear(&log->mailmap, 1);
+ clear_mailmap(&log->mailmap);
}
diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c
index bfc78bb3f6eff2f8e39649b9649ae7263f143ad9..6ae6bcc0e8d02d9af8a81a7d694c0bfd2c6c0514 100644 (file)
--- a/builtin-symbolic-ref.c
+++ b/builtin-symbolic-ref.c
check_symref(argv[0], quiet);
break;
case 2:
+ if (!strcmp(argv[0], "HEAD") &&
+ prefixcmp(argv[1], "refs/"))
+ die("Refusing to point HEAD outside of refs/");
create_symref(argv[0], argv[1], msg);
break;
default:
diff --git a/builtin-tag.c b/builtin-tag.c
index a3984998915921476a80839a07b133779ece72f3..01e73747d02f384c5e31b846340a4b586c84aab3 100644 (file)
--- a/builtin-tag.c
+++ b/builtin-tag.c
struct tag_filter {
const char *pattern;
int lines;
+ struct commit_list *with_commit;
};
#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
char *buf, *sp, *eol;
size_t len;
+ if (filter->with_commit) {
+ struct commit *commit;
+
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return 0;
+ if (!is_descendant_of(commit, filter->with_commit))
+ return 0;
+ }
+
if (!filter->lines) {
printf("%s\n", refname);
return 0;
return 0;
}
-static int list_tags(const char *pattern, int lines)
+static int list_tags(const char *pattern, int lines,
+ struct commit_list *with_commit)
{
struct tag_filter filter;
filter.pattern = pattern;
filter.lines = lines;
+ filter.with_commit = with_commit;
for_each_tag_ref(show_reference, (void *) &filter);
list = 0, delete = 0, verify = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
+ struct commit_list *with_commit = NULL;
struct option options[] = {
OPT_BOOLEAN('l', NULL, &list, "list tag names"),
{ OPTION_INTEGER, 'n', NULL, &lines, NULL,
OPT_STRING('u', NULL, &keyid, "key-id",
"use another key to sign the tag"),
OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"),
+
+ OPT_GROUP("Tag listing options"),
+ {
+ OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+ "print only tags that contain the commit",
+ PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t)"HEAD",
+ },
OPT_END()
};
if (list + delete + verify > 1)
usage_with_options(git_tag_usage, options);
if (list)
- return list_tags(argv[0], lines == -1 ? 0 : lines);
+ return list_tags(argv[0], lines == -1 ? 0 : lines,
+ with_commit);
if (lines != -1)
die("-n option is only allowed with -l.");
+ if (with_commit)
+ die("--contains option is only allowed with -l.");
if (delete)
return for_each_tag_name(argv, delete_tag);
if (verify)
diff --git a/builtin-update-index.c b/builtin-update-index.c
index daca0f775e4b49f2576a7c444895146d05b7022c..dd43d5bef425af318a884bfc235a4aff40931633 100644 (file)
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
static void read_head_pointers(void)
{
if (read_ref("HEAD", head_sha1))
- die("No HEAD -- no initial commit yet?\n");
+ die("No HEAD -- no initial commit yet?");
if (read_ref("MERGE_HEAD", merge_head_sha1)) {
fprintf(stderr, "Not in the middle of a merge.\n");
exit(0);
diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c
index 25a29f11a4b9642c1bd367b81779d8f09ae04604..0ee0a9af60b0601fe0e6db98ec582e059f5e9064 100644 (file)
--- a/builtin-verify-pack.c
+++ b/builtin-verify-pack.c
return err;
}
-static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+static const char verify_pack_usage[] = "git verify-pack [-v] <pack>...";
int cmd_verify_pack(int argc, const char **argv, const char *prefix)
{
diff --git a/bundle.c b/bundle.c
index b20f2101f265786ed61e2ca08764aae249bad9d4..d0dd818b31faa04caa4d418a39ad3020a919aa2d 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -167,6 +167,32 @@ int list_bundle_refs(struct bundle_header *header, int argc, const char **argv)
return list_refs(&header->references, argc, argv);
}
+static int is_tag_in_date_range(struct object *tag, struct rev_info *revs)
+{
+ unsigned long size;
+ enum object_type type;
+ char *buf, *line, *lineend;
+ unsigned long date;
+
+ if (revs->max_age == -1 && revs->min_age == -1)
+ return 1;
+
+ buf = read_sha1_file(tag->sha1, &type, &size);
+ if (!buf)
+ return 1;
+ line = memmem(buf, size, "\ntagger ", 8);
+ if (!line++)
+ return 1;
+ lineend = memchr(line, buf + size - line, '\n');
+ line = memchr(line, lineend ? lineend - line : buf + size - line, '>');
+ if (!line++)
+ return 1;
+ date = strtoul(line, NULL, 10);
+ free(buf);
+ return (revs->max_age == -1 || revs->max_age < date) &&
+ (revs->min_age == -1 || revs->min_age > date);
+}
+
int create_bundle(struct bundle_header *header, const char *path,
int argc, const char **argv)
{
flag = 0;
display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
+ if (e->item->type == OBJ_TAG &&
+ !is_tag_in_date_range(e->item, &revs)) {
+ e->item->flags |= UNINTERESTING;
+ continue;
+ }
+
/*
* Make sure the refs we wrote out is correct; --max-count and
* other limiting options could have prevented all the tips
index 0e2f219b2b4d9ff5944e68dbaf2338c76055161f..189151de25ffd1a6671b7a70f359fa9b94b82173 100644 (file)
--- a/cache.h
+++ b/cache.h
const char *make_absolute_path(const char *path);
const char *make_nonrelative_path(const char *path);
const char *make_relative_path(const char *abs, const char *base);
-int normalize_absolute_path(char *buf, const char *path);
+int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, const char *prefix_list);
+char *strip_path_suffix(const char *path, const char *suffix);
/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
extern int sha1_object_info(const unsigned char *, unsigned long *);
@@ -636,9 +637,6 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig
extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
extern int force_object_loose(const unsigned char *sha1, time_t mtime);
-/* just like read_sha1_file(), but non fatal in presence of bad objects */
-extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size);
-
/* global flag to enable extra checks when accessing packed objects */
extern int do_check_packed_object_crc;
extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+extern int interpret_nth_last_branch(const char *str, struct strbuf *);
extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
extern const char *ref_rev_parse_rules[];
DATE_SHORT,
DATE_LOCAL,
DATE_ISO8601,
- DATE_RFC2822
+ DATE_RFC2822,
+ DATE_RAW
};
const char *show_date(unsigned long time, int timezone, enum date_mode mode);
extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
extern int has_symlink_leading_path(int len, const char *name);
+extern int has_symlink_or_noent_leading_path(int len, const char *name);
+extern int has_dirs_only_path(int len, const char *name, int prefix_len);
+extern void invalidate_lstat_cache(int len, const char *name);
+extern void clear_lstat_cache(void);
extern struct alternate_object_database {
struct alternate_object_database *next;
extern const char *git_commit_encoding;
extern const char *git_log_output_encoding;
+extern const char *git_mailmap_file;
/* IO helper functions */
extern void maybe_flush_or_die(FILE *, const char *);
extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
/* ls-files */
-int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
void overlay_tree_on_cache(const char *tree_name, const char *prefix);
index fc0b72ad59b13e4bd86372e5e81b4f400c99d26e..db4dccfb77d80c9bd8981719fe2d0dc17c6772b6 100644 (file)
--- a/color.c
+++ b/color.c
}
void color_parse(const char *value, const char *var, char *dst)
+{
+ color_parse_mem(value, strlen(value), var, dst);
+}
+
+void color_parse_mem(const char *value, int value_len, const char *var,
+ char *dst)
{
const char *ptr = value;
+ int len = value_len;
int attr = -1;
int fg = -2;
int bg = -2;
- if (!strcasecmp(value, "reset")) {
+ if (!strncasecmp(value, "reset", len)) {
strcpy(dst, "\033[m");
return;
}
/* [fg [bg]] [attr] */
- while (*ptr) {
+ while (len > 0) {
const char *word = ptr;
- int val, len = 0;
+ int val, wordlen = 0;
- while (word[len] && !isspace(word[len]))
- len++;
+ while (len > 0 && !isspace(word[wordlen])) {
+ wordlen++;
+ len--;
+ }
- ptr = word + len;
- while (*ptr && isspace(*ptr))
+ ptr = word + wordlen;
+ while (len > 0 && isspace(*ptr)) {
ptr++;
+ len--;
+ }
- val = parse_color(word, len);
+ val = parse_color(word, wordlen);
if (val >= -1) {
if (fg == -2) {
fg = val;
}
goto bad;
}
- val = parse_attr(word, len);
+ val = parse_attr(word, wordlen);
if (val < 0 || attr != -1)
goto bad;
attr = val;
*dst = 0;
return;
bad:
- die("bad config value '%s' for variable '%s'", value, var);
+ die("bad color value '%.*s' for variable '%s'", value_len, value, var);
}
int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
va_end(args);
return r;
}
+
+/*
+ * This function splits the buffer by newlines and colors the lines individually.
+ *
+ * Returns 0 on success.
+ */
+int color_fwrite_lines(FILE *fp, const char *color,
+ size_t count, const char *buf)
+{
+ if (!*color)
+ return fwrite(buf, count, 1, fp) != 1;
+ while (count) {
+ char *p = memchr(buf, '\n', count);
+ if (p != buf && (fputs(color, fp) < 0 ||
+ fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
+ fputs(COLOR_RESET, fp) < 0))
+ return -1;
+ if (!p)
+ return 0;
+ if (fputc('\n', fp) < 0)
+ return -1;
+ count -= p + 1 - buf;
+ buf = p + 1;
+ }
+ return 0;
+}
+
+
index 6cf5c88aaf8d0e38e2853e6fd212e3cdd6c180cb..5019df82f79f1888b3aa57b9752a6a55b13f475a 100644 (file)
--- a/color.h
+++ b/color.h
int git_color_default_config(const char *var, const char *value, void *cb);
int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);
-void color_parse(const char *var, const char *value, char *dst);
+void color_parse(const char *value, const char *var, char *dst);
+void color_parse_mem(const char *value, int len, const char *var, char *dst);
int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
+int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
#endif /* COLOR_H */
diff --git a/commit.c b/commit.c
index c99db162a48e0a47e73e6bfc2ae5fd4b7c9dfa1a..aa3b35b6a86891ac9d0628e20a6a46d506bf7700 100644 (file)
--- a/commit.c
+++ b/commit.c
return get_merge_bases_many(one, 1, &two, cleanup);
}
+int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
+{
+ if (!with_commit)
+ return 1;
+ while (with_commit) {
+ struct commit *other;
+
+ other = with_commit->item;
+ with_commit = with_commit->next;
+ if (in_merge_bases(other, &commit, 1))
+ return 1;
+ }
+ return 0;
+}
+
int in_merge_bases(struct commit *commit, struct commit **reference, int num)
{
struct commit_list *bases, *b;
diff --git a/commit.h b/commit.h
index 3a7b06a828930ca23d0fb045feab993a1452b2d3..ba9f63813eba004ae409eba8741266a074161239 100644 (file)
--- a/commit.h
+++ b/commit.h
extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
+int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit **, int);
extern int interactive_add(int argc, const char **argv, const char *prefix);
diff --git a/compat/mingw.h b/compat/mingw.h
index 4f275cb8e6a67515292a9dfc60bd1343065067a9..a25589880130f2232aaf626cddcd739ac80dd378 100644 (file)
--- a/compat/mingw.h
+++ b/compat/mingw.h
#define WEXITSTATUS(x) ((x) & 0xff)
#define WIFSIGNALED(x) ((unsigned)(x) > 259)
-#define SIGKILL 0
-#define SIGCHLD 0
-#define SIGPIPE 0
-#define SIGHUP 0
-#define SIGQUIT 0
-#define SIGALRM 100
+#define SIGHUP 1
+#define SIGQUIT 3
+#define SIGKILL 9
+#define SIGPIPE 13
+#define SIGALRM 14
+#define SIGCHLD 17
#define F_GETFD 1
#define F_SETFD 2
diff --git a/config.c b/config.c
index 790405a213b12a4d1c62d9354e1292e0dc6af057..0c8c76f13b03028ad400b1c5b72b3cf0a7ec0940 100644 (file)
--- a/config.c
+++ b/config.c
return 0;
}
+static int git_default_mailmap_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "mailmap.file"))
+ return git_config_string(&git_mailmap_file, var, value);
+
+ /* Add other config variables here and to Documentation/config.txt. */
+ return 0;
+}
+
int git_default_config(const char *var, const char *value, void *dummy)
{
if (!prefixcmp(var, "core."))
if (!prefixcmp(var, "branch."))
return git_default_branch_config(var, value);
+ if (!prefixcmp(var, "mailmap."))
+ return git_default_mailmap_config(var, value);
+
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
pager_use_color = git_config_bool(var,value);
return 0;
diff --git a/config.mak.in b/config.mak.in
index 14dfb21fa59efb7482ea0727845c8198cc996271..7cce0c12d507b222d47d8469abf59bf3ef9096b9 100644 (file)
--- a/config.mak.in
+++ b/config.mak.in
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
-gitexecdir = @libexecdir@/git-core/
+gitexecdir = @libexecdir@/git-core
datarootdir = @datarootdir@
-template_dir = @datadir@/git-core/templates/
+template_dir = @datadir@/git-core/templates
mandir=@mandir@
FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@
SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@
NO_PTHREADS=@NO_PTHREADS@
+THREADED_DELTA_SEARCH=@THREADED_DELTA_SEARCH@
PTHREAD_LIBS=@PTHREAD_LIBS@
diff --git a/configure.ac b/configure.ac
index 0a5fc8c6f6f91099c3c5df10f08240e547126e0a..082a03d3cf6e9416c80b5d486c3191d3ac64cca4 100644 (file)
--- a/configure.ac
+++ b/configure.ac
#
AC_PROG_CC([cc gcc])
# which switch to pass runtime path to dynamic libraries to the linker
-AC_CACHE_CHECK([if linker supports -R], ld_dashr, [
+AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -R /"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_dashr=yes], [ld_dashr=no])
+ AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
-if test "$ld_dashr" = "yes"; then
+if test "$git_cv_ld_dashr" = "yes"; then
AC_SUBST(CC_LD_DYNPATH, [-R])
else
- AC_CACHE_CHECK([if linker supports -Wl,-rpath,], ld_wl_rpath, [
+ AC_CACHE_CHECK([if linker supports -Wl,-rpath,], git_cv_ld_wl_rpath, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_wl_rpath=yes], [ld_wl_rpath=no])
+ AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
- if test "$ld_wl_rpath" = "yes"; then
+ if test "$git_cv_ld_wl_rpath" = "yes"; then
AC_SUBST(CC_LD_DYNPATH, [-Wl,-rpath,])
else
- AC_CACHE_CHECK([if linker supports -rpath], ld_rpath, [
+ AC_CACHE_CHECK([if linker supports -rpath], git_cv_ld_rpath, [
SAVE_LDFLAGS="${LDFLAGS}"
LDFLAGS="${SAVE_LDFLAGS} -rpath /"
- AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_rpath=yes], [ld_rpath=no])
+ AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no])
LDFLAGS="${SAVE_LDFLAGS}"
])
- if test "$ld_rpath" = "yes"; then
+ if test "$git_cv_ld_rpath" = "yes"; then
AC_SUBST(CC_LD_DYNPATH, [-rpath])
else
AC_MSG_WARN([linker does not support runtime path to dynamic libraries])
#
# Define NO_PTHREADS if we do not have pthreads
#
-# Define PTHREAD_LIBS to the linker flag used for Pthread support.
+# Define PTHREAD_LIBS to the linker flag used for Pthread support and define
+# THREADED_DELTA_SEARCH if Pthreads are available.
AC_LANG_CONFTEST([AC_LANG_PROGRAM(
[[#include <pthread.h>]],
[[pthread_mutex_t test_mutex;]]
${CC} -pthread conftest.c -o conftest.o > /dev/null 2>&1
if test $? -eq 0;then
PTHREAD_LIBS="-pthread"
+ THREADED_DELTA_SEARCH=YesPlease
else
${CC} -lpthread conftest.c -o conftest.o > /dev/null 2>&1
if test $? -eq 0;then
PTHREAD_LIBS="-lpthread"
+ THREADED_DELTA_SEARCH=YesPlease
else
NO_PTHREADS=UnfortunatelyYes
fi
fi
AC_SUBST(PTHREAD_LIBS)
AC_SUBST(NO_PTHREADS)
+AC_SUBST(THREADED_DELTA_SEARCH)
## Site configuration (override autodetection)
## --with-PACKAGE[=ARG] and --without-PACKAGE
diff --git a/connect.c b/connect.c
index 2f55ad2c256bc01b3062b99251af4386eef5af22..2f23ab3b87e500137fe0af957901c30e61434564 100644 (file)
--- a/connect.c
+++ b/connect.c
/* Not numeric */
struct servent *se = getservbyname(port,"tcp");
if ( !se )
- die("Unknown port %s\n", port);
+ die("Unknown port %s", port);
nport = se->s_port;
}
index e00454983ee16624910e40fec9ddb12849110b1b..0a3092f646872867b0060d79c0fd1c85a40e24f4 100755 (executable)
+#!bash
#
# bash completion support for core Git.
#
# are currently in a git repository. The %s token will be
# the name of the current branch.
#
+# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
+# value, unstaged (*) and staged (+) changes will be shown next
+# to the branch name. You can configure this per-repository
+# with the bash.showDirtyState variable, which defaults to true
+# once GIT_PS1_SHOWDIRTYSTATE is enabled.
+#
# To submit patches:
#
# *) Read Documentation/SubmittingPatches
*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
esac
+# __gitdir accepts 0 or 1 arguments (i.e., location)
+# returns location of .git repo
__gitdir ()
{
- if [ -z "$1" ]; then
+ if [ -z "${1-}" ]; then
if [ -n "$__git_dir" ]; then
echo "$__git_dir"
elif [ -d .git ]; then
fi
}
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
+# returns text to add to bash PS1 prompt (includes branch name)
__git_ps1 ()
{
local g="$(git rev-parse --git-dir 2>/dev/null)"
fi
fi
- if [ -n "$1" ]; then
- printf "$1" "${b##refs/heads/}$r"
+ local w
+ local i
+
+ if test -n "${GIT_PS1_SHOWDIRTYSTATE-}"; then
+ if test "$(git config --bool bash.showDirtyState)" != "false"; then
+ git diff --no-ext-diff --ignore-submodules \
+ --quiet --exit-code || w="*"
+ if git rev-parse --quiet --verify HEAD >/dev/null; then
+ git diff-index --cached --quiet \
+ --ignore-submodules HEAD -- || i="+"
+ else
+ i="#"
+ fi
+ fi
+ fi
+
+ if [ -n "${1-}" ]; then
+ printf "$1" "${b##refs/heads/}$w$i$r"
else
- printf " (%s)" "${b##refs/heads/}$r"
+ printf " (%s)" "${b##refs/heads/}$w$i$r"
fi
fi
}
+# __gitcomp_1 requires 2 arguments
__gitcomp_1 ()
{
local c IFS=' '$'\t'$'\n'
done
}
+# __gitcomp accepts 1, 2, 3, or 4 arguments
+# generates completion reply with compgen
__gitcomp ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
;;
*)
local IFS=$'\n'
- COMPREPLY=($(compgen -P "$2" \
- -W "$(__gitcomp_1 "$1" "$4")" \
+ COMPREPLY=($(compgen -P "${2-}" \
+ -W "$(__gitcomp_1 "${1-}" "${4-}")" \
-- "$cur"))
;;
esac
}
+# __git_heads accepts 0 or 1 arguments (to pass to __gitdir)
__git_heads ()
{
- local cmd i is_hash=y dir="$(__gitdir "$1")"
+ local cmd i is_hash=y dir="$(__gitdir "${1-}")"
if [ -d "$dir" ]; then
git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
refs/heads
return
fi
- for i in $(git ls-remote "$1" 2>/dev/null); do
+ for i in $(git ls-remote "${1-}" 2>/dev/null); do
case "$is_hash,$i" in
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
done
}
+# __git_tags accepts 0 or 1 arguments (to pass to __gitdir)
__git_tags ()
{
- local cmd i is_hash=y dir="$(__gitdir "$1")"
+ local cmd i is_hash=y dir="$(__gitdir "${1-}")"
if [ -d "$dir" ]; then
git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
refs/tags
return
fi
- for i in $(git ls-remote "$1" 2>/dev/null); do
+ for i in $(git ls-remote "${1-}" 2>/dev/null); do
case "$is_hash,$i" in
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
done
}
+# __git_refs accepts 0 or 1 arguments (to pass to __gitdir)
__git_refs ()
{
- local i is_hash=y dir="$(__gitdir "$1")"
+ local i is_hash=y dir="$(__gitdir "${1-}")"
local cur="${COMP_WORDS[COMP_CWORD]}" format refs
if [ -d "$dir" ]; then
case "$cur" in
done
}
+# __git_refs2 requires 1 argument (to pass to __git_refs)
__git_refs2 ()
{
local i
done
}
+# __git_refs_remotes requires 1 argument (to pass to ls-remote)
__git_refs_remotes ()
{
local cmd i is_hash=y
done
}
+# __git_aliased_command requires 1 argument
__git_aliased_command ()
{
local word cmdline=$(git --git-dir="$(__gitdir)" \
done
}
+# __git_find_subcommand requires 1 argument
__git_find_subcommand ()
{
local word subcommand c=1
--*)
__gitcomp "
--interactive --refresh --patch --update --dry-run
- --ignore-errors
+ --ignore-errors --intent-to-add
"
return
esac
done
case "${COMP_WORDS[COMP_CWORD]}" in
- --*=*) COMPREPLY=() ;;
--*)
__gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev
__gitcomp "$(__git_refs)"
}
-_git_diff ()
-{
- __git_has_doubledash && return
-
- local cur="${COMP_WORDS[COMP_CWORD]}"
- case "$cur" in
- --*)
- __gitcomp "--cached --stat --numstat --shortstat --summary
+__git_diff_common_options="--stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
--no-color --color-words --no-renames --check
--full-index --binary --abbrev --diff-filter=
- --find-copies-harder --pickaxe-all --pickaxe-regex
+ --find-copies-harder
--text --ignore-space-at-eol --ignore-space-change
--ignore-all-space --exit-code --quiet --ext-diff
--no-ext-diff
--no-prefix --src-prefix= --dst-prefix=
+ --inter-hunk-context=
+ --patience
+ --raw
+"
+
+_git_diff ()
+{
+ __git_has_doubledash && return
+
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --*)
+ __gitcomp "--cached --pickaxe-all --pickaxe-regex
--base --ours --theirs
+ $__git_diff_common_options
"
return
;;
--not --all
--cover-letter
--no-prefix --src-prefix= --dst-prefix=
+ --inline --suffix= --ignore-if-in-upstream
+ --subject-prefix=
"
return
;;
__git_complete_file
}
+# Options that go well for log, shortlog and gitk
+__git_log_common_options="
+ --not --all
+ --branches --tags --remotes
+ --first-parent --no-merges
+ --max-count=
+ --max-age= --since= --after=
+ --min-age= --until= --before=
+"
+# Options that go well for log and gitk (not shortlog)
+__git_log_gitk_options="
+ --dense --sparse --full-history
+ --simplify-merges --simplify-by-decoration
+ --left-right
+"
+# Options that go well for log and shortlog (not gitk)
+__git_log_shortlog_options="
+ --author= --committer= --grep=
+ --all-match
+"
+
+__git_log_pretty_formats="oneline short medium full fuller email raw format:"
+
_git_log ()
{
__git_has_doubledash && return
local cur="${COMP_WORDS[COMP_CWORD]}"
+ local g="$(git rev-parse --git-dir 2>/dev/null)"
+ local merge=""
+ if [ -f $g/MERGE_HEAD ]; then
+ merge="--merge"
+ fi
case "$cur" in
--pretty=*)
- __gitcomp "
- oneline short medium full fuller email raw
+ __gitcomp "$__git_log_pretty_formats
" "" "${cur##--pretty=}"
return
;;
;;
--*)
__gitcomp "
- --max-count= --max-age= --since= --after=
- --min-age= --before= --until=
+ $__git_log_common_options
+ $__git_log_shortlog_options
+ $__git_log_gitk_options
--root --topo-order --date-order --reverse
- --no-merges --follow
+ --follow
--abbrev-commit --abbrev=
--relative-date --date=
- --author= --committer= --grep=
- --all-match
- --pretty= --name-status --name-only --raw
- --not --all
- --left-right --cherry-pick
+ --pretty=
+ --cherry-pick
--graph
- --stat --numstat --shortstat
- --decorate --diff-filter=
- --color-words --walk-reflogs
- --parents --children --full-history
- --merge
+ --decorate
+ --walk-reflogs
+ --parents --children
+ $merge
+ $__git_diff_common_options
+ --pickaxe-all --pickaxe-regex
"
return
;;
--*)
__gitcomp "
--no-commit --no-stat --log --no-log --squash --strategy
+ --commit --stat --no-squash --ff --no-ff
"
return
esac
__gitcomp "$(__git_merge_strategies)"
return
;;
- color.branch|color.diff|color.status)
+ color.branch|color.diff|color.interactive|color.status|color.ui)
__gitcomp "always never auto"
return
;;
+ color.pager)
+ __gitcomp "false true"
+ return
+ ;;
color.*.*)
__gitcomp "
normal black red green yellow blue magenta cyan white
_git_remote ()
{
- local subcommands="add rm show prune update"
+ local subcommands="add rename rm show prune update"
local subcommand="$(__git_find_subcommand "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
fi
case "$subcommand" in
- rm|show|prune)
+ rename|rm|show|prune)
__gitcomp "$(__git_remotes)"
;;
update)
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
- __gitcomp "--mixed --hard --soft"
+ __gitcomp "--merge --mixed --hard --soft"
return
;;
esac
case "$cur" in
--*)
__gitcomp "
- --max-count= --max-age= --since= --after=
- --min-age= --before= --until=
- --no-merges
- --author= --committer= --grep=
- --all-match
- --not --all
+ $__git_log_common_options
+ $__git_log_shortlog_options
--numbered --summary
"
return
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--pretty=*)
- __gitcomp "
- oneline short medium full fuller email raw
+ __gitcomp "$__git_log_pretty_formats
" "" "${cur##--pretty=}"
return
;;
--*)
- __gitcomp "--pretty="
+ __gitcomp "--pretty=
+ $__git_diff_common_options
+ "
return
;;
esac
local subcommands="
init fetch clone rebase dcommit log find-rev
set-tree commit-diff info create-ignore propget
- proplist show-ignore show-externals
+ proplist show-ignore show-externals branch tag blame
+ migrate
"
local subcommand="$(__git_find_subcommand "$subcommands")"
if [ -z "$subcommand" ]; then
--follow-parent --authors-file= --repack=
--no-metadata --use-svm-props --use-svnsync-props
--log-window-size= --no-checkout --quiet
- --repack-flags --user-log-author $remote_opts
+ --repack-flags --use-log-author --localtime
+ --ignore-paths= $remote_opts
"
local init_opts="
--template= --shared= --trunk= --tags=
--branches= --stdlayout --minimize-url
--no-metadata --use-svm-props --use-svnsync-props
- --rewrite-root= $remote_opts
+ --rewrite-root= --prefix= --use-log-author
+ --add-author-from $remote_opts
"
local cmt_opts="
--edit --rmdir --find-copies-harder --copy-similarity=
dcommit,--*)
__gitcomp "
--merge --strategy= --verbose --dry-run
- --fetch-all --no-rebase $cmt_opts $fc_opts
+ --fetch-all --no-rebase --commit-url
+ --revision $cmt_opts $fc_opts
"
;;
set-tree,--*)
__gitcomp "
--limit= --revision= --verbose --incremental
--oneline --show-commit --non-recursive
- --authors-file=
+ --authors-file= --color
"
;;
rebase,--*)
__gitcomp "
--merge --verbose --strategy= --local
- --fetch-all $fc_opts
+ --fetch-all --dry-run $fc_opts
"
;;
commit-diff,--*)
info,--*)
__gitcomp "--url"
;;
+ branch,--*)
+ __gitcomp "--dry-run --message --tag"
+ ;;
+ tag,--*)
+ __gitcomp "--dry-run --message"
+ ;;
+ blame,--*)
+ __gitcomp "--git-format"
+ ;;
+ migrate,--*)
+ __gitcomp "
+ --config-dir= --ignore-paths= --minimize
+ --no-auth-cache --username=
+ "
+ ;;
*)
COMPREPLY=()
;;
if [ -z "$command" ]; then
case "${COMP_WORDS[COMP_CWORD]}" in
- --*=*) COMPREPLY=() ;;
--*) __gitcomp "
--paginate
--no-pager
show) _git_show ;;
show-branch) _git_show_branch ;;
stash) _git_stash ;;
+ stage) _git_add ;;
submodule) _git_submodule ;;
svn) _git_svn ;;
tag) _git_tag ;;
fi
case "$cur" in
--*)
- __gitcomp "--not --all $merge"
+ __gitcomp "
+ $__git_log_common_options
+ $__git_log_gitk_options
+ $merge
+ "
return
;;
esac
__git_complete_revlist
}
-complete -o default -o nospace -F _git git
-complete -o default -o nospace -F _gitk gitk
+complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \
+ || complete -o default -o nospace -F _git git
+complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \
+ || complete -o default -o nospace -F _gitk gitk
# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
#
if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-complete -o default -o nospace -F _git git.exe
+complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
+ || complete -o default -o nospace -F _git git.exe
fi
diff --git a/contrib/difftool/git-difftool b/contrib/difftool/git-difftool
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env perl
+# Copyright (c) 2009 David Aguilar
+#
+# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+# git-difftool-helper script. This script exports
+# GIT_EXTERNAL_DIFF and GIT_PAGER for use by git, and
+# GIT_DIFFTOOL_NO_PROMPT and GIT_MERGE_TOOL for use by git-difftool-helper.
+# Any arguments that are unknown to this script are forwarded to 'git diff'.
+
+use strict;
+use warnings;
+use Cwd qw(abs_path);
+use File::Basename qw(dirname);
+
+my $DIR = abs_path(dirname($0));
+
+
+sub usage
+{
+ print << 'USAGE';
+usage: git difftool [--tool=<tool>] [--no-prompt] ["git diff" options]
+USAGE
+ exit 1;
+}
+
+sub setup_environment
+{
+ $ENV{PATH} = "$DIR:$ENV{PATH}";
+ $ENV{GIT_PAGER} = '';
+ $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool-helper';
+}
+
+sub exe
+{
+ my $exe = shift;
+ return defined $ENV{COMSPEC} ? "$exe.exe" : $exe;
+}
+
+sub generate_command
+{
+ my @command = (exe('git'), 'diff');
+ my $skip_next = 0;
+ my $idx = -1;
+ for my $arg (@ARGV) {
+ $idx++;
+ if ($skip_next) {
+ $skip_next = 0;
+ next;
+ }
+ if ($arg eq '-t' or $arg eq '--tool') {
+ usage() if $#ARGV <= $idx;
+ $ENV{GIT_MERGE_TOOL} = $ARGV[$idx + 1];
+ $skip_next = 1;
+ next;
+ }
+ if ($arg =~ /^--tool=/) {
+ $ENV{GIT_MERGE_TOOL} = substr($arg, 7);
+ next;
+ }
+ if ($arg eq '--no-prompt') {
+ $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+ next;
+ }
+ if ($arg eq '-h' or $arg eq '--help') {
+ usage();
+ }
+ push @command, $arg;
+ }
+ return @command
+}
+
+setup_environment();
+exec(generate_command());
diff --git a/contrib/difftool/git-difftool-helper b/contrib/difftool/git-difftool-helper
--- /dev/null
@@ -0,0 +1,240 @@
+#!/bin/sh
+# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
+# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff,
+# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools.
+# This script is typically launched by using the 'git difftool'
+# convenience command.
+#
+# Copyright (c) 2009 David Aguilar
+
+# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
+should_prompt () {
+ ! test -n "$GIT_DIFFTOOL_NO_PROMPT"
+}
+
+# Should we keep the backup .orig file?
+keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
+keep_backup () {
+ test "$keep_backup_mode" = "true"
+}
+
+# This function manages the backup .orig file.
+# A backup $MERGED.orig file is created if changes are detected.
+cleanup_temp_files () {
+ if test -n "$MERGED"; then
+ if keep_backup && test "$MERGED" -nt "$BACKUP"; then
+ test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
+ else
+ rm -f -- "$BACKUP"
+ fi
+ fi
+}
+
+# This is called when users Ctrl-C out of git-difftool-helper
+sigint_handler () {
+ cleanup_temp_files
+ exit 1
+}
+
+# This function prepares temporary files and launches the appropriate
+# merge tool.
+launch_merge_tool () {
+ # Merged is the filename as it appears in the work tree
+ # Local is the contents of a/filename
+ # Remote is the contents of b/filename
+ # Custom merge tool commands might use $BASE so we provide it
+ MERGED="$1"
+ LOCAL="$2"
+ REMOTE="$3"
+ BASE="$1"
+ ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
+ BACKUP="$MERGED.BACKUP.$ext"
+
+ # Create and ensure that we clean up $BACKUP
+ test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
+ trap sigint_handler INT
+
+ # $LOCAL and $REMOTE are temporary files so prompt
+ # the user with the real $MERGED name before launching $merge_tool.
+ if should_prompt; then
+ printf "\nViewing: '$MERGED'\n"
+ printf "Hit return to launch '%s': " "$merge_tool"
+ read ans
+ fi
+
+ # Run the appropriate merge tool command
+ case "$merge_tool" in
+ kdiff3)
+ basename=$(basename "$MERGED")
+ "$merge_tool_path" --auto \
+ --L1 "$basename (A)" \
+ --L2 "$basename (B)" \
+ -o "$MERGED" "$LOCAL" "$REMOTE" \
+ > /dev/null 2>&1
+ ;;
+
+ kompare)
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+ ;;
+
+ tkdiff)
+ "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
+ ;;
+
+ meld)
+ "$merge_tool_path" "$LOCAL" "$REMOTE"
+ ;;
+
+ vimdiff)
+ "$merge_tool_path" -c "wincmd l" "$LOCAL" "$REMOTE"
+ ;;
+
+ gvimdiff)
+ "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$REMOTE"
+ ;;
+
+ xxdiff)
+ "$merge_tool_path" \
+ -X \
+ -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+ -R 'Accel.Search: "Ctrl+F"' \
+ -R 'Accel.SearchForward: "Ctrl-G"' \
+ --merged-file "$MERGED" \
+ "$LOCAL" "$REMOTE"
+ ;;
+
+ opendiff)
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ -merge "$MERGED" | cat
+ ;;
+
+ ecmerge)
+ "$merge_tool_path" "$LOCAL" "$REMOTE" \
+ --default --mode=merge2 --to="$MERGED"
+ ;;
+
+ emerge)
+ "$merge_tool_path" -f emerge-files-command \
+ "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
+ ;;
+
+ *)
+ if test -n "$merge_tool_cmd"; then
+ ( eval $merge_tool_cmd )
+ fi
+ ;;
+ esac
+
+ cleanup_temp_files
+}
+
+# Verifies that mergetool.<tool>.cmd exists
+valid_custom_tool() {
+ merge_tool_cmd="$(git config mergetool.$1.cmd)"
+ test -n "$merge_tool_cmd"
+}
+
+# Verifies that the chosen merge tool is properly setup.
+# Built-in merge tools are always valid.
+valid_tool() {
+ case "$1" in
+ kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
+ ;; # happy
+ *)
+ if ! valid_custom_tool "$1"
+ then
+ return 1
+ fi
+ ;;
+ esac
+}
+
+# Sets up the merge_tool_path variable.
+# This handles the mergetool.<tool>.path configuration.
+init_merge_tool_path() {
+ merge_tool_path=$(git config mergetool."$1".path)
+ if test -z "$merge_tool_path"; then
+ case "$1" in
+ emerge)
+ merge_tool_path=emacs
+ ;;
+ *)
+ merge_tool_path="$1"
+ ;;
+ esac
+ fi
+}
+
+# Allow the GIT_MERGE_TOOL variable to provide a default value
+test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
+
+# If not merge tool was specified then use the merge.tool
+# configuration variable. If that's invalid then reset merge_tool.
+if test -z "$merge_tool"; then
+ merge_tool=$(git config merge.tool)
+ if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
+ echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
+ echo >&2 "Resetting to default..."
+ unset merge_tool
+ fi
+fi
+
+# Try to guess an appropriate merge tool if no tool has been set.
+if test -z "$merge_tool"; then
+ # We have a $DISPLAY so try some common UNIX merge tools
+ if test -n "$DISPLAY"; then
+ # If gnome then prefer meld, otherwise, prefer kdiff3 or kompare
+ if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+ merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff"
+ else
+ merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff"
+ fi
+ fi
+ if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
+ # $EDITOR is emacs so add emerge as a candidate
+ merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
+ elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+ # $EDITOR is vim so add vimdiff as a candidate
+ merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
+ else
+ merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
+ fi
+ echo "merge tool candidates: $merge_tool_candidates"
+
+ # Loop over each candidate and stop when a valid merge tool is found.
+ for i in $merge_tool_candidates
+ do
+ init_merge_tool_path $i
+ if type "$merge_tool_path" > /dev/null 2>&1; then
+ merge_tool=$i
+ break
+ fi
+ done
+
+ if test -z "$merge_tool" ; then
+ echo "No known merge resolution program available."
+ exit 1
+ fi
+
+else
+ # A merge tool has been set, so verify that it's valid.
+ if ! valid_tool "$merge_tool"; then
+ echo >&2 "Unknown merge tool $merge_tool"
+ exit 1
+ fi
+
+ init_merge_tool_path "$merge_tool"
+
+ if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
+ echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
+ exit 1
+ fi
+fi
+
+
+# Launch the merge tool on each path provided by 'git diff'
+while test $# -gt 6
+do
+ launch_merge_tool "$1" "$2" "$5"
+ shift 7
+done
diff --git a/contrib/difftool/git-difftool.txt b/contrib/difftool/git-difftool.txt
--- /dev/null
@@ -0,0 +1,105 @@
+git-difftool(1)
+===============
+
+NAME
+----
+git-difftool - compare changes using common merge tools
+
+SYNOPSIS
+--------
+'git difftool' [--tool=<tool>] [--no-prompt] ['git diff' options]
+
+DESCRIPTION
+-----------
+'git-difftool' is a git command that allows you to compare and edit files
+between revisions using common merge tools. At its most basic level,
+'git-difftool' does what 'git-mergetool' does but its use is for non-merge
+situations such as when preparing commits or comparing changes against
+the index.
+
+'git difftool' is a frontend to 'git diff' and accepts the same
+arguments and options.
+
+See linkgit:git-diff[1] for the full list of supported options.
+
+OPTIONS
+-------
+-t <tool>::
+--tool=<tool>::
+ Use the merge resolution program specified by <tool>.
+ Valid merge tools are:
+ kdiff3, kompare, tkdiff, meld, xxdiff, emerge,
+ vimdiff, gvimdiff, ecmerge, and opendiff
++
+If a merge resolution program is not specified, 'git-difftool'
+will use the configuration variable `merge.tool`. If the
+configuration variable `merge.tool` is not set, 'git difftool'
+will pick a suitable default.
++
+You can explicitly provide a full path to the tool by setting the
+configuration variable `mergetool.<tool>.path`. For example, you
+can configure the absolute path to kdiff3 by setting
+`mergetool.kdiff3.path`. Otherwise, 'git-difftool' assumes the
+tool is available in PATH.
++
+Instead of running one of the known merge tool programs,
+'git-difftool' can be customized to run an alternative program
+by specifying the command line to invoke in a configuration
+variable `mergetool.<tool>.cmd`.
++
+When 'git-difftool' is invoked with this tool (either through the
+`-t` or `--tool` option or the `merge.tool` configuration variable)
+the configured command line will be invoked with the following
+variables available: `$LOCAL` is set to the name of the temporary
+file containing the contents of the diff pre-image and `$REMOTE`
+is set to the name of the temporary file containing the contents
+of the diff post-image. `$BASE` is provided for compatibility
+with custom merge tool commands and has the same value as `$LOCAL`.
+
+--no-prompt::
+ Do not prompt before launching a diff tool.
+
+CONFIG VARIABLES
+----------------
+merge.tool::
+ The default merge tool to use.
++
+See the `--tool=<tool>` option above for more details.
+
+merge.keepBackup::
+ The original, unedited file content can be saved to a file with
+ a `.orig` extension. Defaults to `true` (i.e. keep the backup files).
+
+mergetool.<tool>.path::
+ Override the path for the given tool. This is useful in case
+ your tool is not in the PATH.
+
+mergetool.<tool>.cmd::
+ Specify the command to invoke the specified merge tool.
++
+See the `--tool=<tool>` option above for more details.
+
+
+SEE ALSO
+--------
+linkgit:git-diff[1]::
+ Show changes between commits, commit and working tree, etc
+
+linkgit:git-mergetool[1]::
+ Run merge conflict resolution tools to resolve merge conflicts
+
+linkgit:git-config[1]::
+ Get and set repository or global options
+
+
+AUTHOR
+------
+Written by David Aguilar <davvid@gmail.com>.
+
+Documentation
+--------------
+Documentation by David Aguilar and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
index a48540a92b4aa5140a87469b36c1f9b3d8e46e7f..24d931294180c10646249bbbe31b5215f9996a66 100644 (file)
--- a/contrib/emacs/Makefile
+++ b/contrib/emacs/Makefile
EMACS = emacs
-ELC = git.elc vc-git.elc git-blame.elc
+ELC = git.elc git-blame.elc
INSTALL ?= install
INSTALL_ELC = $(INSTALL) -m 644
prefix ?= $(HOME)
diff --git a/contrib/emacs/README b/contrib/emacs/README
--- /dev/null
+++ b/contrib/emacs/README
@@ -0,0 +1,39 @@
+This directory contains various modules for Emacs support.
+
+To make the modules available to Emacs, you should add this directory
+to your load-path, and then require the modules you want. This can be
+done by adding to your .emacs something like this:
+
+ (add-to-list 'load-path ".../git/contrib/emacs")
+ (require 'git)
+ (require 'git-blame)
+
+
+The following modules are available:
+
+* git.el:
+
+ Status manager that displays the state of all the files of the
+ project, and provides easy access to the most frequently used git
+ commands. The user interface is as far as possible compatible with
+ the pcl-cvs mode. It can be started with `M-x git-status'.
+
+* git-blame.el:
+
+ Emacs implementation of incremental git-blame. When you turn it on
+ while viewing a file, the editor buffer will be updated by setting
+ the background of individual lines to a color that reflects which
+ commit it comes from. And when you move around the buffer, a
+ one-line summary will be shown in the echo area.
+
+* vc-git.el:
+
+ This file used to contain the VC-mode backend for git, but it is no
+ longer distributed with git. It is now maintained as part of Emacs
+ and included in standard Emacs distributions starting from version
+ 22.2.
+
+ If you have an earlier Emacs version, upgrading to Emacs 22 is
+ recommended, since the VC mode in older Emacs is not generic enough
+ to be able to support git in a reasonable manner, and no attempt has
+ been made to backport vc-git.el.
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 09e8bae3a41827a20f6f0693c28f91a98adcb8a4..eace9c18eb1d17075836694ce664a009f3e02038 100644 (file)
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
;;; git.el --- A user interface for git
-;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org>
+;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org>
;; Version: 1.0
;; To start: `M-x git-status'
;;
;; TODO
-;; - portability to XEmacs
;; - diff against other branch
;; - renaming files from the status buffer
;; - creating tags
;; - fetch/pull
-;; - switching branches
;; - revlist browser
;; - git-show-branch browser
-;; - menus
+;;
+
+;;; Compatibility:
+;;
+;; This file works on GNU Emacs 21 or later. It may work on older
+;; versions but this is not guaranteed.
+;;
+;; It may work on XEmacs 21, provided that you first install the ewoc
+;; and log-edit packages.
;;
(eval-when-compile (require 'cl))
(with-current-buffer buffer
(cd dir)
(apply #'call-process-region start end program
- nil (list output-buffer nil) nil args))))
+ nil (list output-buffer t) nil args))))
(defun git-run-command-buffer (buffer-name &rest args)
"Run a git command, sending the output to a buffer named BUFFER-NAME."
(defun git-run-command-region (buffer start end env &rest args)
"Run a git command with specified buffer region as input."
- (unless (eq 0 (if env
- (git-run-process-region
- buffer start end "env"
- (append (git-get-env-strings env) (list "git") args))
+ (with-temp-buffer
+ (if (eq 0 (if env
(git-run-process-region
- buffer start end "git" args)))
- (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string))))
+ buffer start end "env"
+ (append (git-get-env-strings env) (list "git") args))
+ (git-run-process-region buffer start end "git" args)))
+ (buffer-string)
+ (display-message-or-buffer (current-buffer))
+ nil)))
(defun git-run-hook (hook env &rest args)
"Run a git hook and display its output if any."
(unless newval (push "-d" args))
(apply 'git-call-process-display-error "update-ref" args)))
+(defun git-for-each-ref (&rest specs)
+ "Return a list of refs using git-for-each-ref.
+Each entry is a cons of (SHORT-NAME . FULL-NAME)."
+ (let (refs)
+ (with-temp-buffer
+ (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs)
+ (goto-char (point-min))
+ (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t)
+ (push (cons (match-string 1) (match-string 0)) refs)))
+ (nreverse refs)))
+
(defun git-read-tree (tree &optional index-file)
"Read a tree into the index file."
(let ((process-environment
(setq coding-system-for-write buffer-file-coding-system))
(let ((commit
(git-get-string-sha1
- (with-output-to-string
- (with-current-buffer standard-output
- (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
- ("GIT_AUTHOR_EMAIL" . ,author-email)
- ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
- ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
- (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
- (apply #'git-run-command-region
- buffer log-start log-end env
- "commit-tree" tree (nreverse args))))))))
- (and (git-update-ref "HEAD" commit head subject)
- commit))))
+ (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
+ ("GIT_AUTHOR_EMAIL" . ,author-email)
+ ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
+ ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
+ (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
+ (apply #'git-run-command-region
+ buffer log-start log-end env
+ "commit-tree" tree (nreverse args))))))
+ (when commit (git-update-ref "HEAD" commit head subject))
+ commit)))
(defun git-empty-db-p ()
"Check if the git db is empty (no commit done yet)."
(git-fileinfo->needs-refresh info) t)))
(defun git-status-filenames-map (status func files &rest args)
- "Apply FUNC to the status files names in the FILES list."
+ "Apply FUNC to the status files names in the FILES list.
+The list must be sorted."
(when files
- (setq files (sort files #'string-lessp))
(let ((file (pop files))
(node (ewoc-nth status 0)))
(while (and file node)
(setq file (pop files))))))))
(defun git-set-filenames-state (status files state)
- "Set the state of a list of named files."
+ "Set the state of a list of named files. The list must be sorted"
(when files
(git-status-filenames-map status #'git-set-fileinfo-state files state)
(unless state ;; delete files whose state has been set to nil
(let* ((old-type (lsh (or old-perm 0) -9))
(new-type (lsh (or new-perm 0) -9))
(str (case new-type
- (?\100 ;; file
+ (64 ;; file
(case old-type
- (?\100 nil)
- (?\120 " (type change symlink -> file)")
- (?\160 " (type change subproject -> file)")))
- (?\120 ;; symlink
+ (64 nil)
+ (80 " (type change symlink -> file)")
+ (112 " (type change subproject -> file)")))
+ (80 ;; symlink
(case old-type
- (?\100 " (type change file -> symlink)")
- (?\160 " (type change subproject -> symlink)")
+ (64 " (type change file -> symlink)")
+ (112 " (type change subproject -> symlink)")
(t " (symlink)")))
- (?\160 ;; subproject
+ (112 ;; subproject
(case old-type
- (?\100 " (type change file -> subproject)")
- (?\120 " (type change symlink -> subproject)")
+ (64 " (type change file -> subproject)")
+ (80 " (type change symlink -> subproject)")
(t " (subproject)")))
- (?\110 nil) ;; directory (internal, not a real git state)
- (?\000 ;; deleted or unknown
+ (72 nil) ;; directory (internal, not a real git state)
+ (0 ;; deleted or unknown
(case old-type
- (?\120 " (symlink)")
- (?\160 " (subproject)")))
+ (80 " (symlink)")
+ (112 " (subproject)")))
(t (format " (unknown type %o)" new-type)))))
(cond (str (propertize str 'face 'git-status-face))
- ((eq new-type ?\110) "/")
+ ((eq new-type 72) "/")
(t ""))))
(defun git-rename-as-string (info)
(let (unmerged-files)
(while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
(push (match-string 1) unmerged-files))
+ (setq unmerged-files (nreverse unmerged-files)) ;; assume it is sorted already
(git-set-filenames-state status unmerged-files 'unmerged))))
(defun git-get-exclude-files ()
(append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
(defun git-update-status-files (&optional files mark-files)
- "Update the status of FILES from the index."
+ "Update the status of FILES from the index.
+The FILES list must be sorted."
(unless git-status (error "Not in git-status buffer."))
;; set the needs-update flag on existing files
- (if (setq files (sort files #'string-lessp))
+ (if files
(git-status-filenames-map
git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files)
(ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status)
(git-call-process nil "update-index" "--refresh")
(when git-show-uptodate
(git-run-ls-files-cached git-status nil 'uptodate)))
- (let* ((remaining-files
+ (let ((remaining-files
(if (git-empty-db-p) ; we need some special handling for an empty db
(git-run-ls-files-cached git-status files 'added)
(git-run-diff-index git-status files))))
(list (ewoc-data (ewoc-locate git-status)))))
(defun git-marked-files-state (&rest states)
- "Return marked files that are in the specified states."
+ "Return a sorted list of marked files that are in the specified states."
(let ((files (git-marked-files))
result)
(dolist (info files)
(when (memq (git-fileinfo->state info) states)
(push info result)))
- result))
+ (nreverse result)))
(defun git-refresh-files ()
"Refresh all files that need it and clear the needs-refresh flag."
(unless files
(push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
(if (yes-or-no-p
- (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
+ (if (cdr files)
+ (format "Remove %d files? " (length files))
+ (format "Remove %s? " (car files))))
(progn
(dolist (name files)
(ignore-errors
added modified)
(when (and files
(yes-or-no-p
- (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
+ (if (cdr files)
+ (format "Revert %d files? " (length files))
+ (format "Revert %s? " (git-fileinfo->name (car files))))))
(dolist (info files)
(case (git-fileinfo->state info)
('added (push (git-fileinfo->name info) added))
(or (not added)
(apply 'git-call-process-display-error "update-index" "--force-remove" "--" added))
(or (not modified)
- (apply 'git-call-process-display-error "checkout" "HEAD" modified)))))
- (git-update-status-files (append added modified))
+ (apply 'git-call-process-display-error "checkout" "HEAD" modified))))
+ (names (git-get-filenames files)))
+ (git-update-status-files names)
(when ok
(dolist (file modified)
(let ((buffer (get-file-buffer file)))
(when buffer (with-current-buffer buffer (revert-buffer t t t)))))
- (git-success-message "Reverted" (git-get-filenames files)))))))
+ (git-success-message "Reverted" names))))))
(defun git-resolve-file ()
"Resolve conflicts in marked file(s)."
(log-edit-diff-function . git-log-edit-diff)) buffer)
(log-edit 'git-do-commit nil 'git-log-edit-files buffer))
(setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+ (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$"))
(setq buffer-file-coding-system coding-system)
(re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
(mapconcat #'identity msg "\n"))))
(defun git-get-commit-files (commit)
- "Retrieve the list of files modified by COMMIT."
+ "Retrieve a sorted list of files modified by COMMIT."
(let (files)
(with-temp-buffer
(git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit)
(goto-char (point-min))
(while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
(push (match-string 1) files)))
- files))
+ (sort files #'string-lessp)))
+
+(defun git-read-commit-name (prompt &optional default)
+ "Ask for a commit name, with completion for local branch, remote branch and tag."
+ (completing-read prompt
+ (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref)))
+ nil nil nil nil default))
+
+(defun git-checkout (branch &optional merge)
+ "Checkout a branch, tag, or any commit.
+Use a prefix arg if git should merge while checking out."
+ (interactive
+ (list (git-read-commit-name "Checkout: ")
+ current-prefix-arg))
+ (unless git-status (error "Not in git-status buffer."))
+ (let ((args (list branch "--")))
+ (when merge (push "-m" args))
+ (when (apply #'git-call-process-display-error "checkout" args)
+ (git-update-status-files))))
+
+(defun git-branch (branch)
+ "Create a branch from the current HEAD and switch to it."
+ (interactive (list (git-read-commit-name "Branch: ")))
+ (unless git-status (error "Not in git-status buffer."))
+ (if (git-rev-parse (concat "refs/heads/" branch))
+ (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch))
+ (and (git-call-process-display-error "branch" "-f" branch)
+ (git-call-process-display-error "checkout" branch))
+ (message "Canceled."))
+ (git-call-process-display-error "checkout" "-b" branch))
+ (git-refresh-ewoc-hf git-status))
(defun git-amend-commit ()
"Undo the last commit on HEAD, and set things up to commit an
(git-setup-commit-buffer commit)
(git-commit-file))))
+(defun git-cherry-pick-commit (arg)
+ "Cherry-pick a commit."
+ (interactive (list (git-read-commit-name "Cherry-pick commit: ")))
+ (unless git-status (error "Not in git-status buffer."))
+ (let ((commit (git-rev-parse (concat arg "^0"))))
+ (unless commit (error "Not a valid commit '%s'." arg))
+ (when (git-rev-parse (concat commit "^2"))
+ (error "Cannot cherry-pick a merge commit."))
+ (let ((files (git-get-commit-files commit))
+ (ok (git-call-process-display-error "cherry-pick" "-n" commit)))
+ (git-update-status-files files ok)
+ (with-current-buffer (git-setup-commit-buffer commit)
+ (goto-char (point-min))
+ (if (re-search-forward "^\n*Signed-off-by:" nil t 1)
+ (goto-char (match-beginning 0))
+ (goto-char (point-max)))
+ (insert "(cherry picked from commit " commit ")\n"))
+ (when ok (git-commit-file)))))
+
+(defun git-revert-commit (arg)
+ "Revert a commit."
+ (interactive (list (git-read-commit-name "Revert commit: ")))
+ (unless git-status (error "Not in git-status buffer."))
+ (let ((commit (git-rev-parse (concat arg "^0"))))
+ (unless commit (error "Not a valid commit '%s'." arg))
+ (when (git-rev-parse (concat commit "^2"))
+ (error "Cannot revert a merge commit."))
+ (let ((files (git-get-commit-files commit))
+ (subject (git-get-commit-description commit))
+ (ok (git-call-process-display-error "revert" "-n" commit)))
+ (git-update-status-files files ok)
+ (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject)
+ (setq subject (match-string 1 subject)))
+ (git-setup-log-buffer (get-buffer-create "*git-commit*")
+ (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil
+ (format "This reverts commit %s.\n" commit))
+ (when ok (git-commit-file)))))
+
(defun git-find-file ()
"Visit the current file in its own buffer."
(interactive)
(define-key map "\M-\C-?" 'git-unmark-all)
; the commit submap
(define-key commit-map "\C-a" 'git-amend-commit)
+ (define-key commit-map "\C-b" 'git-branch)
+ (define-key commit-map "\C-o" 'git-checkout)
+ (define-key commit-map "\C-p" 'git-cherry-pick-commit)
+ (define-key commit-map "\C-v" 'git-revert-commit)
; the diff submap
(define-key diff-map "b" 'git-diff-file-base)
(define-key diff-map "c" 'git-diff-file-combined)
`("Git"
["Refresh" git-refresh-status t]
["Commit" git-commit-file t]
+ ["Checkout..." git-checkout t]
+ ["New Branch..." git-branch t]
+ ["Cherry-pick Commit..." git-cherry-pick-commit t]
+ ["Revert Commit..." git-revert-commit t]
("Merge"
["Next Unmerged File" git-next-unmerged-file t]
["Prev Unmerged File" git-prev-unmerged-file t]
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
--- a/contrib/emacs/vc-git.el
+++ /dev/null
@@ -1,216 +0,0 @@
-;;; vc-git.el --- VC backend for the git version control system
-
-;; Copyright (C) 2006 Alexandre Julliard
-
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
-;;
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE. See the GNU General Public License for more details.
-;;
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, write to the Free
-;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-;; MA 02111-1307 USA
-
-;;; Commentary:
-
-;; This file contains a VC backend for the git version control
-;; system.
-;;
-;; To install: put this file on the load-path and add GIT to the list
-;; of supported backends in `vc-handled-backends'; the following line,
-;; placed in your ~/.emacs, will accomplish this:
-;;
-;; (add-to-list 'vc-handled-backends 'GIT)
-;;
-;; TODO
-;; - changelog generation
-;; - working with revisions other than HEAD
-;;
-
-(eval-when-compile (require 'cl))
-
-(defvar git-commits-coding-system 'utf-8
- "Default coding system for git commits.")
-
-(defun vc-git--run-command-string (file &rest args)
- "Run a git command on FILE and return its output as string."
- (let* ((ok t)
- (str (with-output-to-string
- (with-current-buffer standard-output
- (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
- (append args (list (file-relative-name file)))))
- (setq ok nil))))))
- (and ok str)))
-
-(defun vc-git--run-command (file &rest args)
- "Run a git command on FILE, discarding any output."
- (let ((name (file-relative-name file)))
- (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
-
-(defun vc-git-registered (file)
- "Check whether FILE is registered with git."
- (with-temp-buffer
- (let* ((dir (file-name-directory file))
- (name (file-relative-name file dir)))
- (and (ignore-errors
- (when dir (cd dir))
- (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
- (let ((str (buffer-string)))
- (and (> (length str) (length name))
- (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
-
-(defun vc-git-state (file)
- "git-specific version of `vc-state'."
- (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
- (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
- 'edited
- 'up-to-date)))
-
-(defun vc-git-workfile-version (file)
- "git-specific version of `vc-workfile-version'."
- (let ((str (with-output-to-string
- (with-current-buffer standard-output
- (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
- (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
- (match-string 2 str)
- str)))
-
-(defun vc-git-symbolic-commit (commit)
- "Translate COMMIT string into symbolic form.
-Returns nil if not possible."
- (and commit
- (with-temp-buffer
- (and
- (zerop
- (call-process "git" nil '(t nil) nil "name-rev"
- "--name-only" "--tags"
- commit))
- (goto-char (point-min))
- (= (forward-line 2) 1)
- (bolp)
- (buffer-substring-no-properties (point-min) (1- (point-max)))))))
-
-(defun vc-git-previous-version (file rev)
- "git-specific version of `vc-previous-version'."
- (let ((default-directory (file-name-directory (expand-file-name file)))
- (file (file-name-nondirectory file)))
- (vc-git-symbolic-commit
- (with-temp-buffer
- (and
- (zerop
- (call-process "git" nil '(t nil) nil "rev-list"
- "-2" rev "--" file))
- (goto-char (point-max))
- (bolp)
- (zerop (forward-line -1))
- (not (bobp))
- (buffer-substring-no-properties
- (point)
- (1- (point-max))))))))
-
-(defun vc-git-next-version (file rev)
- "git-specific version of `vc-next-version'."
- (let* ((default-directory (file-name-directory
- (expand-file-name file)))
- (file (file-name-nondirectory file))
- (current-rev
- (with-temp-buffer
- (and
- (zerop
- (call-process "git" nil '(t nil) nil "rev-list"
- "-1" rev "--" file))
- (goto-char (point-max))
- (bolp)
- (zerop (forward-line -1))
- (bobp)
- (buffer-substring-no-properties
- (point)
- (1- (point-max)))))))
- (and current-rev
- (vc-git-symbolic-commit
- (with-temp-buffer
- (and
- (zerop
- (call-process "git" nil '(t nil) nil "rev-list"
- "HEAD" "--" file))
- (goto-char (point-min))
- (search-forward current-rev nil t)
- (zerop (forward-line -1))
- (buffer-substring-no-properties
- (point)
- (progn (forward-line 1) (1- (point))))))))))
-
-(defun vc-git-revert (file &optional contents-done)
- "Revert FILE to the version stored in the git repository."
- (if contents-done
- (vc-git--run-command file "update-index" "--")
- (vc-git--run-command file "checkout" "HEAD")))
-
-(defun vc-git-checkout-model (file)
- 'implicit)
-
-(defun vc-git-workfile-unchanged-p (file)
- (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
- (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
- (and head
- (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
- (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
-
-(defun vc-git-register (file &optional rev comment)
- "Register FILE into the git version-control system."
- (vc-git--run-command file "update-index" "--add" "--"))
-
-(defun vc-git-print-log (file &optional buffer)
- (let ((name (file-relative-name file))
- (coding-system-for-read git-commits-coding-system))
- (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
-
-(defun vc-git-diff (file &optional rev1 rev2 buffer)
- (let ((name (file-relative-name file))
- (buf (or buffer "*vc-diff*")))
- (if (and rev1 rev2)
- (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
- (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
- ; git-diff-index doesn't set exit status like diff does
- (if (vc-git-workfile-unchanged-p file) 0 1)))
-
-(defun vc-git-checkin (file rev comment)
- (let ((coding-system-for-write git-commits-coding-system))
- (vc-git--run-command file "commit" "-m" comment "--only" "--")))
-
-(defun vc-git-checkout (file &optional editable rev destfile)
- (if destfile
- (let ((fullname (substring
- (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--")
- 0 -1))
- (coding-system-for-read 'no-conversion)
- (coding-system-for-write 'no-conversion))
- (with-temp-file destfile
- (eq 0 (call-process "git" nil t nil "cat-file" "blob"
- (concat (or rev "HEAD") ":" fullname)))))
- (vc-git--run-command file "checkout" (or rev "HEAD"))))
-
-(defun vc-git-annotate-command (file buf &optional rev)
- ; FIXME: rev is ignored
- (let ((name (file-relative-name file)))
- (call-process "git" nil buf nil "blame" name)))
-
-(defun vc-git-annotate-time ()
- (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t)
- (vc-annotate-convert-time
- (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
-
-;; Not really useful since we can't do anything with the revision yet
-;;(defun vc-annotate-extract-revision-at-line ()
-;; (save-excursion
-;; (move-beginning-of-line 1)
-;; (and (looking-at "[0-9a-f]+")
-;; (buffer-substring (match-beginning 0) (match-end 0)))))
-
-(provide 'vc-git)
index a13bb6afec2fe5e0b5249523ec8c62d8e517de88..4576c4a862c8ea0565a67eccdae6ef1ac4d9af9a 100755 (executable)
my $last_branch;
my $current_rev = $opt_s || 1;
unless(-d $git_dir) {
- system("git-init");
+ system("git init");
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system("git-read-tree");
+ system("git read-tree");
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
-f "$git_dir/svn2git"
or die "'$git_dir/svn2git' does not exist.\n".
"You need that file for incremental imports.\n";
- open(F, "git-symbolic-ref HEAD |") or
+ open(F, "git symbolic-ref HEAD |") or
die "Cannot run git-symbolic-ref: $!\n";
chomp ($last_branch = <F>);
$last_branch = basename($last_branch);
"$git_dir/refs/heads/$opt_o") == 0;
# populate index
- system('git-read-tree', $last_rev);
+ system('git', 'read-tree', $last_rev);
die "read-tree failed: $?\n" if $?;
# Get the last import timestamps
my $pid = open(my $F, '-|');
die $! unless defined $pid;
if (!$pid) {
- exec("git-hash-object", "-w", $name)
+ exec("git", "hash-object", "-w", $name)
or die "Cannot create object: $!\n";
}
my $sha = <$F>;
my $pid = open(my $F, '-|');
die $! unless defined $pid;
if (!$pid) {
- exec("git-hash-object", "-w", $name)
+ exec("git", "hash-object", "-w", $name)
or die "Cannot create object: $!\n";
}
my $sha = <$F>;
my $pid = open my $f,'-|';
die $! unless defined $pid;
if (!$pid) {
- exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+ exec("git","ls-tree","-r","-z",$gitrev,$srcpath)
or die $!;
}
local $/ = "\0";
my $rev;
if($revision > $opt_s and defined $parent) {
- open(H,'-|',"git-rev-parse","--verify",$parent);
+ open(H,'-|',"git","rev-parse","--verify",$parent);
$rev = <H>;
close(H) or do {
print STDERR "$revision: cannot find commit '$parent'!\n";
unlink($git_index);
} elsif ($rev ne $last_rev) {
print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
- system("git-read-tree", $rev);
+ system("git", "read-tree", $rev);
die "read-tree failed for $rev: $?\n" if $?;
$last_rev = $rev;
}
my $pid = open my $F, "-|";
die "$!" unless defined $pid;
if (!$pid) {
- exec("git-ls-files", "-z", @o1) or die $!;
+ exec("git", "ls-files", "-z", @o1) or die $!;
}
@o1 = ();
local $/ = "\0";
@o2 = @o1;
@o1 = ();
}
- system("git-update-index","--force-remove","--",@o2);
+ system("git","update-index","--force-remove","--",@o2);
die "Cannot remove files: $?\n" if $?;
}
}
@n2 = @new;
@new = ();
}
- system("git-update-index","--add",
+ system("git","update-index","--add",
(map { ('--cacheinfo', @$_) } @n2));
die "Cannot add files: $?\n" if $?;
}
my $pid = open(C,"-|");
die "Cannot fork: $!" unless defined $pid;
unless($pid) {
- exec("git-write-tree");
+ exec("git","write-tree");
die "Cannot exec git-write-tree: $!\n";
}
chomp(my $tree = <C>);
"GIT_COMMITTER_NAME=$committer_name",
"GIT_COMMITTER_EMAIL=$committer_email",
"GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
- "git-commit-tree", $tree,@par);
+ "git", "commit-tree", $tree,@par);
die "Cannot exec git-commit-tree: $!\n";
}
$pw->writer();
$dest =~ tr/_/\./ if $opt_u;
- system('git-tag', '-f', $dest, $cid) == 0
+ system('git', 'tag', '-f', $dest, $cid) == 0
or die "Cannot create tag $dest: $!\n";
print "Created tag '$dest' on '$branch'\n" if $opt_v;
my $pid = fork();
die "Fork: $!\n" unless defined $pid;
unless($pid) {
- exec("git-repack", "-d")
+ exec("git", "repack", "-d")
or die "Cannot repack: $!\n";
}
waitpid($pid, 0);
system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
if $forward_master;
unless ($opt_i) {
- system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+ system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
die "read-tree failed: $?\n" if $?;
}
} else {
print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
unless -f "$git_dir/refs/heads/master";
- system('git-update-ref', 'HEAD', "$orig_branch");
+ system('git', 'update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
system('git checkout');
die "checkout failed: $?\n" if $?;
index 71aad8b45bd4c5f59c2ce3746cb3299821729c2a..3bb871e42f9c786eb8c40553ea3b0e0eb433b195 100644 (file)
-R <repack_each_revs>::
Specify how often git repository should be repacked.
+
-The default value is 1000. git-svnimport will do import in chunks of 1000
-revisions, after each chunk git repository will be repacked. To disable
-this behavior specify some big value here which is mote than number of
+The default value is 1000. git-svnimport will do imports in chunks of 1000
+revisions, after each chunk the git repository will be repacked. To disable
+this behavior specify some large value here which is greater than the number of
revisions to import.
-P <path_from_trunk>::
index a85a7b2a583ee9270fc2d765ec8c8c6e9d6b5e32..3832f602253fbe793ddf81c61b61e5a2757ce89d 100755 (executable)
output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
for p in depotPaths]))
- changes = []
+ changes = {}
for line in output:
- changeNum = line.split(" ")[1]
- changes.append(int(changeNum))
+ changeNum = int(line.split(" ")[1])
+ changes[changeNum] = True
- changes.sort()
- return changes
+ changelist = changes.keys()
+ changelist.sort()
+ return changelist
class Command:
def __init__(self):
diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh
--- /dev/null
+++ b/contrib/git-resurrect.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>"
+LONG_USAGE="git-resurrect attempts to find traces of a branch tip
+called <name>, and tries to resurrect it. Currently, the reflog is
+searched for checkout messages, and with -r also merge messages. With
+-m and -t, the history of all refs is scanned for Merge <name> into
+other/Merge <other> into <name> (respectively) commit subjects, which
+is rather slow but allows you to resurrect other people's topic
+branches."
+
+OPTIONS_SPEC="\
+git resurrect $USAGE
+--
+b,branch= save branch as <newname> instead of <name>
+a,all same as -l -r -m -t
+k,keep-going full rev-list scan (instead of first match)
+l,reflog scan reflog for checkouts (enabled by default)
+r,reflog-merges scan for merges recorded in reflog
+m,merges scan for merges into other branches (slow)
+t,merge-targets scan for merges of other branches into <name>
+n,dry-run don't recreate the branch"
+
+. git-sh-setup
+
+search_reflog () {
+ sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \
+ < "$GIT_DIR"/logs/HEAD
+}
+
+search_reflog_merges () {
+ git rev-parse $(
+ sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \
+ < "$GIT_DIR"/logs/HEAD
+ )
+}
+
+_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+search_merges () {
+ git rev-list --all --grep="Merge branch '$1'" \
+ --pretty=tformat:"%P %s" |
+ sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}"
+}
+
+search_merge_targets () {
+ git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \
+ --pretty=tformat:"%H %s" --all |
+ sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} "
+}
+
+dry_run=
+early_exit=q
+scan_reflog=t
+scan_reflog_merges=
+scan_merges=
+scan_merge_targets=
+new_name=
+
+while test "$#" != 0; do
+ case "$1" in
+ -b|--branch)
+ shift
+ new_name="$1"
+ ;;
+ -n|--dry-run)
+ dry_run=t
+ ;;
+ --no-dry-run)
+ dry_run=
+ ;;
+ -k|--keep-going)
+ early_exit=
+ ;;
+ --no-keep-going)
+ early_exit=q
+ ;;
+ -m|--merges)
+ scan_merges=t
+ ;;
+ --no-merges)
+ scan_merges=
+ ;;
+ -l|--reflog)
+ scan_reflog=t
+ ;;
+ --no-reflog)
+ scan_reflog=
+ ;;
+ -r|--reflog_merges)
+ scan_reflog_merges=t
+ ;;
+ --no-reflog_merges)
+ scan_reflog_merges=
+ ;;
+ -t|--merge-targets)
+ scan_merge_targets=t
+ ;;
+ --no-merge-targets)
+ scan_merge_targets=
+ ;;
+ -a|--all)
+ scan_reflog=t
+ scan_reflog_merges=t
+ scan_merges=t
+ scan_merge_targets=t
+ ;;
+ --)
+ shift
+ break
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ shift
+done
+
+test "$#" = 1 || usage
+
+all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets"
+if test -z "$all_strategies"; then
+ die "must enable at least one of -lrmt"
+fi
+
+branch="$1"
+test -z "$new_name" && new_name="$branch"
+
+if test ! -z "$scan_reflog"; then
+ if test -r "$GIT_DIR"/logs/HEAD; then
+ candidates="$(search_reflog $branch)"
+ else
+ die 'reflog scanning requested, but' \
+ '$GIT_DIR/logs/HEAD not readable'
+ fi
+fi
+if test ! -z "$scan_reflog_merges"; then
+ if test -r "$GIT_DIR"/logs/HEAD; then
+ candidates="$candidates $(search_reflog_merges $branch)"
+ else
+ die 'reflog scanning requested, but' \
+ '$GIT_DIR/logs/HEAD not readable'
+ fi
+fi
+if test ! -z "$scan_merges"; then
+ candidates="$candidates $(search_merges $branch)"
+fi
+if test ! -z "$scan_merge_targets"; then
+ candidates="$candidates $(search_merge_targets $branch)"
+fi
+
+candidates="$(git rev-parse $candidates | sort -u)"
+
+if test -z "$candidates"; then
+ hint=
+ test "z$all_strategies" != "ztttt" \
+ && hint=" (maybe try again with -a)"
+ die "no candidates for $branch found$hint"
+fi
+
+echo "** Candidates for $branch **"
+for cmt in $candidates; do
+ git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt
+done \
+| sort -n | cut -d: -f2-
+
+newest="$(git rev-list -1 $candidates)"
+if test ! -z "$dry_run"; then
+ printf "** Most recent: "
+ git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+elif ! git rev-parse --verify --quiet $new_name >/dev/null; then
+ printf "** Restoring $new_name to "
+ git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+ git branch $new_name $newest
+else
+ printf "Most recent: "
+ git --no-pager log -1 --pretty=tformat:"%h %s" $newest
+ echo "** $new_name already exists, doing nothing"
+fi
index 28a3c0e46ecf9951f3f42a025a288a65c70e0424..60cbab65d3f8230be3041a13fac2fd9f9b3018d5 100644 (file)
revspec=$oldrev..$newrev
fi
- git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+ other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
+ grep -F -v $refname)
+ git rev-parse --not $other_branches |
if [ -z "$custom_showrev" ]
then
git rev-list --pretty --stdin $revspec
diff --git a/contrib/vim/README b/contrib/vim/README
index c487346eba0f4ecad01903ccc68963040d9944ab..fca1e17251f1f414a1d97bf6e6240bdf20daa648 100644 (file)
--- a/contrib/vim/README
+++ b/contrib/vim/README
If you have an older version of vim, you can get the latest syntax
files from the vim project:
- http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/git.vim
- http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitcommit.vim
- http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitconfig.vim
- http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitrebase.vim
- http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitsendemail.vim
+ http://ftp.vim.org/pub/vim/runtime/syntax/git.vim
+ http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim
+ http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim
+ http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim
+ http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim
+
+These files are also available via FTP at the same location.
To install:
index 9208d674dbc081878532f08d2eddfc66c6a2e327..b90ec004f29c30c4b6a6ea5339599d7a8db0fb8c 100644 (file)
--- a/ctype.c
+++ b/ctype.c
*/
#include "cache.h"
-/* Just so that no insane platform contaminate namespace with these symbols */
-#undef SS
-#undef AA
-#undef DD
-#undef GS
-
-#define SS GIT_SPACE
-#define AA GIT_ALPHA
-#define DD GIT_DIGIT
-#define GS GIT_SPECIAL /* \0, *, ?, [, \\ */
+enum {
+ S = GIT_SPACE,
+ A = GIT_ALPHA,
+ D = GIT_DIGIT,
+ G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
+ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | * */
+};
unsigned char sane_ctype[256] = {
- GS, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-15 */
- SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, GS, 0, 0, 0, 0, 0, /* 32-15 */
- DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, GS, /* 48-15 */
- 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */
- AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, GS, GS, 0, 0, 0, /* 80-15 */
- 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */
- AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */
+ S, 0, 0, 0, R, 0, 0, 0, R, R, G, R, 0, 0, R, 0, /* 32.. 47 */
+ D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, G, /* 48.. 63 */
+ 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
+ A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, 0, /* 80.. 95 */
+ 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
+ A, A, A, A, A, A, A, A, A, A, A, R, R, 0, 0, 0, /* 112..127 */
/* Nothing in the 128.. range */
};
diff --git a/daemon.c b/daemon.c
index 60bf6c743c559676f0c9e0ff8dc6d9a5dfede195..d93cf960f9eaf05eec11b67746142e6e94d719cb 100644 (file)
--- a/daemon.c
+++ b/daemon.c
gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
if (gai)
- die("getaddrinfo() failed: %s\n", gai_strerror(gai));
+ die("getaddrinfo() failed: %s", gai_strerror(gai));
for (ai = ai0; ai; ai = ai->ai_next) {
int sockfd;
gid_t gid = 0;
int i;
+ git_extract_argv0_path(argv[0]);
+
for (i = 1; i < argc; i++) {
char *arg = argv[i];
index 950b88fdcf74f550a582684f1702ffb58c62c7f9..d75dff42405404ec86820a534620c1ba68cd9116 100644 (file)
--- a/date.c
+++ b/date.c
struct tm *tm;
static char timebuf[200];
+ if (mode == DATE_RAW) {
+ snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
+ return timebuf;
+ }
+
if (mode == DATE_RELATIVE) {
unsigned long diff;
struct timeval now;
return DATE_LOCAL;
else if (!strcmp(format, "default"))
return DATE_NORMAL;
+ else if (!strcmp(format, "raw"))
+ return DATE_RAW;
else
die("unknown date format %s", format);
}
diff --git a/diff-lib.c b/diff-lib.c
index ae96c64ca209f4df9008198e8a04b160bed618c7..79d06068344f5a602f6c8799f6671ccbf98cf49c 100644 (file)
--- a/diff-lib.c
+++ b/diff-lib.c
int silent_on_removed = option & DIFF_SILENT_ON_REMOVED;
unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
? CE_MATCH_RACY_IS_DIRTY : 0);
- char symcache[PATH_MAX];
diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
if (diff_unmerged_stage < 0)
diff_unmerged_stage = 2;
entries = active_nr;
- symcache[0] = '\0';
for (i = 0; i < entries; i++) {
struct stat st;
unsigned int oldmode, newmode;
* diff-index
*/
-struct oneway_unpack_data {
- struct rev_info *revs;
- char symcache[PATH_MAX];
-};
-
/* A file entry went away or appeared */
static void diff_index_show_file(struct rev_info *revs,
const char *prefix,
static int get_stat_data(struct cache_entry *ce,
const unsigned char **sha1p,
unsigned int *modep,
- int cached, int match_missing,
- struct oneway_unpack_data *cbdata)
+ int cached, int match_missing)
{
const unsigned char *sha1 = ce->sha1;
unsigned int mode = ce->ce_mode;
return 0;
}
-static void show_new_file(struct oneway_unpack_data *cbdata,
+static void show_new_file(struct rev_info *revs,
struct cache_entry *new,
int cached, int match_missing)
{
const unsigned char *sha1;
unsigned int mode;
- struct rev_info *revs = cbdata->revs;
/*
* New file in the index: it might actually be different in
* the working copy.
*/
- if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0)
+ if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
return;
diff_index_show_file(revs, "+", new, sha1, mode);
}
-static int show_modified(struct oneway_unpack_data *cbdata,
+static int show_modified(struct rev_info *revs,
struct cache_entry *old,
struct cache_entry *new,
int report_missing,
{
unsigned int mode, oldmode;
const unsigned char *sha1;
- struct rev_info *revs = cbdata->revs;
- if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0) {
+ if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
if (report_missing)
diff_index_show_file(revs, "-", old,
old->sha1, old->ce_mode);
struct cache_entry *idx,
struct cache_entry *tree)
{
- struct oneway_unpack_data *cbdata = o->unpack_data;
- struct rev_info *revs = cbdata->revs;
+ struct rev_info *revs = o->unpack_data;
int match_missing, cached;
/*
* Something added to the tree?
*/
if (!tree) {
- show_new_file(cbdata, idx, cached, match_missing);
+ show_new_file(revs, idx, cached, match_missing);
return;
}
}
/* Show difference between old and new */
- show_modified(cbdata, tree, idx, 1, cached, match_missing);
+ show_modified(revs, tree, idx, 1, cached, match_missing);
}
static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
@@ -418,8 +407,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
{
struct cache_entry *idx = src[0];
struct cache_entry *tree = src[1];
- struct oneway_unpack_data *cbdata = o->unpack_data;
- struct rev_info *revs = cbdata->revs;
+ struct rev_info *revs = o->unpack_data;
if (idx && ce_stage(idx))
skip_same_name(idx, o);
const char *tree_name;
struct unpack_trees_options opts;
struct tree_desc t;
- struct oneway_unpack_data unpack_cb;
mark_merge_entries();
if (!tree)
return error("bad tree object %s", tree_name);
- unpack_cb.revs = revs;
- unpack_cb.symcache[0] = '\0';
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.index_only = cached;
opts.merge = 1;
opts.fn = oneway_diff;
- opts.unpack_data = &unpack_cb;
+ opts.unpack_data = revs;
opts.src_index = &the_index;
opts.dst_index = NULL;
struct cache_entry *last = NULL;
struct unpack_trees_options opts;
struct tree_desc t;
- struct oneway_unpack_data unpack_cb;
/*
* This is used by git-blame to run diff-cache internally;
if (!tree)
die("bad tree object %s", sha1_to_hex(tree_sha1));
- unpack_cb.revs = &revs;
- unpack_cb.symcache[0] = '\0';
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.index_only = 1;
opts.merge = 1;
opts.fn = oneway_diff;
- opts.unpack_data = &unpack_cb;
+ opts.unpack_data = &revs;
opts.src_index = &the_index;
opts.dst_index = &the_index;
exit(128);
return 0;
}
+
+int index_differs_from(const char *def, int diff_flags)
+{
+ struct rev_info rev;
+
+ init_revisions(&rev, NULL);
+ setup_revisions(0, NULL, &rev, def);
+ DIFF_OPT_SET(&rev.diffopt, QUIET);
+ DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+ rev.diffopt.flags |= diff_flags;
+ run_diff_index(&rev, 1);
+ if (rev.pending.alloc)
+ free(rev.pending.objects);
+ return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0);
+}
diff --git a/diff-no-index.c b/diff-no-index.c
index 2d541d9aba11ee69525a6a1ef5da6f8a61e9e65e..0a14268ba952da5ff66be753cc0cc147ba64ee2b 100644 (file)
--- a/diff-no-index.c
+++ b/diff-no-index.c
*mode = 0;
else if (!strcmp(path, "-"))
*mode = create_ce_mode(0666);
- else if (stat(path, &st))
+ else if (lstat(path, &st))
return error("Could not access '%s'", path);
else
*mode = st.st_mode;
index 416c5aa722afc8d10e4bc910e7b297b3eb0a2760..006aa017e28dd217d07bb2c48d932e026175f98d 100644 (file)
--- a/diff.c
+++ b/diff.c
#include "run-command.h"
#include "utf8.h"
#include "userdiff.h"
+#include "sigchain.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
static int diff_rename_limit_default = 200;
static int diff_suppress_blank_empty;
int diff_use_color_default = -1;
+static const char *diff_word_regex_cfg;
static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
}
if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value);
+ if (!strcmp(var, "diff.wordregex"))
+ return git_config_string(&diff_word_regex_cfg, var, value);
return git_diff_basic_config(var, value, cb);
}
char tmp_path[PATH_MAX];
} diff_temp[2];
+static struct diff_tempfile *claim_diff_tempfile(void) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
+ if (!diff_temp[i].name)
+ return diff_temp + i;
+ die("BUG: diff is failing to clean up its tempfiles");
+}
+
+static int remove_tempfile_installed;
+
+static void remove_tempfile(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
+ if (diff_temp[i].name == diff_temp[i].tmp_path)
+ unlink(diff_temp[i].name);
+ diff_temp[i].name = NULL;
+ }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+ remove_tempfile();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
static int count_lines(const char *data, int size)
{
int count, ch, completely_empty = 1, nl_just_seen = 0;
struct diff_words_buffer {
mmfile_t text;
long alloc;
- long current; /* output pointer */
- int suppressed_newline;
+ struct diff_words_orig {
+ const char *begin, *end;
+ } *orig;
+ int orig_nr, orig_alloc;
};
static void diff_words_append(char *line, unsigned long len,
struct diff_words_buffer *buffer)
{
- if (buffer->text.size + len > buffer->alloc) {
- buffer->alloc = (buffer->text.size + len) * 3 / 2;
- buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc);
- }
+ ALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);
line++;
len--;
memcpy(buffer->text.ptr + buffer->text.size, line, len);
buffer->text.size += len;
+ buffer->text.ptr[buffer->text.size] = '\0';
}
struct diff_words_data {
struct diff_words_buffer minus, plus;
+ const char *current_plus;
FILE *file;
+ regex_t *word_regex;
};
-static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color,
- int suppress_newline)
+static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
{
- const char *ptr;
- int eol = 0;
+ struct diff_words_data *diff_words = priv;
+ int minus_first, minus_len, plus_first, plus_len;
+ const char *minus_begin, *minus_end, *plus_begin, *plus_end;
- if (len == 0)
+ if (line[0] != '@' || parse_hunk_header(line, len,
+ &minus_first, &minus_len, &plus_first, &plus_len))
return;
- ptr = buffer->text.ptr + buffer->current;
- buffer->current += len;
+ /* POSIX requires that first be decremented by one if len == 0... */
+ if (minus_len) {
+ minus_begin = diff_words->minus.orig[minus_first].begin;
+ minus_end =
+ diff_words->minus.orig[minus_first + minus_len - 1].end;
+ } else
+ minus_begin = minus_end =
+ diff_words->minus.orig[minus_first].end;
- if (ptr[len - 1] == '\n') {
- eol = 1;
- len--;
+ if (plus_len) {
+ plus_begin = diff_words->plus.orig[plus_first].begin;
+ plus_end = diff_words->plus.orig[plus_first + plus_len - 1].end;
+ } else
+ plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
+
+ if (diff_words->current_plus != plus_begin)
+ fwrite(diff_words->current_plus,
+ plus_begin - diff_words->current_plus, 1,
+ diff_words->file);
+ if (minus_begin != minus_end)
+ color_fwrite_lines(diff_words->file,
+ diff_get_color(1, DIFF_FILE_OLD),
+ minus_end - minus_begin, minus_begin);
+ if (plus_begin != plus_end)
+ color_fwrite_lines(diff_words->file,
+ diff_get_color(1, DIFF_FILE_NEW),
+ plus_end - plus_begin, plus_begin);
+
+ diff_words->current_plus = plus_end;
+}
+
+/* This function starts looking at *begin, and returns 0 iff a word was found. */
+static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
+ int *begin, int *end)
+{
+ if (word_regex && *begin < buffer->size) {
+ regmatch_t match[1];
+ if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) {
+ char *p = memchr(buffer->ptr + *begin + match[0].rm_so,
+ '\n', match[0].rm_eo - match[0].rm_so);
+ *end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
+ *begin += match[0].rm_so;
+ return *begin >= *end;
+ }
+ return -1;
}
- fputs(diff_get_color(1, color), file);
- fwrite(ptr, len, 1, file);
- fputs(diff_get_color(1, DIFF_RESET), file);
+ /* find the next word */
+ while (*begin < buffer->size && isspace(buffer->ptr[*begin]))
+ (*begin)++;
+ if (*begin >= buffer->size)
+ return -1;
- if (eol) {
- if (suppress_newline)
- buffer->suppressed_newline = 1;
- else
- putc('\n', file);
- }
+ /* find the end of the word */
+ *end = *begin + 1;
+ while (*end < buffer->size && !isspace(buffer->ptr[*end]))
+ (*end)++;
+
+ return 0;
}
-static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
+/*
+ * This function splits the words in buffer->text, stores the list with
+ * newline separator into out, and saves the offsets of the original words
+ * in buffer->orig.
+ */
+static void diff_words_fill(struct diff_words_buffer *buffer, mmfile_t *out,
+ regex_t *word_regex)
{
- struct diff_words_data *diff_words = priv;
+ int i, j;
+ long alloc = 0;
- if (diff_words->minus.suppressed_newline) {
- if (line[0] != '+')
- putc('\n', diff_words->file);
- diff_words->minus.suppressed_newline = 0;
- }
+ out->size = 0;
+ out->ptr = NULL;
- len--;
- switch (line[0]) {
- case '-':
- print_word(diff_words->file,
- &diff_words->minus, len, DIFF_FILE_OLD, 1);
- break;
- case '+':
- print_word(diff_words->file,
- &diff_words->plus, len, DIFF_FILE_NEW, 0);
- break;
- case ' ':
- print_word(diff_words->file,
- &diff_words->plus, len, DIFF_PLAIN, 0);
- diff_words->minus.current += len;
- break;
+ /* fake an empty "0th" word */
+ ALLOC_GROW(buffer->orig, 1, buffer->orig_alloc);
+ buffer->orig[0].begin = buffer->orig[0].end = buffer->text.ptr;
+ buffer->orig_nr = 1;
+
+ for (i = 0; i < buffer->text.size; i++) {
+ if (find_word_boundaries(&buffer->text, word_regex, &i, &j))
+ return;
+
+ /* store original boundaries */
+ ALLOC_GROW(buffer->orig, buffer->orig_nr + 1,
+ buffer->orig_alloc);
+ buffer->orig[buffer->orig_nr].begin = buffer->text.ptr + i;
+ buffer->orig[buffer->orig_nr].end = buffer->text.ptr + j;
+ buffer->orig_nr++;
+
+ /* store one word */
+ ALLOC_GROW(out->ptr, out->size + j - i + 1, alloc);
+ memcpy(out->ptr + out->size, buffer->text.ptr + i, j - i);
+ out->ptr[out->size + j - i] = '\n';
+ out->size += j - i + 1;
+
+ i = j - 1;
}
}
xdemitconf_t xecfg;
xdemitcb_t ecb;
mmfile_t minus, plus;
- int i;
+
+ /* special case: only removal */
+ if (!diff_words->plus.text.size) {
+ color_fwrite_lines(diff_words->file,
+ diff_get_color(1, DIFF_FILE_OLD),
+ diff_words->minus.text.size, diff_words->minus.text.ptr);
+ diff_words->minus.text.size = 0;
+ return;
+ }
+
+ diff_words->current_plus = diff_words->plus.text.ptr;
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
- minus.size = diff_words->minus.text.size;
- minus.ptr = xmalloc(minus.size);
- memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size);
- for (i = 0; i < minus.size; i++)
- if (isspace(minus.ptr[i]))
- minus.ptr[i] = '\n';
- diff_words->minus.current = 0;
-
- plus.size = diff_words->plus.text.size;
- plus.ptr = xmalloc(plus.size);
- memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size);
- for (i = 0; i < plus.size; i++)
- if (isspace(plus.ptr[i]))
- plus.ptr[i] = '\n';
- diff_words->plus.current = 0;
-
+ diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex);
+ diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex);
xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
+ /* as only the hunk header will be parsed, we need a 0-context */
+ xecfg.ctxlen = 0;
xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
&xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
+ if (diff_words->current_plus != diff_words->plus.text.ptr +
+ diff_words->plus.text.size)
+ fwrite(diff_words->current_plus,
+ diff_words->plus.text.ptr + diff_words->plus.text.size
+ - diff_words->current_plus, 1,
+ diff_words->file);
diff_words->minus.text.size = diff_words->plus.text.size = 0;
-
- if (diff_words->minus.suppressed_newline) {
- putc('\n', diff_words->file);
- diff_words->minus.suppressed_newline = 0;
- }
}
typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
diff_words_show(ecbdata->diff_words);
free (ecbdata->diff_words->minus.text.ptr);
+ free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
+ free (ecbdata->diff_words->plus.orig);
+ free(ecbdata->diff_words->word_regex);
free(ecbdata->diff_words);
ecbdata->diff_words = NULL;
}
@@ -1325,6 +1413,12 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
+static const char *userdiff_word_regex(struct diff_filespec *one)
+{
+ diff_filespec_load_driver(one);
+ return one->driver->word_regex;
+}
+
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{
if (!options->a_prefix)
ecbdata.file = o->file;
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
+ xecfg.interhunkctxlen = o->interhunkcontext;
xecfg.flags = XDL_EMIT_FUNCNAMES;
if (pe)
xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata.diff_words->file = o->file;
+ if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(one);
+ if (!o->word_regex)
+ o->word_regex = userdiff_word_regex(two);
+ if (!o->word_regex)
+ o->word_regex = diff_word_regex_cfg;
+ if (o->word_regex) {
+ ecbdata.diff_words->word_regex = (regex_t *)
+ xmalloc(sizeof(regex_t));
+ if (regcomp(ecbdata.diff_words->word_regex,
+ o->word_regex,
+ REG_EXTENDED | REG_NEWLINE))
+ die ("Invalid regular expression: %s",
+ o->word_regex);
+ }
}
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
&xpp, &xecfg, &ecb);
sprintf(temp->mode, "%06o", mode);
}
-static void prepare_temp_file(const char *name,
- struct diff_tempfile *temp,
- struct diff_filespec *one)
+static struct diff_tempfile *prepare_temp_file(const char *name,
+ struct diff_filespec *one)
{
+ struct diff_tempfile *temp = claim_diff_tempfile();
+
if (!DIFF_FILE_VALID(one)) {
not_a_valid_file:
/* A '-' entry produces this for file-2, and
temp->name = "/dev/null";
strcpy(temp->hex, ".");
strcpy(temp->mode, ".");
- return;
+ return temp;
+ }
+
+ if (!remove_tempfile_installed) {
+ atexit(remove_tempfile);
+ sigchain_push_common(remove_tempfile_on_signal);
+ remove_tempfile_installed = 1;
}
if (!one->sha1_valid ||
*/
sprintf(temp->mode, "%06o", one->mode);
}
- return;
+ return temp;
}
else {
if (diff_populate_filespec(one, 0))
prep_temp_blob(temp, one->data, one->size,
one->sha1, one->mode);
}
-}
-
-static void remove_tempfile(void)
-{
- int i;
-
- for (i = 0; i < 2; i++)
- if (diff_temp[i].name == diff_temp[i].tmp_path) {
- unlink(diff_temp[i].name);
- diff_temp[i].name = NULL;
- }
-}
-
-static void remove_tempfile_on_signal(int signo)
-{
- remove_tempfile();
- signal(SIGINT, SIG_DFL);
- raise(signo);
+ return temp;
}
/* An external diff command takes:
int complete_rewrite)
{
const char *spawn_arg[10];
- struct diff_tempfile *temp = diff_temp;
int retval;
- static int atexit_asked = 0;
- const char *othername;
const char **arg = &spawn_arg[0];
- othername = (other? other : name);
- if (one && two) {
- prepare_temp_file(name, &temp[0], one);
- prepare_temp_file(othername, &temp[1], two);
- if (! atexit_asked &&
- (temp[0].name == temp[0].tmp_path ||
- temp[1].name == temp[1].tmp_path)) {
- atexit_asked = 1;
- atexit(remove_tempfile);
- }
- signal(SIGINT, remove_tempfile_on_signal);
- }
-
if (one && two) {
+ struct diff_tempfile *temp_one, *temp_two;
+ const char *othername = (other ? other : name);
+ temp_one = prepare_temp_file(name, one);
+ temp_two = prepare_temp_file(othername, two);
*arg++ = pgm;
*arg++ = name;
- *arg++ = temp[0].name;
- *arg++ = temp[0].hex;
- *arg++ = temp[0].mode;
- *arg++ = temp[1].name;
- *arg++ = temp[1].hex;
- *arg++ = temp[1].mode;
+ *arg++ = temp_one->name;
+ *arg++ = temp_one->hex;
+ *arg++ = temp_one->mode;
+ *arg++ = temp_two->name;
+ *arg++ = temp_two->hex;
+ *arg++ = temp_two->mode;
if (other) {
*arg++ = other;
*arg++ = xfrm_msg;
if (lstat(one->path, &st) < 0)
die("stat %s", one->path);
if (index_path(one->sha1, one->path, &st, 0))
- die("cannot hash %s\n", one->path);
+ die("cannot hash %s", one->path);
}
}
else
options->break_opt = -1;
options->rename_limit = -1;
options->dirstat_percent = 3;
- DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
options->context = 3;
options->change = diff_change;
options->add_remove = diff_addremove;
if (diff_use_color_default > 0)
DIFF_OPT_SET(options, COLOR_DIFF);
- else
- DIFF_OPT_CLR(options, COLOR_DIFF);
options->detect_rename = diff_detect_rename_default;
if (!diff_mnemonic_prefix) {
options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(arg, "--ignore-space-at-eol"))
options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+ else if (!strcmp(arg, "--patience"))
+ options->xdl_opts |= XDF_PATIENCE_DIFF;
/* flags options */
else if (!strcmp(arg, "--binary")) {
DIFF_OPT_CLR(options, COLOR_DIFF);
else if (!strcmp(arg, "--color-words"))
options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+ else if (!prefixcmp(arg, "--color-words=")) {
+ options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+ options->word_regex = arg + 14;
+ }
else if (!strcmp(arg, "--exit-code"))
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
else if (!strcmp(arg, "--quiet"))
options->b_prefix = arg + 13;
else if (!strcmp(arg, "--no-prefix"))
options->a_prefix = options->b_prefix = "";
+ else if (opt_arg(arg, '\0', "inter-hunk-context",
+ &options->interhunkcontext))
+ ;
else if (!prefixcmp(arg, "--output=")) {
options->file = fopen(arg + strlen("--output="), "w");
options->close_file = 1;
static char *run_textconv(const char *pgm, struct diff_filespec *spec,
size_t *outsize)
{
- struct diff_tempfile temp;
+ struct diff_tempfile *temp;
const char *argv[3];
const char **arg = argv;
struct child_process child;
struct strbuf buf = STRBUF_INIT;
- prepare_temp_file(spec->path, &temp, spec);
+ temp = prepare_temp_file(spec->path, spec);
*arg++ = pgm;
- *arg++ = temp.name;
+ *arg++ = temp->name;
*arg = NULL;
memset(&child, 0, sizeof(child));
if (start_command(&child) != 0 ||
strbuf_read(&buf, child.out, 0) < 0 ||
finish_command(&child) != 0) {
- if (temp.name == temp.tmp_path)
- unlink(temp.name);
+ remove_tempfile();
error("error running textconv command '%s'", pgm);
return NULL;
}
- if (temp.name == temp.tmp_path)
- unlink(temp.name);
+ remove_tempfile();
return strbuf_detach(&buf, outsize);
}
index 42582edee68a4a4717ae5debebf37e6b9610fc8f..6703a4fb4f0302f4adf1065e91cd1bb27e5c973a 100644 (file)
--- a/diff.h
+++ b/diff.h
const char *a_prefix, *b_prefix;
unsigned flags;
int context;
+ int interhunkcontext;
int break_opt;
int detect_rename;
int skip_stat_unmatch;
int stat_width;
int stat_name_width;
+ const char *word_regex;
/* this is set by diffcore for DIFF_FORMAT_PATCH */
int found_changes;
extern void diff_no_index(struct rev_info *, int, const char **, int, const char *);
+extern int index_differs_from(const char *def, int diff_flags);
+
#endif /* DIFF_H */
index 0131983dfbc143ce5dae77e067663bb2e7d5f126..cfd1ea587d9cce825e238ca81ea99b752543dada 100644 (file)
--- a/dir.c
+++ b/dir.c
for (;;) {
unsigned char c1 = *match;
unsigned char c2 = *name;
- if (isspecial(c1))
+ if (c1 == '\0' || is_glob_special(c1))
break;
if (c1 != c2)
return 0;
* and a mark is left in seen[] array for pathspec element that
* actually matched anything.
*/
-int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+int match_pathspec(const char **pathspec, const char *name, int namelen,
+ int prefix, char *seen)
{
- int retval;
- const char *match;
+ int i, retval = 0;
+
+ if (!pathspec)
+ return 1;
name += prefix;
namelen -= prefix;
- for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+ for (i = 0; pathspec[i] != NULL; i++) {
int how;
- if (retval && *seen == MATCHED_EXACTLY)
+ const char *match = pathspec[i] + prefix;
+ if (seen && seen[i] == MATCHED_EXACTLY)
continue;
- match += prefix;
how = match_one(match, name, namelen);
if (how) {
if (retval < how)
retval = how;
- if (*seen < how)
- *seen = how;
+ if (seen && seen[i] < how)
+ seen[i] = how;
}
}
return retval;
@@ -585,10 +588,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
int len, dtype;
int exclude;
- if ((de->d_name[0] == '.') &&
- (de->d_name[1] == 0 ||
- !strcmp(de->d_name + 1, ".") ||
- !strcmp(de->d_name + 1, "git")))
+ if (is_dot_or_dotdot(de->d_name) ||
+ !strcmp(de->d_name, ".git"))
continue;
len = strlen(de->d_name);
/* Ignore overly long pathnames! */
for (;;) {
unsigned char c = *match++;
len++;
- if (isspecial(c))
+ if (c == '\0' || is_glob_special(c))
return len;
}
}
return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
}
+int is_empty_dir(const char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *e;
+ int ret = 1;
+
+ if (!dir)
+ return 0;
+
+ while ((e = readdir(dir)) != NULL)
+ if (!is_dot_or_dotdot(e->d_name)) {
+ ret = 0;
+ break;
+ }
+
+ closedir(dir);
+ return ret;
+}
+
int remove_dir_recursively(struct strbuf *path, int only_empty)
{
DIR *dir = opendir(path->buf);
len = path->len;
while ((e = readdir(dir)) != NULL) {
struct stat st;
- if ((e->d_name[0] == '.') &&
- ((e->d_name[1] == 0) ||
- ((e->d_name[1] == '.') && e->d_name[2] == 0)))
- continue; /* "." and ".." */
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
strbuf_setlen(path, len);
strbuf_addstr(path, e->d_name);
index 768425af0e7095a54edf161fe20400c4caf85b31..bdc2d47447c2ca406aac41d7a8382bf5928fbda8 100644 (file)
--- a/dir.h
+++ b/dir.h
extern char *get_relative_cwd(char *buffer, int size, const char *dir);
extern int is_inside_dir(const char *dir);
+static inline int is_dot_or_dotdot(const char *name)
+{
+ return (name[0] == '.' &&
+ (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0')));
+}
+
+extern int is_empty_dir(const char *dir);
+
extern void setup_standard_excludes(struct dir_struct *dir);
extern int remove_dir_recursively(struct strbuf *path, int only_empty);
index aa2ee46a84033585d8e07a585610c5a697af82c2..05aa58d34823258789ec9e32abc897b8e6777412 100644 (file)
--- a/entry.c
+++ b/entry.c
#include "cache.h"
#include "blob.h"
+#include "dir.h"
static void create_directories(const char *path, const struct checkout *state)
{
const char *slash = path;
while ((slash = strchr(slash+1, '/')) != NULL) {
- struct stat st;
- int stat_status;
-
len = slash - path;
memcpy(buf, path, len);
buf[len] = 0;
- if (len <= state->base_dir_len)
- /*
- * checkout-index --prefix=<dir>; <dir> is
- * allowed to be a symlink to an existing
- * directory.
- */
- stat_status = stat(buf, &st);
- else
- /*
- * if there currently is a symlink, we would
- * want to replace it with a real directory.
- */
- stat_status = lstat(buf, &st);
-
- if (!stat_status && S_ISDIR(st.st_mode))
+ /*
+ * For 'checkout-index --prefix=<dir>', <dir> is
+ * allowed to be a symlink to an existing directory,
+ * and we set 'state->base_dir_len' below, such that
+ * we test the path components of the prefix with the
+ * stat() function instead of the lstat() function.
+ */
+ if (has_dirs_only_path(len, buf, state->base_dir_len))
continue; /* ok, it is already a directory. */
/*
- * We know stat_status == 0 means something exists
- * there and this mkdir would fail, but that is an
- * error codepath; we do not care, as we unlink and
- * mkdir again in such a case.
+ * If this mkdir() would fail, it could be that there
+ * is already a symlink or something else exists
+ * there, therefore we then try to unlink it and try
+ * one more time to create the directory.
*/
if (mkdir(buf, 0777)) {
if (errno == EEXIST && state->force &&
*name++ = '/';
while ((de = readdir(dir)) != NULL) {
struct stat st;
- if ((de->d_name[0] == '.') &&
- ((de->d_name[1] == 0) ||
- ((de->d_name[1] == '.') && de->d_name[2] == 0)))
+ if (is_dot_or_dotdot(de->d_name))
continue;
strcpy(name, de->d_name);
if (lstat(pathbuf, &st))
diff --git a/exec_cmd.c b/exec_cmd.c
index cdd35f91954bdc751455e1083a0612a21eeadc67..217c12577f52b8ff9d535a086ec75d54107ee01c 100644 (file)
--- a/exec_cmd.c
+++ b/exec_cmd.c
const char *system_path(const char *path)
{
- if (!is_absolute_path(path) && argv0_path) {
- struct strbuf d = STRBUF_INIT;
- strbuf_addf(&d, "%s/%s", argv0_path, path);
- path = strbuf_detach(&d, NULL);
+#ifdef RUNTIME_PREFIX
+ static const char *prefix;
+#else
+ static const char *prefix = PREFIX;
+#endif
+ struct strbuf d = STRBUF_INIT;
+
+ if (is_absolute_path(path))
+ return path;
+
+#ifdef RUNTIME_PREFIX
+ assert(argv0_path);
+ assert(is_absolute_path(argv0_path));
+
+ if (!prefix &&
+ !(prefix = strip_path_suffix(argv0_path, GIT_EXEC_PATH)) &&
+ !(prefix = strip_path_suffix(argv0_path, BINDIR)) &&
+ !(prefix = strip_path_suffix(argv0_path, "git"))) {
+ prefix = PREFIX;
+ fprintf(stderr, "RUNTIME_PREFIX requested, "
+ "but prefix computation failed. "
+ "Using static fallback '%s'.\n", prefix);
}
+#endif
+
+ strbuf_addf(&d, "%s/%s", prefix, path);
+ path = strbuf_detach(&d, NULL);
return path;
}
-void git_set_argv0_path(const char *path)
+const char *git_extract_argv0_path(const char *argv0)
{
- argv0_path = path;
+ const char *slash;
+
+ if (!argv0 || !*argv0)
+ return NULL;
+ slash = argv0 + strlen(argv0);
+
+ while (argv0 <= slash && !is_dir_sep(*slash))
+ slash--;
+
+ if (slash >= argv0) {
+ argv0_path = xstrndup(argv0, slash - argv0);
+ return slash + 1;
+ }
+
+ return argv0;
}
void git_set_argv_exec_path(const char *exec_path)
const char *old_path = getenv("PATH");
struct strbuf new_path = STRBUF_INIT;
- add_path(&new_path, argv_exec_path);
- add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT));
- add_path(&new_path, system_path(GIT_EXEC_PATH));
+ add_path(&new_path, git_exec_path());
add_path(&new_path, argv0_path);
if (old_path)
diff --git a/exec_cmd.h b/exec_cmd.h
index 594f961387240c221020c9ea0bccd8a39ff69595..e2b546b615e2806bf7d733099ca0ac7bcfaef823 100644 (file)
--- a/exec_cmd.h
+++ b/exec_cmd.h
#define GIT_EXEC_CMD_H
extern void git_set_argv_exec_path(const char *exec_path);
-extern void git_set_argv0_path(const char *path);
-extern const char* git_exec_path(void);
+extern const char *git_extract_argv0_path(const char *path);
+extern const char *git_exec_path(void);
extern void setup_path(void);
extern const char **prepare_git_cmd(const char **argv);
extern int execv_git_cmd(const char **argv); /* NULL terminated */
diff --git a/fast-import.c b/fast-import.c
index 23e970d581ad00c97ba8ea870c0e43b16258b219..3748ddf48d9bdeea890af805016b69e76493a79d 100644 (file)
--- a/fast-import.c
+++ b/fast-import.c
#include "refs.h"
#include "csum-file.h"
#include "quote.h"
+#include "exec_cmd.h"
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
struct pack_header hdr;
int pack_fd;
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/pack/tmp_pack_XXXXXX", get_object_directory());
- pack_fd = xmkstemp(tmpfile);
+ pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ "pack/tmp_pack_XXXXXX");
p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
strcpy(p->pack_name, tmpfile);
p->pack_fd = pack_fd;
/* Generate the fan-out array. */
c = idx;
for (i = 0; i < 256; i++) {
- struct object_entry **next = c;;
+ struct object_entry **next = c;
while (next < last) {
if ((*next)->sha1[0] != i)
break;
c = next;
}
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/pack/tmp_idx_XXXXXX", get_object_directory());
- idx_fd = xmkstemp(tmpfile);
+ idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ "pack/tmp_idx_XXXXXX");
f = sha1fd(idx_fd, tmpfile);
sha1write(f, array, 256 * sizeof(int));
git_SHA1_Init(&ctx);
chmod(pack_data->pack_name, 0444);
chmod(curr_index_name, 0444);
- snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
- get_object_directory(), sha1_to_hex(pack_data->sha1));
- keep_fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1);
if (keep_fd < 0)
die("cannot create keep file");
write_or_die(keep_fd, keep_msg, strlen(keep_msg));
{
unsigned int i, show_stats = 1;
+ git_extract_argv0_path(argv[0]);
+
setup_git_directory();
git_config(git_pack_config, NULL);
if (!pack_compression_seen && core_compression_seen)
index b0223c3419301132032fb67519a275e57707df22..5f129a42030917bc5dcab67aeaf67a5f00c17677 100755 (executable)
$repo->get_color('color.interactive.header', 'bold'),
$repo->get_color('color.interactive.help', 'red bold'),
) : ();
+my $error_color = ();
+if ($menu_use_color) {
+ my $help_color_spec = ($repo->config('color.interactive.help') or
+ 'red bold');
+ $error_color = $repo->get_color('color.interactive.error',
+ $help_color_spec);
+}
my $diff_use_color = $repo->get_colorbool('color.diff');
my ($fraginfo_color) =
my $normal_color = $repo->get_color("", "reset");
+my $use_readkey = 0;
+sub ReadMode;
+sub ReadKey;
+if ($repo->config_bool("interactive.singlekey")) {
+ eval {
+ require Term::ReadKey;
+ Term::ReadKey->import;
+ $use_readkey = 1;
+ };
+}
+
sub colored {
my $color = shift;
my $string = join("", @_);
return "$prompt_color$prefix$normal_color$remainder";
}
+sub error_msg {
+ print STDERR colored $error_color, @_;
+}
+
sub list_and_choose {
my ($opts, @stuff) = @_;
my (@chosen, @return);
else {
$bottom = $top = find_unique($choice, @stuff);
if (!defined $bottom) {
- print "Huh ($choice)?\n";
+ error_msg "Huh ($choice)?\n";
next TOPLOOP;
}
}
if ($opts->{SINGLETON} && $bottom != $top) {
- print "Huh ($choice)?\n";
+ error_msg "Huh ($choice)?\n";
next TOPLOOP;
}
for ($i = $bottom-1; $i <= $top-1; $i++) {
return close $fh;
}
+sub _restore_terminal_and_die {
+ ReadMode 'restore';
+ print "\n";
+ exit 1;
+}
+
+sub prompt_single_character {
+ if ($use_readkey) {
+ local $SIG{TERM} = \&_restore_terminal_and_die;
+ local $SIG{INT} = \&_restore_terminal_and_die;
+ ReadMode 'cbreak';
+ my $key = ReadKey 0;
+ ReadMode 'restore';
+ print "$key" if defined $key;
+ print "\n";
+ return $key;
+ } else {
+ return <STDIN>;
+ }
+}
+
sub prompt_yesno {
my ($prompt) = @_;
while (1) {
print colored $prompt_color, $prompt;
- my $line = <STDIN>;
+ my $line = prompt_single_character;
return 0 if $line =~ /^n/i;
return 1 if $line =~ /^y/i;
}
n - do not stage this hunk
a - stage this and all the remaining hunks in the file
d - do not stage this hunk nor any of the remaining hunks in the file
+g - select a hunk to go to
+/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
}
}
+# Generate a one line summary of a hunk.
+sub summarize_hunk {
+ my $rhunk = shift;
+ my $summary = $rhunk->{TEXT}[0];
+
+ # Keep the line numbers, discard extra context.
+ $summary =~ s/@@(.*?)@@.*/$1 /s;
+ $summary .= " " x (20 - length $summary);
+
+ # Add some user context.
+ for my $line (@{$rhunk->{TEXT}}) {
+ if ($line =~ m/^[+-].*\w/) {
+ $summary .= $line;
+ last;
+ }
+ }
+
+ chomp $summary;
+ return substr($summary, 0, 80) . "\n";
+}
+
+
+# Print a one-line summary of each hunk in the array ref in
+# the first argument, starting wih the index in the 2nd.
+sub display_hunks {
+ my ($hunks, $i) = @_;
+ my $ctr = 0;
+ $i ||= 0;
+ for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
+ my $status = " ";
+ if (defined $hunks->[$i]{USE}) {
+ $status = $hunks->[$i]{USE} ? "+" : "-";
+ }
+ printf "%s%2d: %s",
+ $status,
+ $i + 1,
+ summarize_hunk($hunks->[$i]);
+ }
+ return $i;
+}
+
sub patch_update_file {
my ($ix, $num);
my $path = shift;
print @{$mode->{DISPLAY}};
print colored $prompt_color,
"Stage mode change [y/n/a/d/?]? ";
- my $line = <STDIN>;
+ my $line = prompt_single_character;
if ($line =~ /^y/i) {
$mode->{USE} = 1;
last;
for ($i = 0; $i < $ix; $i++) {
if (!defined $hunk[$i]{USE}) {
$prev = 1;
- $other .= '/k';
+ $other .= ',k';
last;
}
}
if ($ix) {
- $other .= '/K';
+ $other .= ',K';
}
for ($i = $ix + 1; $i < $num; $i++) {
if (!defined $hunk[$i]{USE}) {
$next = 1;
- $other .= '/j';
+ $other .= ',j';
last;
}
}
if ($ix < $num - 1) {
- $other .= '/J';
+ $other .= ',J';
+ }
+ if ($num > 1) {
+ $other .= ',g';
}
for ($i = 0; $i < $num; $i++) {
if (!defined $hunk[$i]{USE}) {
last if (!$undecided);
if (hunk_splittable($hunk[$ix]{TEXT})) {
- $other .= '/s';
+ $other .= ',s';
}
- $other .= '/e';
+ $other .= ',e';
for (@{$hunk[$ix]{DISPLAY}}) {
print;
}
- print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? ";
- my $line = <STDIN>;
+ print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? ";
+ my $line = prompt_single_character;
if ($line) {
if ($line =~ /^y/i) {
$hunk[$ix]{USE} = 1;
}
next;
}
+ elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
+ my $response = $1;
+ my $no = $ix > 10 ? $ix - 10 : 0;
+ while ($response eq '') {
+ my $extra = "";
+ $no = display_hunks(\@hunk, $no);
+ if ($no < $num) {
+ $extra = " (<ret> to see more)";
+ }
+ print "go to which hunk$extra? ";
+ $response = <STDIN>;
+ if (!defined $response) {
+ $response = '';
+ }
+ chomp $response;
+ }
+ if ($response !~ /^\s*\d+\s*$/) {
+ error_msg "Invalid number: '$response'\n";
+ } elsif (0 < $response && $response <= $num) {
+ $ix = $response - 1;
+ } else {
+ error_msg "Sorry, only $num hunks available.\n";
+ }
+ next;
+ }
elsif ($line =~ /^d/i) {
while ($ix < $num) {
if (!defined $hunk[$ix]{USE}) {
}
next;
}
- elsif ($other =~ /K/ && $line =~ /^K/) {
- $ix--;
- next;
- }
- elsif ($other =~ /J/ && $line =~ /^J/) {
- $ix++;
+ elsif ($line =~ m|^/(.*)|) {
+ my $regex = $1;
+ if ($1 eq "") {
+ print colored $prompt_color, "search for regex? ";
+ $regex = <STDIN>;
+ if (defined $regex) {
+ chomp $regex;
+ }
+ }
+ my $search_string;
+ eval {
+ $search_string = qr{$regex}m;
+ };
+ if ($@) {
+ my ($err,$exp) = ($@, $1);
+ $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
+ error_msg "Malformed search regexp $exp: $err\n";
+ next;
+ }
+ my $iy = $ix;
+ while (1) {
+ my $text = join ("", @{$hunk[$iy]{TEXT}});
+ last if ($text =~ $search_string);
+ $iy++;
+ $iy = 0 if ($iy >= $num);
+ if ($ix == $iy) {
+ error_msg "No hunk matches the given pattern\n";
+ last;
+ }
+ }
+ $ix = $iy;
next;
}
- elsif ($other =~ /k/ && $line =~ /^k/) {
- while (1) {
+ elsif ($line =~ /^K/) {
+ if ($other =~ /K/) {
$ix--;
- last if (!$ix ||
- !defined $hunk[$ix]{USE});
+ }
+ else {
+ error_msg "No previous hunk\n";
}
next;
}
- elsif ($other =~ /j/ && $line =~ /^j/) {
- while (1) {
+ elsif ($line =~ /^J/) {
+ if ($other =~ /J/) {
$ix++;
- last if ($ix >= $num ||
- !defined $hunk[$ix]{USE});
+ }
+ else {
+ error_msg "No next hunk\n";
}
next;
}
+ elsif ($line =~ /^k/) {
+ if ($other =~ /k/) {
+ while (1) {
+ $ix--;
+ last if (!$ix ||
+ !defined $hunk[$ix]{USE});
+ }
+ }
+ else {
+ error_msg "No previous hunk\n";
+ }
+ next;
+ }
+ elsif ($line =~ /^j/) {
+ if ($other !~ /j/) {
+ error_msg "No next hunk\n";
+ next;
+ }
+ }
elsif ($other =~ /s/ && $line =~ /^s/) {
my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
if (1 < @split) {
diff --git a/git-am.sh b/git-am.sh
index 4b157fe5d536fdbdbf85e0e06c419b5927a90867..d3390755fc687a611e89320a7bbfb4ead512c863 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
git am [options] [<mbox>|<Maildir>...]
git am [options] (--resolved | --skip | --abort)
--
-d,dotest= (removed -- do not use)
i,interactive run interactively
-b,binary (historical option -- no-op)
+b,binary* (historical option -- no-op)
3,3way allow fall back on 3way merging if needed
s,signoff add a Signed-off-by line to the commit message
u,utf8 recode into utf8 (default)
k,keep pass -k flag to git-mailinfo
whitespace= pass it through git-apply
+directory= pass it through git-apply
C= pass it through git-apply
p= pass it through git-apply
+reject pass it through git-apply
resolvemsg= override error message when patch failure occurs
r,resolved to be used after a patch failure
skip skip the current patch
abort restore the original branch and abort the patching operation.
-rebasing (internal use for git-rebase)"
+committer-date-is-author-date lie about committer date
+ignore-date use current timestamp for author date
+rebasing* (internal use for git-rebase)"
. git-sh-setup
prefix=$(git rev-parse --show-prefix)
git var GIT_COMMITTER_IDENT >/dev/null ||
die "You need to set your committer info first"
+sq () {
+ for sqarg
+ do
+ printf "%s" "$sqarg" |
+ sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/'
+ done
+}
+
stop_here () {
echo "$1" >"$dotest/next"
exit 1
sign= utf8=t keep= skip= interactive= resolved= rebasing= abort=
resolvemsg= resume=
git_apply_opt=
+committer_date_is_author_date=
+ignore_date=
while test $# != 0
do
;;
--resolvemsg)
shift; resolvemsg=$1 ;;
- --whitespace)
- git_apply_opt="$git_apply_opt $1=$2"; shift ;;
+ --whitespace|--directory)
+ git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
-C|-p)
- git_apply_opt="$git_apply_opt $1$2"; shift ;;
+ git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
+ --reject)
+ git_apply_opt="$git_apply_opt $1" ;;
+ --committer-date-is-author-date)
+ committer_date_is_author_date=t ;;
+ --ignore-date)
+ ignore_date=t ;;
--)
shift; break ;;
*)
# unreliable -- stdin could be /dev/null for example
# and the caller did not intend to feed us a patch but
# wanted to continue unattended.
- tty -s
+ test -t 0
;;
*)
false
resume=yes
case "$skip,$abort" in
+ t,t)
+ die "Please make up your mind. --skip or --abort?"
+ ;;
t,)
git rerere clear
git read-tree --reset -u HEAD HEAD
git update-ref ORIG_HEAD $orig_head
;;
,t)
+ if test -f "$dotest/rebasing"
+ then
+ exec git rebase --abort
+ fi
git rerere clear
- git read-tree --reset -u HEAD ORIG_HEAD
- git reset ORIG_HEAD
+ test -f "$dotest/dirtyindex" || {
+ git read-tree --reset -u HEAD ORIG_HEAD
+ git reset ORIG_HEAD
+ }
rm -fr "$dotest"
exit ;;
esac
+ rm -f "$dotest/dirtyindex"
else
# Make sure we are not given --skip, --resolved, nor --abort
test "$skip$resolved$abort" = "" ||
case "$resolved" in
'')
files=$(git diff-index --cached --name-only HEAD --) || exit
- if [ "$files" ]; then
- echo "Dirty index: cannot apply patches (dirty: $files)" >&2
- exit 1
+ if test "$files"
+ then
+ : >"$dotest/dirtyindex"
+ die "Dirty index: cannot apply patches (dirty: $files)"
fi
esac
case "$resolved" in
'')
- git apply $git_apply_opt --index "$dotest/patch"
+ eval 'git apply '"$git_apply_opt"' --index "$dotest/patch"'
apply_status=$?
;;
t)
fi
if test $apply_status != 0
then
- echo Patch failed at $msgnum.
+ printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE"
stop_here_user_resolve $this
fi
tree=$(git write-tree) &&
parent=$(git rev-parse --verify HEAD) &&
- commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
+ commit=$(
+ if test -n "$ignore_date"
+ then
+ GIT_AUTHOR_DATE=
+ fi
+ if test -n "$committer_date_is_author_date"
+ then
+ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+ export GIT_COMMITTER_DATE
+ fi &&
+ git commit-tree $tree -p $parent <"$dotest/final-commit"
+ ) &&
git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
stop_here $this
diff --git a/git-bisect.sh b/git-bisect.sh
index 85db4ba40022e3a9e5790879d6d21fa59475b316..10ad340920efb7177df53cb3a209d1a3edd5a039 100755 (executable)
--- a/git-bisect.sh
+++ b/git-bisect.sh
_skip="$2"
if [ -z "$_skip" ]; then
- eval "$_eval"
+ eval "$_eval" | {
+ while read line
+ do
+ echo "$line &&"
+ done
+ echo ':'
+ }
return
fi
# Let's parse the output of:
# "git rev-list --bisect-vars --bisect-all ..."
- eval "$_eval" | while read hash line
- do
- case "$VARS,$FOUND,$TRIED,$hash" in
- # We display some vars.
- 1,*,*,*) echo "$hash $line" ;;
-
- # Split line.
- ,*,*,---*) ;;
-
- # We had nothing to search.
+ eval "$_eval" | {
+ VARS= FOUND= TRIED=
+ while read hash line
+ do
+ case "$VARS,$FOUND,$TRIED,$hash" in
+ 1,*,*,*)
+ # "bisect_foo=bar" read from rev-list output.
+ echo "$hash &&"
+ ;;
+ ,*,*,---*)
+ # Separator
+ ;;
,,,bisect_rev*)
- echo "bisect_rev="
+ # We had nothing to search.
+ echo "bisect_rev= &&"
VARS=1
;;
-
- # We did not find a good bisect rev.
- # This should happen only if the "bad"
- # commit is also a "skip" commit.
,,*,bisect_rev*)
- echo "bisect_rev=$TRIED"
+ # We did not find a good bisect rev.
+ # This should happen only if the "bad"
+ # commit is also a "skip" commit.
+ echo "bisect_rev='$TRIED' &&"
VARS=1
;;
-
- # We are searching.
,,*,*)
+ # We are searching.
TRIED="${TRIED:+$TRIED|}$hash"
case "$_skip" in
*$hash*) ;;
*)
- echo "bisect_rev=$hash"
- echo "bisect_tried=\"$TRIED\""
+ echo "bisect_rev=$hash &&"
+ echo "bisect_tried='$TRIED' &&"
FOUND=1
;;
esac
;;
-
- # We have already found a rev to be tested.
- ,1,*,bisect_rev*) VARS=1 ;;
- ,1,*,*) ;;
-
- # ???
- *) die "filter_skipped error " \
- "VARS: '$VARS' " \
- "FOUND: '$FOUND' " \
- "TRIED: '$TRIED' " \
- "hash: '$hash' " \
- "line: '$line'"
- ;;
- esac
- done
+ ,1,*,bisect_rev*)
+ # We have already found a rev to be tested.
+ VARS=1
+ ;;
+ ,1,*,*)
+ ;;
+ *)
+ # Unexpected input
+ echo "die 'filter_skipped error'"
+ die "filter_skipped error " \
+ "VARS: '$VARS' " \
+ "FOUND: '$FOUND' " \
+ "TRIED: '$TRIED' " \
+ "hash: '$hash' " \
+ "line: '$line'"
+ ;;
+ esac
+ done
+ echo ':'
+ }
}
exit_if_skipped_commits () {
diff --git a/git-compat-util.h b/git-compat-util.h
index e20b1e858cc715d1840da11c6198a1d6e6bec2a4..dcf41277502f5fb8fc99b310ebdaf63b6781b3ea 100644 (file)
--- a/git-compat-util.h
+++ b/git-compat-util.h
extern int xdup(int fd);
extern FILE *xfdopen(int fd, const char *mode);
extern int xmkstemp(char *template);
+extern int odb_mkstemp(char *template, size_t limit, const char *pattern);
+extern int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1);
static inline size_t xsize_t(off_t len)
{
#define GIT_SPACE 0x01
#define GIT_DIGIT 0x02
#define GIT_ALPHA 0x04
-#define GIT_SPECIAL 0x08
+#define GIT_GLOB_SPECIAL 0x08
+#define GIT_REGEX_SPECIAL 0x10
#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
#define isspace(x) sane_istest(x,GIT_SPACE)
#define isdigit(x) sane_istest(x,GIT_DIGIT)
#define isalpha(x) sane_istest(x,GIT_ALPHA)
#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
-#define isspecial(x) sane_istest(x,GIT_SPECIAL)
+#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
+#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
#define tolower(x) sane_case((unsigned char)(x), 0x20)
#define toupper(x) sane_case((unsigned char)(x), 0)
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index b0a805c688f59af29e1f25b514d73f3991285dee..ab6cea3e538047c30a5d728c7a16ee3c935c070b 100755 (executable)
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
'history' => \&req_CATCHALL,
'watchers' => \&req_EMPTY,
'editors' => \&req_EMPTY,
+ 'noop' => \&req_EMPTY,
'annotate' => \&req_annotate,
'Global_option' => \&req_Globaloption,
#'annotate' => \&req_CATCHALL,
# write our commit message out if we have one ...
my ( $msg_fh, $msg_filename ) = tempfile( DIR => $TEMP_DIR );
print $msg_fh $state->{opt}{m};# if ( exists ( $state->{opt}{m} ) );
- print $msg_fh "\n\nvia git-CVS emulator\n";
+ if ( defined ( $cfg->{gitcvs}{commitmsgannotation} ) ) {
+ if ($cfg->{gitcvs}{commitmsgannotation} !~ /^\s*$/ ) {
+ print $msg_fh "\n\n".$cfg->{gitcvs}{commitmsgannotation}."\n"
+ }
+ } else {
+ print $msg_fh "\n\nvia git-CVS emulator\n";
+ }
close $msg_fh;
my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`;
close $pipe || die "bad pipe: $! $?";
}
+ $updater->update();
+
### Then hooks/post-update
$hook = $ENV{GIT_DIR}.'hooks/post-update';
if (-x $hook) {
system($hook, "refs/heads/$state->{module}");
}
- $updater->update();
-
# foreach file specified on the command line ...
foreach my $filename ( @committedfiles )
{
return $fh;
}
-# Generate a CVS author name from Git author information, by taking
-# the first eight characters of the user part of the email address.
+# Generate a CVS author name from Git author information, by taking the local
+# part of the email address and replacing characters not in the Portable
+# Filename Character Set (see IEEE Std 1003.1-2001, 3.276) by underscores. CVS
+# Login names are Unix login names, which should be restricted to this
+# character set.
sub cvs_author
{
my $author_line = shift;
- (my $author) = $author_line =~ /<([^>@]{1,8})/;
+ (my $author) = $author_line =~ /<([^@>]*)/;
+
+ $author =~ s/[^-a-zA-Z0-9_.]/_/g;
+ $author =~ s/^-/_/;
$author;
}
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index c106f45af73446d26630030f77107efa520aa296..9a09ba138244b1665de78e94ce12fb8fdb878758 100755 (executable)
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
done;
}
+# if you run 'git_commit_non_empty_tree "$@"' in a commit filter,
+# it will skip commits that leave the tree untouched, commit the other.
+git_commit_non_empty_tree()
+{
+ if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then
+ map "$3"
+ else
+ git commit-tree "$@"
+ fi
+}
# override die(): this version puts in an extra line break, so that
# the progress is still visible
. git-sh-setup
if [ "$(is_bare_repository)" = false ]; then
- git diff-files --quiet &&
+ git diff-files --ignore-submodules --quiet &&
git diff-index --cached --quiet HEAD -- ||
die "Cannot rewrite branch(es) with a dirty working directory."
fi
filter_index=
filter_parent=
filter_msg=cat
-filter_commit='git commit-tree "$@"'
+filter_commit=
filter_tag_name=
filter_subdir=
orig_namespace=refs/original/
force=
+prune_empty=
while :
do
case "$1" in
force=t
continue
;;
+ --prune-empty)
+ shift
+ prune_empty=t
+ continue
+ ;;
-*)
;;
*)
esac
done
+case "$prune_empty,$filter_commit" in
+,)
+ filter_commit='git commit-tree "$@"';;
+t,)
+ filter_commit="$functions;"' git_commit_non_empty_tree "$@"';;
+,*)
+ ;;
+*)
+ die "Cannot set --prune-empty and --filter-commit at the same time"
+esac
+
case "$force" in
t)
rm -rf "$tempdir"
# Remove tempdir on exit
trap 'cd ../..; rm -rf "$tempdir"' 0
+ORIG_GIT_DIR="$GIT_DIR"
+ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
+ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
+GIT_WORK_TREE=.
+export GIT_DIR GIT_WORK_TREE
+
# Make sure refs/original is empty
-git for-each-ref > "$tempdir"/backup-refs
+git for-each-ref > "$tempdir"/backup-refs || exit
while read sha1 type name
do
case "$force,$name" in
esac
done < "$tempdir"/backup-refs
-ORIG_GIT_DIR="$GIT_DIR"
-ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
-ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
-GIT_WORK_TREE=.
-export GIT_DIR GIT_WORK_TREE
-
# The refs should be updated if their heads were rewritten
-git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" |
-sed -e '/^^/d' >"$tempdir"/heads
+git rev-parse --no-flags --revs-only --symbolic-full-name \
+ --default HEAD "$@" > "$tempdir"/raw-heads || exit
+sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
test -s "$tempdir"/heads ||
die "Which ref do you want to rewrite?"
export GIT_INDEX_FILE
git read-tree || die "Could not seed the index"
-ret=0
-
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"
die "tree filter failed: $filter_tree"
(
- git diff-index -r --name-only $commit
+ git diff-index -r --name-only $commit &&
git ls-files --others
- ) |
- git update-index --add --replace --remove --stdin
+ ) > "$tempdir"/tree-state || exit
+ git update-index --add --replace --remove --stdin \
+ < "$tempdir"/tree-state || exit
fi
eval "$filter_index" < /dev/null ||
eval "$filter_msg" > ../message ||
die "msg filter failed: $filter_msg"
@SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
- $(git write-tree) $parentstr < ../message > ../map/$commit
+ $(git write-tree) $parentstr < ../message > ../map/$commit ||
+ die "could not write rewritten commit"
done <../revs
# In case of a subdirectory filter, it is possible that a specified head
die "Could not rewrite $ref"
;;
esac
- git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+ git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
+ exit
done < "$tempdir"/heads
# TODO: This should possibly go, with the semantics that all positive given
trap - 0
+unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
+test -z "$ORIG_GIT_DIR" || {
+ GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
+}
+test -z "$ORIG_GIT_WORK_TREE" || {
+ GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
+ export GIT_WORK_TREE
+}
+test -z "$ORIG_GIT_INDEX_FILE" || {
+ GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
+ export GIT_INDEX_FILE
+}
+
if [ "$(is_bare_repository)" = false ]; then
- unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
- test -z "$ORIG_GIT_DIR" || {
- GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR
- }
- test -z "$ORIG_GIT_WORK_TREE" || {
- GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" &&
- export GIT_WORK_TREE
- }
- test -z "$ORIG_GIT_INDEX_FILE" || {
- GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" &&
- export GIT_INDEX_FILE
- }
- git read-tree -u -m HEAD
+ git read-tree -u -m HEAD || exit
fi
-exit $ret
+exit 0
diff --git a/git-mergetool.sh b/git-mergetool.sh
index d4078a6affd9b4c1fa52e6dba0fe6c151fa452dc..87fa88af5526c8e27b823a65ca15bee4085f8ef2 100755 (executable)
--- a/git-mergetool.sh
+++ b/git-mergetool.sh
# at the discretion of Junio C Hamano.
#
-USAGE='[--tool=tool] [file to merge] ...'
+USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
. git-sh-setup
require_work_tree
-prefix=$(git rev-parse --show-prefix)
# Returns true if the mode reflects a symlink
is_symlink () {
git checkout-index -f --stage=2 -- "$MERGED"
git add -- "$MERGED"
cleanup_temp_files --save-backup
- return
+ return 0
;;
[rR]*)
git checkout-index -f --stage=3 -- "$MERGED"
git add -- "$MERGED"
cleanup_temp_files --save-backup
- return
+ return 0
;;
[aA]*)
- exit 1
+ return 1
;;
esac
done
[mMcC]*)
git add -- "$MERGED"
cleanup_temp_files --save-backup
- return
+ return 0
;;
[dD]*)
git rm -- "$MERGED" > /dev/null
cleanup_temp_files
- return
+ return 0
;;
[aA]*)
- exit 1
+ return 1
;;
esac
done
fi
}
+checkout_staged_file () {
+ tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ')
+
+ if test $? -eq 0 -a -n "$tmpfile" ; then
+ mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3"
+ fi
+}
+
merge_file () {
MERGED="$1"
else
echo "$MERGED: file does not need merging"
fi
- exit 1
+ return 1
fi
ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
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;}'`
- base_present && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null
- local_present && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null
- remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null
+ base_present && checkout_staged_file 1 "$MERGED" "$BASE"
+ local_present && checkout_staged_file 2 "$MERGED" "$LOCAL"
+ remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE"
if test -z "$local_mode" -o -z "$remote_mode"; then
echo "Deleted merge conflict for '$MERGED':"
echo "Normal merge conflict for '$MERGED':"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
- printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
- read ans
+ if "$prompt" = true; then
+ printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
+ read ans
+ fi
case "$merge_tool" in
kdiff3)
fi
status=$?
;;
- meld|vimdiff)
+ meld)
touch "$BACKUP"
"$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
;;
+ vimdiff)
+ touch "$BACKUP"
+ "$merge_tool_path" -c "wincmd l" "$LOCAL" "$MERGED" "$REMOTE"
+ check_unchanged
+ ;;
gvimdiff)
touch "$BACKUP"
- "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"
+ "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
;;
xxdiff)
if test "$status" -ne 0; then
echo "merge of $MERGED failed" 1>&2
mv -- "$BACKUP" "$MERGED"
- exit 1
+
+ if test "$merge_keep_temporaries" = "false"; then
+ cleanup_temp_files
+ fi
+
+ return 1
fi
if test "$merge_keep_backup" = "true"; then
git add -- "$MERGED"
cleanup_temp_files
+ return 0
}
+prompt=$(git config --bool mergetool.prompt || echo true)
+
while test $# != 0
do
case "$1" in
shift ;;
esac
;;
+ -y|--no-prompt)
+ prompt=false
+ ;;
+ --prompt)
+ prompt=true
+ ;;
--)
shift
break
fi
}
+prompt_after_failed_merge() {
+ while true; do
+ printf "Continue merging other unresolved paths (y/n) ? "
+ read ans
+ case "$ans" in
+
+ [yY]*)
+ return 0
+ ;;
+
+ [nN]*)
+ return 1
+ ;;
+ esac
+ done
+}
if test -z "$merge_tool"; then
merge_tool=`git config merge.tool`
if test -z "$merge_tool" ; then
if test -n "$DISPLAY"; then
- merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
- merge_tool_candidates="meld $merge_tool_candidates"
- fi
- if test "$KDE_FULL_SESSION" = "true"; then
- merge_tool_candidates="kdiff3 $merge_tool_candidates"
+ merge_tool_candidates="meld kdiff3 tkdiff xxdiff gvimdiff"
+ else
+ merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
fi
fi
if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
- merge_tool_candidates="$merge_tool_candidates emerge"
- fi
- if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
- merge_tool_candidates="$merge_tool_candidates vimdiff"
+ merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
+ elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
+ merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
+ else
+ merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
fi
- merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
echo "merge tool candidates: $merge_tool_candidates"
for i in $merge_tool_candidates; do
init_merge_tool_path $i
init_merge_tool_path "$merge_tool"
merge_keep_backup="$(git config --bool merge.keepBackup || echo true)"
+ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
fi
fi
+last_status=0
+rollup_status=0
if test $# -eq 0 ; then
- files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u`
- if test -z "$files" ; then
- echo "No files need merging"
- exit 0
+ files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u`
+ if test -z "$files" ; then
+ echo "No files need merging"
+ exit 0
+ fi
+ echo Merging the files: "$files"
+ git ls-files -u |
+ sed -e 's/^[^ ]* //' |
+ sort -u |
+ while IFS= read i
+ do
+ if test $last_status -ne 0; then
+ prompt_after_failed_merge < /dev/tty || exit 1
fi
- echo Merging the files: "$files"
- git ls-files -u |
- sed -e 's/^[^ ]* //' |
- sort -u |
- while IFS= read i
- do
- printf "\n"
- merge_file "$i" < /dev/tty > /dev/tty
- done
+ printf "\n"
+ merge_file "$i" < /dev/tty > /dev/tty
+ last_status=$?
+ if test $last_status -ne 0; then
+ rollup_status=1
+ fi
+ done
else
- while test $# -gt 0; do
- printf "\n"
- merge_file "$1"
- shift
- done
+ while test $# -gt 0; do
+ if test $last_status -ne 0; then
+ prompt_after_failed_merge || exit 1
+ fi
+ printf "\n"
+ merge_file "$1"
+ last_status=$?
+ if test $last_status -ne 0; then
+ rollup_status=1
+ fi
+ shift
+ done
fi
-exit 0
+
+exit $rollup_status
diff --git a/git-pull.sh b/git-pull.sh
index 2c7f432dc04254dcb9906f4d078eb16d195848ca..25adddfddfc0a7e2cc33ea0dc1e1d7a101514972 100755 (executable)
--- a/git-pull.sh
+++ b/git-pull.sh
echo >&2 "Cannot merge multiple branches into empty head"
exit 1
fi
+ if test true = "$rebase"
+ then
+ echo >&2 "Cannot rebase onto multiple branches"
+ exit 1
+ fi
;;
esac
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
index cebaee1cc9dfc28d80173583b144a480be2f9bfd..9a6ba2b9874e12d31adeba354d8d44436ceadaf3 100755 (executable)
--- a/git-quiltimport.sh
+++ b/git-quiltimport.sh
commit=$(git rev-parse HEAD)
mkdir $tmp_dir || exit 2
-while read patch_name level garbage
+while read patch_name level garbage <&3
do
case "$patch_name" in ''|'#'*) continue;; esac
case "$level" in
commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
fi
-done <"$QUILT_PATCHES/series"
+done 3<"$QUILT_PATCHES/series"
rm -rf $tmp_dir || exit 5
index 8ed2244819d0950ddf1ffaa151b4d46bdc8b6db4..3dc659dd5896ce6c887eb786718919aef82f4ac6 100755 (executable)
abort abort rebasing process and restore original branch
skip skip current patch and continue rebasing process
no-verify override pre-rebase hook from stopping the operation
+root rebase all reachable commmits up to the root(s)
"
. git-sh-setup
ONTO=
VERBOSE=
OK_TO_SKIP_PRE_REBASE=
+REBASE_ROOT=
GIT_CHERRY_PICK_HELP=" After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
test -d "$REWRITTEN" &&
pick_one_preserving_merges "$@" && return
+ if test ! -z "$REBASE_ROOT"
+ then
+ output git cherry-pick "$@"
+ return
+ fi
parent_sha1=$(git rev-parse --verify $sha1^) ||
die "Could not get the parent of $sha1"
current_sha1=$(git rev-parse --verify HEAD)
# rewrite parents; if none were rewritten, we can fast-forward.
new_parents=
- pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)"
+ pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
+ if test "$pend" = " "
+ then
+ pend=" root"
+ fi
while [ "$pend" != "" ]
do
p=$(expr "$pend" : ' \([^ ]*\)')
if test -f "$DROPPED"/$p
then
fast_forward=f
- pend=" $(cat "$DROPPED"/$p)$pend"
+ replacement="$(cat "$DROPPED"/$p)"
+ test -z "$replacement" && replacement=root
+ pend=" $replacement$pend"
else
new_parents="$new_parents $p"
fi
pick_one -n $sha1 || failed=t
case "$(peek_next_command)" in
squash|s)
- EDIT_COMMIT=
USE_OUTPUT=output
MSG_OPT=-F
- MSG_FILE="$MSG"
+ EDIT_OR_FILE="$MSG"
cp "$MSG" "$SQUASH_MSG"
;;
*)
- EDIT_COMMIT=-e
USE_OUTPUT=
MSG_OPT=
- MSG_FILE=
+ EDIT_OR_FILE=-e
rm -f "$SQUASH_MSG" || exit
cp "$MSG" "$GIT_DIR"/SQUASH_MSG
rm -f "$GIT_DIR"/MERGE_MSG || exit
GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
- $USE_OUTPUT git commit --no-verify $MSG_OPT "$MSG_FILE" $EDIT_COMMIT || failed=t
+ $USE_OUTPUT git commit --no-verify \
+ $MSG_OPT "$EDIT_OR_FILE" || failed=t
fi
if test $failed = t
then
test -d "$REWRITTEN" && PRESERVE_MERGES=t
test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
test -f "$DOTEST"/verbose && VERBOSE=t
+ test -f "$DOTEST"/rebase-root && REBASE_ROOT=t
}
while test $# != 0
-i)
# yeah, we know
;;
+ --root)
+ REBASE_ROOT=t
+ ;;
--onto)
shift
ONTO=$(git rev-parse --verify "$1") ||
;;
--)
shift
- run_pre_rebase_hook ${1+"$@"}
- test $# -eq 1 -o $# -eq 2 || usage
+ test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 ||
+ test ! -z "$REBASE_ROOT" -a $# -le 1 || usage
test -d "$DOTEST" &&
die "Interactive rebase already started"
git var GIT_COMMITTER_IDENT >/dev/null ||
die "You need to set your committer info first"
+ if test -z "$REBASE_ROOT"
+ then
+ UPSTREAM_ARG="$1"
+ UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+ test -z "$ONTO" && ONTO=$UPSTREAM
+ shift
+ else
+ UPSTREAM=
+ UPSTREAM_ARG=--root
+ test -z "$ONTO" &&
+ die "You must specify --onto when using --root"
+ fi
+ run_pre_rebase_hook "$UPSTREAM_ARG" "$@"
+
comment_for_reflog start
require_clean_work_tree
- UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
- test -z "$ONTO" && ONTO=$UPSTREAM
-
- if test ! -z "$2"
+ if test ! -z "$1"
then
- output git show-ref --verify --quiet "refs/heads/$2" ||
- die "Invalid branchname: $2"
- output git checkout "$2" ||
- die "Could not checkout $2"
+ output git show-ref --verify --quiet "refs/heads/$1" ||
+ die "Invalid branchname: $1"
+ output git checkout "$1" ||
+ die "Could not checkout $1"
fi
HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
echo "detached HEAD" > "$DOTEST"/head-name
echo $HEAD > "$DOTEST"/head
- echo $UPSTREAM > "$DOTEST"/upstream
+ case "$REBASE_ROOT" in
+ '')
+ rm -f "$DOTEST"/rebase-root ;;
+ *)
+ : >"$DOTEST"/rebase-root ;;
+ esac
echo $ONTO > "$DOTEST"/onto
test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
test t = "$VERBOSE" && : > "$DOTEST"/verbose
# This ensures that commits on merged, but otherwise
# unrelated side branches are left alone. (Think "X"
# in the man page's example.)
- mkdir "$REWRITTEN" &&
- for c in $(git merge-base --all $HEAD $UPSTREAM)
- do
- echo $ONTO > "$REWRITTEN"/$c ||
+ if test -z "$REBASE_ROOT"
+ then
+ mkdir "$REWRITTEN" &&
+ for c in $(git merge-base --all $HEAD $UPSTREAM)
+ do
+ echo $ONTO > "$REWRITTEN"/$c ||
+ die "Could not init rewritten commits"
+ done
+ else
+ mkdir "$REWRITTEN" &&
+ echo $ONTO > "$REWRITTEN"/root ||
die "Could not init rewritten commits"
- done
+ fi
# No cherry-pick because our first pass is to determine
# parents to rewrite and skipping dropped commits would
# prematurely end our probe
MERGES_OPTION="--no-merges --cherry-pick"
fi
- SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
SHORTHEAD=$(git rev-parse --short $HEAD)
SHORTONTO=$(git rev-parse --short $ONTO)
+ if test -z "$REBASE_ROOT"
+ # this is now equivalent to ! -z "$UPSTREAM"
+ then
+ SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
+ REVISIONS=$UPSTREAM...$HEAD
+ SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD
+ else
+ REVISIONS=$ONTO...$HEAD
+ SHORTREVISIONS=$SHORTHEAD
+ fi
git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
--abbrev=7 --reverse --left-right --topo-order \
- $UPSTREAM...$HEAD | \
+ $REVISIONS | \
sed -n "s/^>//p" | while read shortsha1 rest
do
if test t != "$PRESERVE_MERGES"
echo "pick $shortsha1 $rest" >> "$TODO"
else
sha1=$(git rev-parse $shortsha1)
- preserve=t
- for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
- do
- if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
- then
- preserve=f
- fi
- done
+ if test -z "$REBASE_ROOT"
+ then
+ preserve=t
+ for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
+ do
+ if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \)
+ then
+ preserve=f
+ fi
+ done
+ else
+ preserve=f
+ fi
if test f = "$preserve"
then
touch "$REWRITTEN"/$sha1
then
mkdir "$DROPPED"
# Save all non-cherry-picked changes
- git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \
+ git rev-list $REVISIONS --left-right --cherry-pick | \
sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks
# Now all commits and note which ones are missing in
# not-cherry-picks and hence being dropped
- git rev-list $UPSTREAM..$HEAD |
+ git rev-list $REVISIONS |
while read rev
do
if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = ""
# not worthwhile, we don't want to track its multiple heads,
# just the history of its first-parent for others that will
# be rebasing on top of it
- git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev
+ git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev
short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev)
grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO"
rm "$REWRITTEN"/$rev
fi
done
fi
+
test -s "$TODO" || echo noop >> "$TODO"
cat >> "$TODO" << EOF
-# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+# Rebase $SHORTREVISIONS onto $SHORTONTO
#
# Commands:
# p, pick = use commit
diff --git a/git-rebase.sh b/git-rebase.sh
index ebd4df3a0e821ddcfd1eabfcaac17f854e172a85..368c0ef4342df990a5617fd6fbe2cd973b1b208b 100755 (executable)
--- a/git-rebase.sh
+++ b/git-rebase.sh
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
prec=4
verbose=
git_am_opt=
+rebase_root=
continue_merge () {
test -n "$prev_head" || die "prev_head must be defined"
-C*)
git_am_opt="$git_am_opt $1"
;;
+ --root)
+ rebase_root=t
+ ;;
-*)
usage
;;
esac
shift
done
+test $# -gt 2 && usage
# Make sure we do not have $GIT_DIR/rebase-apply
if test -z "$do_merge"
;;
esac
-# The upstream head must be given. Make sure it is valid.
-upstream_name="$1"
-upstream=`git rev-parse --verify "${upstream_name}^0"` ||
- die "invalid upstream $upstream_name"
+if test -z "$rebase_root"
+then
+ # The upstream head must be given. Make sure it is valid.
+ upstream_name="$1"
+ shift
+ upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+ die "invalid upstream $upstream_name"
+ unset root_flag
+ upstream_arg="$upstream_name"
+else
+ test -z "$newbase" && die "--root must be used with --onto"
+ unset upstream_name
+ unset upstream
+ root_flag="--root"
+ upstream_arg="$root_flag"
+fi
# Make sure the branch to rebase onto is valid.
onto_name=${newbase-"$upstream_name"}
onto=$(git rev-parse --verify "${onto_name}^0") || exit
# If a hook exists, give it a chance to interrupt
-run_pre_rebase_hook ${1+"$@"}
+run_pre_rebase_hook "$upstream_arg" "$@"
# If the branch to rebase is given, that is the branch we will rebase
# $branch_name -- branch being rebased, or HEAD (already detached)
# $head_name -- refs/heads/<that-branch> or "detached HEAD"
switch_to=
case "$#" in
-2)
+1)
# Is it "rebase other $branchname" or "rebase other $commit"?
- branch_name="$2"
- switch_to="$2"
+ branch_name="$1"
+ switch_to="$1"
- if git show-ref --verify --quiet -- "refs/heads/$2" &&
- branch=$(git rev-parse -q --verify "refs/heads/$2")
+ if git show-ref --verify --quiet -- "refs/heads/$1" &&
+ branch=$(git rev-parse -q --verify "refs/heads/$1")
then
- head_name="refs/heads/$2"
- elif branch=$(git rev-parse -q --verify "$2")
+ head_name="refs/heads/$1"
+ elif branch=$(git rev-parse -q --verify "$1")
then
head_name="detached HEAD"
else
esac
orig_head=$branch
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
# Check if we are already based on $onto with linear history,
# but this should be done only when upstream and onto are the same.
exit 0
fi
+if test -n "$rebase_root"
+then
+ revisions="$onto..$orig_head"
+else
+ revisions="$upstream..$orig_head"
+fi
+
if test -z "$do_merge"
then
git format-patch -k --stdout --full-index --ignore-if-in-upstream \
- "$upstream..$orig_head" |
+ $root_flag "$revisions" |
git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
ret=$?
echo "$head_name" > "$dotest/head-name"
msgnum=0
-for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"`
+for cmt in `git rev-list --reverse --no-merges "$revisions"`
do
msgnum=$(($msgnum + 1))
echo "$cmt" > "$dotest/cmt.$msgnum"
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 2142308bcc6d2e2c4962859d18e12070cd4c1b1d..838233926f7ed07f781f2eb2e7547a2a5a086071 100755 (executable)
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
cdup=$(git rev-parse --show-cdup)
if test ! -z "$cdup"
then
- case "$cdup" in
- /*)
- # Not quite the same as if we did "cd -P '$cdup'" when
- # $cdup contains ".." after symlink path components.
- # Don't fix that case at least until Git switches to
- # "cd -P" across the board.
- phys="$cdup"
- ;;
- ..|../*|*/..|*/../*)
- # Interpret $cdup relative to the physical, not logical, cwd.
- # Probably /bin/pwd is more portable than passing -P to cd or pwd.
- phys="$(unset PWD; /bin/pwd)/$cdup"
- ;;
- *)
- # There's no "..", so no need to make things absolute.
- phys="$cdup"
- ;;
- esac
-
- cd "$phys" || {
- echo >&2 "Cannot chdir to $phys, the toplevel of the working tree"
+ # The "-P" option says to follow "physical" directory
+ # structure instead of following symbolic links. When cdup is
+ # "../", this means following the ".." entry in the current
+ # directory instead textually removing a symlink path element
+ # from the PWD shell variable. The "-P" behavior is more
+ # consistent with the C-style chdir used by most of Git.
+ cd -P "$cdup" || {
+ echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
exit 1
}
fi
diff --git a/git-submodule.sh b/git-submodule.sh
index 2f47e065fe8b7ca856f4527d6a507a28f1b2a06b..204aab671ef78edc24acff4019a2f40a71a59020 100755 (executable)
--- a/git-submodule.sh
+++ b/git-submodule.sh
# Copyright (c) 2007 Lars Hjemli
USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch] <path>]|[status|init|update [-i|--init] [-N|--no-fetch]|summary [-n|--summary-limit <n>] [<commit>]] \
[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
OPTIONS_SPEC=
. git-sh-setup
branch=
quiet=
cached=
+nofetch=
#
# print stuff on stdout unless -q was specified
#
module_list()
{
- git ls-files --stage -- "$@" | grep '^160000 '
+ git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
}
#
shift
cmd_init "$@" || return
;;
+ -N|--no-fetch)
+ shift
+ nofetch=1
+ ;;
--)
shift
break
then
force="-f"
fi
- (unset GIT_DIR; cd "$path" && git-fetch &&
- git-checkout $force -q "$sha1") ||
+
+ if test -z "$nofetch"
+ then
+ (unset GIT_DIR; cd "$path" &&
+ git-fetch) ||
+ die "Unable to fetch in submodule path '$path'"
+ fi
+
+ (unset GIT_DIR; cd "$path" &&
+ git-checkout $force -q "$sha1") ||
die "Unable to checkout '$sha1' in submodule path '$path'"
say "Submodule path '$path': checked out '$sha1'"
diff --git a/git-svn.perl b/git-svn.perl
index ad01e182df8abbeac7efa3db0d84d55ab1563e40..959eb52f3fbe01c5f13eeb169549da98b0b9d64f 100755 (executable)
--- a/git-svn.perl
+++ b/git-svn.perl
$Git::SVN::_follow_parent = 1;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
'config-dir=s' => \$Git::SVN::Ra::config_dir,
- 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
+ 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
+ 'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
'authors-file|A=s' => \$_authors,
'repack:i' => \$Git::SVN::_repack,
\$Git::SVN::_repack_flags,
'use-log-author' => \$Git::SVN::_use_log_author,
'add-author-from' => \$Git::SVN::_add_author_from,
+ 'localtime' => \$Git::SVN::_localtime,
%remote_opts );
my ($_trunk, $_tags, $_branches, $_stdlayout);
die "Unable to determine upstream SVN information from ",
"$head history.\nPerhaps the repository is empty.";
}
- $url = defined $_commit_url ? $_commit_url : $gs->full_url;
+
+ if (defined $_commit_url) {
+ $url = $_commit_url;
+ } else {
+ $url = eval { command_oneline('config', '--get',
+ "svn-remote.$gs->{repo_id}.commiturl") };
+ if (!$url) {
+ $url = $gs->full_url
+ }
+ }
+
my $last_rev = $_revision if defined $_revision;
if ($url) {
print "Committing to $url ...\n";
$gs->prop_walk($gs->{path}, $r, sub {
my ($gs, $path, $props) = @_;
# $path is of the form /path/to/dir/
- my $ignore = '.' . $path . '.gitignore';
+ $path = '.' . $path;
+ # SVN can have attributes on empty directories,
+ # which git won't track
+ mkpath([$path]) unless -d $path;
+ my $ignore = $path . '.gitignore';
my $s = $props->{'svn:ignore'} or return;
open(GITIGNORE, '>', $ignore)
or fatal("Failed to open `$ignore' for writing: $!");
if ($@) {
$result .= "Repository Root: (offline)\n";
}
- $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A";
+ $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
+ ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
$result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
$result .= "Node Kind: " .
use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
$_repack $_repack_flags $_use_svm_props $_head
$_use_svnsync_props $no_reuse_existing $_minimize_url
- $_use_log_author $_add_author_from/;
+ $_use_log_author $_add_author_from $_localtime/;
use Carp qw/croak/;
use File::Path qw/mkpath/;
use File::Copy qw/copy/;
my $prefix = '';
if ($rwr) {
$z = $rwr;
+ remove_username($z);
} elsif (defined $svm) {
$z = $svm->{source};
$prefix = $svm->{replace};
print STDERR "Found possible branch point: ",
"$new_url => ", $self->full_url, ", $r\n";
$branch_from =~ s#^/##;
- my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
- unless ($gs) {
- my $ref_id = $self->{ref_id};
- $ref_id =~ s/\@\d+$//;
- $ref_id .= "\@$r";
- # just grow a tail if we're not unique enough :x
- $ref_id .= '-' while find_ref($ref_id);
- print STDERR "Initializing parent: $ref_id\n";
- my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
- if ($u =~ s#^\Q$url\E(/|$)##) {
- $p = $u;
- $u = $url;
- $repo_id = $self->{repo_id};
- }
- $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
- }
+ my $gs = $self->other_gs($new_url, $url, $repos_root,
+ $branch_from, $r, $self->{ref_id});
my ($r0, $parent) = $gs->find_rev_before($r, 1);
{
my ($base, $head);
# do_switch works with svn/trunk >= r22312, but that
# is not included with SVN 1.4.3 (the latest version
# at the moment), so we can't rely on it
+ $self->{last_rev} = $r0;
$self->{last_commit} = $parent;
- $ed = SVN::Git::Fetcher->new($self);
+ $ed = SVN::Git::Fetcher->new($self, $gs->{path});
$gs->ra->gs_do_switch($r0, $rev, $gs,
$self->full_url, $ed)
or die "SVN connection failed somewhere...\n";
\@out;
}
+# parse_svn_date(DATE)
+# --------------------
+# Given a date (in UTC) from Subversion, return a string in the format
+# "<TZ Offset> <local date/time>" that Git will use.
+#
+# By default the parsed date will be in UTC; if $Git::SVN::_localtime
+# is true we'll convert it to the local timezone instead.
sub parse_svn_date {
my $date = shift || return '+0000 1970-01-01 00:00:00';
my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
- (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or
+ (\d\d)\:(\d\d)\:(\d\d)\.\d*Z$/x) or
croak "Unable to parse date: $date\n";
- "+0000 $Y-$m-$d $H:$M:$S";
+ my $parsed_date; # Set next.
+
+ if ($Git::SVN::_localtime) {
+ # Translate the Subversion datetime to an epoch time.
+ # Begin by switching ourselves to $date's timezone, UTC.
+ my $old_env_TZ = $ENV{TZ};
+ $ENV{TZ} = 'UTC';
+
+ my $epoch_in_UTC =
+ POSIX::strftime('%s', $S, $M, $H, $d, $m - 1, $Y - 1900);
+
+ # Determine our local timezone (including DST) at the
+ # time of $epoch_in_UTC. $Git::SVN::Log::TZ stored the
+ # value of TZ, if any, at the time we were run.
+ if (defined $Git::SVN::Log::TZ) {
+ $ENV{TZ} = $Git::SVN::Log::TZ;
+ } else {
+ delete $ENV{TZ};
+ }
+
+ my $our_TZ =
+ POSIX::strftime('%Z', $S, $M, $H, $d, $m - 1, $Y - 1900);
+
+ # This converts $epoch_in_UTC into our local timezone.
+ my ($sec, $min, $hour, $mday, $mon, $year,
+ $wday, $yday, $isdst) = localtime($epoch_in_UTC);
+
+ $parsed_date = sprintf('%s %04d-%02d-%02d %02d:%02d:%02d',
+ $our_TZ, $year + 1900, $mon + 1,
+ $mday, $hour, $min, $sec);
+
+ # Reset us to the timezone in effect when we entered
+ # this routine.
+ if (defined $old_env_TZ) {
+ $ENV{TZ} = $old_env_TZ;
+ } else {
+ delete $ENV{TZ};
+ }
+ } else {
+ $parsed_date = "+0000 $Y-$m-$d $H:$M:$S";
+ }
+
+ return $parsed_date;
+}
+
+sub other_gs {
+ my ($self, $new_url, $url, $repos_root,
+ $branch_from, $r, $old_ref_id) = @_;
+ my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
+ unless ($gs) {
+ my $ref_id = $old_ref_id;
+ $ref_id =~ s/\@\d+$//;
+ $ref_id .= "\@$r";
+ # just grow a tail if we're not unique enough :x
+ $ref_id .= '-' while find_ref($ref_id);
+ print STDERR "Initializing parent: $ref_id\n";
+ my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
+ if ($u =~ s#^\Q$url\E(/|$)##) {
+ $p = $u;
+ $u = $url;
+ $repo_id = $self->{repo_id};
+ }
+ $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+ }
+ $gs
}
sub check_author {
use Carp qw/croak/;
use File::Temp qw/tempfile/;
use IO::File qw//;
+use vars qw/$_ignore_regex/;
# file baton members: path, mode_a, mode_b, pool, fh, blob, base
sub new {
- my ($class, $git_svn) = @_;
+ my ($class, $git_svn, $switch_path) = @_;
my $self = SVN::Delta::Editor->new;
bless $self, $class;
- $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
+ if (exists $git_svn->{last_commit}) {
+ $self->{c} = $git_svn->{last_commit};
+ $self->{empty_symlinks} =
+ _mark_empty_symlinks($git_svn, $switch_path);
+ }
$self->{empty} = {};
$self->{dir_prop} = {};
$self->{file_prop} = {};
$self;
}
+# this uses the Ra object, so it must be called before do_{switch,update},
+# not inside them (when the Git::SVN::Fetcher object is passed) to
+# do_{switch,update}
+sub _mark_empty_symlinks {
+ my ($git_svn, $switch_path) = @_;
+ my $bool = Git::config_bool('svn.brokenSymlinkWorkaround');
+ return {} if (!defined($bool)) || (defined($bool) && ! $bool);
+
+ my %ret;
+ my ($rev, $cmt) = $git_svn->last_rev_commit;
+ return {} unless ($rev && $cmt);
+
+ # allow the warning to be printed for each revision we fetch to
+ # ensure the user sees it. The user can also disable the workaround
+ # on the repository even while git svn is running and the next
+ # revision fetched will skip this expensive function.
+ my $printed_warning;
+ 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};
+ $pfx .= '/' if length($pfx);
+ while (<$ls>) {
+ chomp;
+ s/\A100644 blob $empty_blob\t//o or next;
+ unless ($printed_warning) {
+ print STDERR "Scanning for empty symlinks, ",
+ "this may take a while if you have ",
+ "many empty files\n",
+ "You may disable this with `",
+ "git config svn.brokenSymlinkWorkaround ",
+ "false'.\n",
+ "This may be done in a different ",
+ "terminal without restarting ",
+ "git svn\n";
+ $printed_warning = 1;
+ }
+ my $path = $_;
+ my (undef, $props) =
+ $git_svn->ra->get_file($pfx.$path, $rev, undef);
+ if ($props->{'svn:special'}) {
+ $ret{$path} = 1;
+ }
+ }
+ command_close_pipe($ls, $ctx);
+ \%ret;
+}
+
+# returns true if a given path is inside a ".git" directory
+sub in_dot_git {
+ $_[0] =~ m{(?:^|/)\.git(?:/|$)};
+}
+
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_path_ignored {
+ my ($path) = @_;
+ return 1 if in_dot_git($path);
+ return 0 unless defined($_ignore_regex);
+ return 1 if $path =~ m!$_ignore_regex!o;
+ return 0;
+}
+
sub set_path_strip {
my ($self, $path) = @_;
$self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
sub delete_entry {
my ($self, $path, $rev, $pb) = @_;
+ return undef if is_path_ignored($path);
my $gpath = $self->git_path($path);
return undef if ($gpath eq '');
sub open_file {
my ($self, $path, $pb, $rev) = @_;
+ my ($mode, $blob);
+
+ goto out if is_path_ignored($path);
+
my $gpath = $self->git_path($path);
- my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
+ ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
=~ /^(\d{6}) blob ([a-f\d]{40})\t/);
unless (defined $mode && defined $blob) {
die "$path was not found in commit $self->{c} (r$rev)\n";
}
+ if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
+ $mode = '120000';
+ }
+out:
{ path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
pool => SVN::Pool->new, action => 'M' };
}
sub add_file {
my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
- my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
- delete $self->{empty}->{$dir};
- { path => $path, mode_a => 100644, mode_b => 100644,
+ my $mode;
+
+ if (!is_path_ignored($path)) {
+ my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+ delete $self->{empty}->{$dir};
+ $mode = '100644';
+ }
+ { path => $path, mode_a => $mode, mode_b => $mode,
pool => SVN::Pool->new, action => 'A' };
}
sub add_directory {
my ($self, $path, $cp_path, $cp_rev) = @_;
+ goto out if is_path_ignored($path);
my $gpath = $self->git_path($path);
if ($gpath eq '') {
my ($ls, $ctx) = command_output_pipe(qw/ls-tree
my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
delete $self->{empty}->{$dir};
$self->{empty}->{$path} = 1;
+out:
{ path => $path };
}
sub change_dir_prop {
my ($self, $db, $prop, $value) = @_;
+ return undef if is_path_ignored($db->{path});
$self->{dir_prop}->{$db->{path}} ||= {};
$self->{dir_prop}->{$db->{path}}->{$prop} = $value;
undef;
sub absent_directory {
my ($self, $path, $pb) = @_;
+ return undef if is_path_ignored($path);
$self->{absent_dir}->{$pb->{path}} ||= [];
push @{$self->{absent_dir}->{$pb->{path}}}, $path;
undef;
sub absent_file {
my ($self, $path, $pb) = @_;
+ return undef if is_path_ignored($path);
$self->{absent_file}->{$pb->{path}} ||= [];
push @{$self->{absent_file}->{$pb->{path}}}, $path;
undef;
sub change_file_prop {
my ($self, $fb, $prop, $value) = @_;
+ return undef if is_path_ignored($fb->{path});
if ($prop eq 'svn:executable') {
if ($fb->{mode_b} != 120000) {
$fb->{mode_b} = defined $value ? 100755 : 100644;
sub apply_textdelta {
my ($self, $fb, $exp) = @_;
+ return undef if is_path_ignored($fb->{path});
my $fh = $::_repository->temp_acquire('svn_delta');
# $fh gets auto-closed() by SVN::TxDelta::apply(),
# (but $base does not,) so dup() it for reading in close_file
open my $dup, '<&', $fh or croak $!;
my $base = $::_repository->temp_acquire('git_blob');
+
if ($fb->{blob}) {
- print $base 'link ' if ($fb->{mode_a} == 120000);
- my $size = $::_repository->cat_blob($fb->{blob}, $base);
+ my ($base_is_link, $size);
+
+ if ($fb->{mode_a} eq '120000' &&
+ ! $self->{empty_symlinks}->{$fb->{path}}) {
+ print $base 'link ' or die "print $!\n";
+ $base_is_link = 1;
+ }
+ retry:
+ $size = $::_repository->cat_blob($fb->{blob}, $base);
die "Failed to read object $fb->{blob}" if ($size < 0);
if (defined $exp) {
seek $base, 0, 0 or croak $!;
my $got = ::md5sum($base);
- die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
- "expected: $exp\n",
- " got: $got\n" if ($got ne $exp);
+ if ($got ne $exp) {
+ my $err = "Checksum mismatch: ".
+ "$fb->{path} $fb->{blob}\n" .
+ "expected: $exp\n" .
+ " got: $got\n";
+ if ($base_is_link) {
+ warn $err,
+ "Retrying... (possibly ",
+ "a bad symlink from SVN)\n";
+ $::_repository->temp_reset($base);
+ $base_is_link = 0;
+ goto retry;
+ }
+ die $err;
+ }
}
}
seek $base, 0, 0 or croak $!;
sub close_file {
my ($self, $fb, $exp) = @_;
+ return undef if is_path_ignored($fb->{path});
+
my $hash;
my $path = $self->git_path($fb->{path});
if (my $fh = $fb->{fh}) {
}
if ($fb->{mode_b} == 120000) {
sysseek($fh, 0, 0) or croak $!;
- sysread($fh, my $buf, 5) == 5 or croak $!;
+ my $rd = sysread($fh, my $buf, 5);
- unless ($buf eq 'link ') {
+ if (!defined $rd) {
+ croak "sysread: $!\n";
+ } elsif ($rd == 0) {
warn "$path has mode 120000",
- " but is not a link\n";
+ " but it points to nothing\n",
+ "converting to an empty file with mode",
+ " 100644\n";
+ $fb->{mode_b} = '100644';
+ } elsif ($buf ne 'link ') {
+ warn "$path has mode 120000",
+ " but is not a link\n";
} else {
my $tmp_fh = $::_repository->temp_acquire(
'svn_hash');
BEGIN {
# enforce temporary pool usage for some simple functions
no strict 'refs';
- for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
+ for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root
+ get_file/) {
my $SUPER = "SUPER::$f";
*$f = sub {
my $self = shift;
# do not call the real DESTROY since we store ourselves in $RA
}
+# get_log(paths, start, end, limit,
+# discover_changed_paths, strict_node_history, receiver)
sub get_log {
my ($self, @args) = @_;
my $pool = SVN::Pool->new;
- splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0');
+
+ # the limit parameter was not supported in SVN 1.1.x, so we
+ # drop it. Therefore, the receiver callback passed to it
+ # is made aware of this limitation by being wrapped if
+ # the limit passed to is being wrapped.
+ if ($SVN::Core::VERSION le '1.2.0') {
+ my $limit = splice(@args, 3, 1);
+ if ($limit > 0) {
+ my $receiver = pop @args;
+ push(@args, sub { &$receiver(@_) if (--$limit >= 0) });
+ }
+ }
my $ret = $self->SUPER::get_log(@args, $pool);
$pool->clear;
$ret;
}
$self->get_log([$longest_path], $min, $max, 0, 1, 1,
sub { $revs{$_[1]} = _cb(@_) });
+ if ($err) {
+ print "Checked through r$max\r";
+ }
if ($err && $max >= $head) {
print STDERR "Path '$longest_path' ",
"was probably deleted:\n",
use strict;
use warnings;
use POSIX qw/strftime/;
+use Time::Local;
use constant commit_log_separator => ('-' x 72) . "\n";
use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
%rusers $show_commit $incremental/;
}
sub format_svn_date {
- return strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", localtime(shift));
+ # some systmes don't handle or mishandle %z, so be creative.
+ my $t = shift || time;
+ my $gm = timelocal(gmtime($t));
+ my $sign = qw( + + - )[ $t <=> $gm ];
+ my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+ return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
}
sub parse_git_date {
diff --git a/git-web--browse.sh b/git-web--browse.sh
index 78d236b77f6e2b52d89bf7b579762723ede86d15..7ed0faddcd75e024ce18d8b80994d41a9207a7ca 100755 (executable)
--- a/git-web--browse.sh
+++ b/git-web--browse.sh
browser_candidates="open $browser_candidates"
fi
# /bin/start indicates MinGW
- if test -n /bin/start; then
+ if test -x /bin/start; then
browser_candidates="start $browser_candidates"
fi
index 940a498962ceb06a1dba85547d61ad62bb81a499..c2b181ed78daa4510f5cfb7bbff5b78f449f872a 100644 (file)
--- a/git.c
+++ b/git.c
#include "exec_cmd.h"
#include "cache.h"
#include "quote.h"
+#include "run-command.h"
const char git_usage_string[] =
"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
if (ret >= 0 && WIFEXITED(ret) &&
WEXITSTATUS(ret) != 127)
exit(WEXITSTATUS(ret));
- die("Failed to run '%s' when expanding alias '%s'\n",
+ die("Failed to run '%s' when expanding alias '%s'",
alias_string + 1, alias_command);
}
count = split_cmdline(alias_string, &new_argv);
int option;
};
-static int run_command(struct cmd_struct *p, int argc, const char **argv)
+static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
{
int status;
struct stat st;
struct cmd_struct *p = commands+i;
if (strcmp(p->cmd, cmd))
continue;
- exit(run_command(p, argc, argv));
+ exit(run_builtin(p, argc, argv));
}
}
{
struct strbuf cmd = STRBUF_INIT;
const char *tmp;
+ int status;
strbuf_addf(&cmd, "git-%s", argv[0]);
trace_argv_printf(argv, "trace: exec:");
- /* execvp() can only ever return if it fails */
- execvp(cmd.buf, (char **)argv);
-
- trace_printf("trace: exec failed: %s\n", strerror(errno));
+ /*
+ * if we fail because the command is not found, it is
+ * OK to return. Otherwise, we just pass along the status code.
+ */
+ status = run_command_v_opt(argv, 0);
+ if (status != -ERR_RUN_COMMAND_EXEC) {
+ if (IS_RUN_COMMAND_ERR(status))
+ die("unable to run '%s'", argv[0]);
+ exit(-status);
+ }
+ errno = ENOENT; /* as if we called execvp */
argv[0] = tmp;
strbuf_release(&cmd);
}
-
-int main(int argc, const char **argv)
+static int run_argv(int *argcp, const char ***argv)
{
- const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help";
- char *slash = (char *)cmd + strlen(cmd);
int done_alias = 0;
- /*
- * Take the basename of argv[0] as the command
- * name, and the dirname as the default exec_path
- * if we don't have anything better.
- */
- do
- --slash;
- while (cmd <= slash && !is_dir_sep(*slash));
- if (cmd <= slash) {
- *slash++ = 0;
- git_set_argv0_path(cmd);
- cmd = slash;
+ while (1) {
+ /* See if it's an internal command */
+ handle_internal_command(*argcp, *argv);
+
+ /* .. then try the external ones */
+ execv_dashed_external(*argv);
+
+ /* It could be an alias -- this works around the insanity
+ * of overriding "git log" with "git show" by having
+ * alias.log = show
+ */
+ if (done_alias || !handle_alias(argcp, argv))
+ break;
+ done_alias = 1;
}
+ return done_alias;
+}
+
+
+int main(int argc, const char **argv)
+{
+ const char *cmd;
+
+ cmd = git_extract_argv0_path(argv[0]);
+ if (!cmd)
+ cmd = "git-help";
+
/*
* "git-xxxx" is the same as "git xxxx", but we obviously:
*
setup_path();
while (1) {
- /* See if it's an internal command */
- handle_internal_command(argc, argv);
-
- /* .. then try the external ones */
- execv_dashed_external(argv);
-
- /* It could be an alias -- this works around the insanity
- * of overriding "git log" with "git show" by having
- * alias.log = show
- */
- if (done_alias || !handle_alias(&argc, &argv))
+ static int done_help = 0;
+ static int was_alias = 0;
+ was_alias = run_argv(&argc, &argv);
+ if (errno != ENOENT)
break;
- done_alias = 1;
- }
-
- if (errno == ENOENT) {
- if (done_alias) {
+ if (was_alias) {
fprintf(stderr, "Expansion of alias '%s' failed; "
"'%s' is not a git-command\n",
cmd, argv[0]);
exit(1);
}
- argv[0] = help_unknown_cmd(cmd);
- handle_internal_command(argc, argv);
- execv_dashed_external(argv);
+ if (!done_help) {
+ cmd = argv[0] = help_unknown_cmd(cmd);
+ done_help = 1;
+ } else
+ break;
}
fprintf(stderr, "Failed to run command '%s': %s\n",
diff --git a/gitweb/README b/gitweb/README
index 19ae28ef9b5de046e003c02b9258dd576d8064f7..8433dd1d45780b3947cc4a0b31ee2059ea6e65bf 100644 (file)
--- a/gitweb/README
+++ b/gitweb/README
$GITWEB_LIST during installation. If empty, $projectroot is used
to scan for repositories.
* $my_url, $my_uri
- URL and absolute URL of gitweb script; you might need to set those
- variables if you are using 'pathinfo' feature: see also below.
+ Full URL and absolute URL of gitweb script;
+ in earlier versions of gitweb you might have need to set those
+ variables, now there should be no need to do it.
* $home_link
Target of the home link on top of all pages (the first part of view
- "breadcrumbs"). By default set to absolute URI of a page; you might
- need to set it up to [base] gitweb URI if you use 'pathinfo' feature
- (alternative format of the URLs, with project name embedded directly
- in the path part of URL).
+ "breadcrumbs"). By default set to absolute URI of a page ($my_uri).
* @stylesheets
List of URIs of stylesheets (relative to base URI of a page). You
might specify more than one stylesheet, for example use gitweb.css
$home_link = "/";
+PATH_INFO usage
+-----------------------
+If you enable PATH_INFO usage in gitweb by putting
+
+ $feature{'pathinfo'}{'default'} = [1];
+
+in your gitweb.conf, it is possible to set up your server so that it
+consumes and produces URLs in the form
+
+http://git.example.com/project.git/shortlog/sometag
+
+by using a configuration such as the following, that assumes that
+/var/www/gitweb is the DocumentRoot of your webserver, and that it
+contains the gitweb.cgi script and complementary static files
+(stylesheet, favicon):
+
+<VirtualHost *:80>
+ ServerAlias git.example.com
+
+ DocumentRoot /var/www/gitweb
+
+ <Directory /var/www/gitweb>
+ Options ExecCGI
+ AddHandler cgi-script cgi
+
+ DirectoryIndex gitweb.cgi
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+ </Directory>
+</VirtualHost>
+
+The rewrite rule guarantees that existing static files will be properly
+served, whereas any other URL will be passed to gitweb as PATH_INFO
+parameter.
+
+Notice that in this case you don't need special settings for
+@stylesheets, $my_uri and $home_link, but you lose "dumb client" access
+to your project .git dirs. A possible workaround for the latter is the
+following: in your project root dir (e.g. /pub/git) have the projects
+named without a .git extension (e.g. /pub/git/project instead of
+/pub/git/project.git) and configure Apache as follows:
+
+<VirtualHost *:80>
+ ServerAlias git.example.com
+
+ DocumentRoot /var/www/gitweb
+
+ AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3
+ <Directory /var/www/gitweb>
+ Options ExecCGI
+ AddHandler cgi-script cgi
+
+ DirectoryIndex gitweb.cgi
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+ </Directory>
+</VirtualHost>
+
+The additional AliasMatch makes it so that
+
+http://git.example.com/project.git
+
+will give raw access to the project's git dir (so that the project can
+be cloned), while
+
+http://git.example.com/project
+
+will provide human-friendly gitweb access.
+
+
Originally written by:
Kay Sievers <kay.sievers@vrfy.org>
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index bdaa4e9463460a149a5c7f13881e5373257bc4e5..33ef190ceb99565f804783ba9fe76987ad6bea89 100755 (executable)
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
our $my_url = $cgi->url();
our $my_uri = $cgi->url(-absolute => 1);
-# if we're called with PATH_INFO, we have to strip that
-# from the URL to find our real URL
-# we make $path_info global because it's also used later on
+# Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+# needed and used only for URLs with nonempty PATH_INFO
+our $base_url = $my_url;
+
+# When the script is used as DirectoryIndex, the URL does not contain the name
+# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+# have to do it ourselves. We make $path_info global because it's also used
+# later on.
+#
+# Another issue with the script being the DirectoryIndex is that the resulting
+# $my_url data is not the full script URL: this is good, because we want
+# generated links to keep implying the script name if it wasn't explicitly
+# indicated in the URL we're handling, but it means that $my_url cannot be used
+# as base URL.
+# Therefore, if we needed to strip PATH_INFO, then we know that we have
+# to build the base URL ourselves:
our $path_info = $ENV{"PATH_INFO"};
if ($path_info) {
- $my_url =~ s,\Q$path_info\E$,,;
- $my_uri =~ s,\Q$path_info\E$,,;
+ if ($my_url =~ s,\Q$path_info\E$,, &&
+ $my_uri =~ s,\Q$path_info\E$,, &&
+ defined $ENV{'SCRIPT_NAME'}) {
+ $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+ }
}
# core git executable to use
# $feature{'blame'}{'override'} = 1;
# and in project config gitweb.blame = 0|1;
'blame' => {
- 'sub' => \&feature_blame,
+ 'sub' => sub { feature_bool('blame', @_) },
'override' => 0,
'default' => [0]},
# $feature{'grep'}{'override'} = 1;
# and in project config gitweb.grep = 0|1;
'grep' => {
- 'sub' => \&feature_grep,
+ 'sub' => sub { feature_bool('grep', @_) },
'override' => 0,
'default' => [1]},
# $feature{'pickaxe'}{'override'} = 1;
# and in project config gitweb.pickaxe = 0|1;
'pickaxe' => {
- 'sub' => \&feature_pickaxe,
+ 'sub' => sub { feature_bool('pickaxe', @_) },
'override' => 0,
'default' => [1]},
'ctags' => {
'override' => 0,
'default' => [0]},
+
+ # The maximum number of patches in a patchset generated in patch
+ # view. Set this to 0 or undef to disable patch view, or to a
+ # negative number to remove any limit.
+
+ # To disable system wide have in $GITWEB_CONFIG
+ # $feature{'patches'}{'default'} = [0];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'patches'}{'override'} = 1;
+ # and in project config gitweb.patches = 0|n;
+ # where n is the maximum number of patches allowed in a patchset.
+ 'patches' => {
+ 'sub' => \&feature_patches,
+ 'override' => 0,
+ 'default' => [16]},
);
sub gitweb_get_feature {
}
-sub feature_blame {
- my ($val) = git_get_project_config('blame', '--bool');
+sub feature_bool {
+ my $key = shift;
+ my ($val) = git_get_project_config($key, '--bool');
- if ($val eq 'true') {
- return 1;
+ if (!defined $val) {
+ return ($_[0]);
+ } elsif ($val eq 'true') {
+ return (1);
} elsif ($val eq 'false') {
- return 0;
+ return (0);
}
-
- return $_[0];
}
sub feature_snapshot {
return @fmts;
}
-sub feature_grep {
- my ($val) = git_get_project_config('grep', '--bool');
-
- if ($val eq 'true') {
- return (1);
- } elsif ($val eq 'false') {
- return (0);
- }
-
- return ($_[0]);
-}
-
-sub feature_pickaxe {
- my ($val) = git_get_project_config('pickaxe', '--bool');
+sub feature_patches {
+ my @val = (git_get_project_config('patches', '--int'));
- if ($val eq 'true') {
- return (1);
- } elsif ($val eq 'false') {
- return (0);
+ if (@val) {
+ return @val;
}
return ($_[0]);
"heads" => \&git_heads,
"history" => \&git_history,
"log" => \&git_log,
+ "patch" => \&git_patch,
+ "patches" => \&git_patches,
"rss" => \&git_rss,
"atom" => \&git_atom,
"search" => \&git_search,
}
my $use_pathinfo = gitweb_check_feature('pathinfo');
- if ($use_pathinfo) {
+ if ($use_pathinfo and defined $params{'project'}) {
# try to put as many parameters as possible in PATH_INFO:
# - project name
# - action
$href =~ s,/$,,;
# Then add the project name, if present
- $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
+ $href .= "/".esc_url($params{'project'});
delete $params{'project'};
# since we destructively absorb parameters, we keep this
my $line = shift;
$line = esc_html($line, -nbsp=>1);
- if ($line =~ m/([0-9a-fA-F]{8,40})/) {
- my $hash_text = $1;
- my $link =
- $cgi->a({-href => href(action=>"object", hash=>$hash_text),
- -class => "text"}, $hash_text);
- $line =~ s/$hash_text/$link/;
- }
+ $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
+ $cgi->a({-href => href(action=>"object", hash=>$1),
+ -class => "text"}, $1);
+ }eg;
+
return $line;
}
return %config;
}
-# convert config value to boolean, 'true' or 'false'
+# convert config value to boolean: 'true' or 'false'
# no value, number > 0, 'true' and 'yes' values are true
# rest of values are treated as false (never as error)
sub config_to_bool {
my $val = shift;
+ return 1 if !defined $val; # section.key
+
# strip leading and trailing whitespace
$val =~ s/^\s+//;
$val =~ s/\s+$//;
- return (!defined $val || # section.key
- ($val =~ /^\d+$/ && $val) || # section.key = 1
+ return (($val =~ /^\d+$/ && $val) || # section.key = 1
($val =~ /^(?:true|yes)$/i)); # section.key = true
}
$config_file = "$git_dir/config";
}
+ # check if config variable (key) exists
+ return unless exists $config{"gitweb.$key"};
+
# ensure given type
if (!defined $type) {
return $config{"gitweb.$key"};
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
EOF
-# print out each stylesheet that exist
+ # the stylesheet, favicon etc urls won't work correctly with path_info
+ # unless we set the appropriate base URL
+ if ($ENV{'PATH_INFO'}) {
+ print "<base href=\"".esc_url($base_url)."\" />\n";
+ }
+ # print out each stylesheet that exist, providing backwards capability
+ # for those people who defined $stylesheet in a config file
if (defined $stylesheet) {
-#provides backwards capability for those people who define style sheet in a config file
print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
} else {
foreach my $stylesheet (@stylesheets) {
}
sub git_blame {
- my $fd;
- my $ftype;
-
+ # permissions
gitweb_check_feature('blame')
- or die_error(403, "Blame view not allowed");
+ or die_error(403, "Blame view not allowed");
+ # error checking
die_error(400, "No file name given") unless $file_name;
$hash_base ||= git_get_head_hash($project);
- die_error(404, "Couldn't find base commit") unless ($hash_base);
+ die_error(404, "Couldn't find base commit") unless $hash_base;
my %co = parse_commit($hash_base)
or die_error(404, "Commit not found");
+ my $ftype = "blob";
if (!defined $hash) {
$hash = git_get_hash_by_path($hash_base, $file_name, "blob")
or die_error(404, "Error looking up file");
+ } else {
+ $ftype = git_get_type($hash);
+ if ($ftype !~ "blob") {
+ die_error(400, "Object is not a blob");
+ }
}
- $ftype = git_get_type($hash);
- if ($ftype !~ "blob") {
- die_error(400, "Object is not a blob");
- }
- open ($fd, "-|", git_cmd(), "blame", '-p', '--',
- $file_name, $hash_base)
+
+ # run git-blame --porcelain
+ open my $fd, "-|", git_cmd(), "blame", '-p',
+ $hash_base, '--', $file_name
or die_error(500, "Open git-blame failed");
+
+ # page header
git_header_html();
my $formats_nav =
$cgi->a({-href => href(action=>"blob", -replay=>1)},
git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
git_print_page_path($file_name, $ftype, $hash_base);
- my @rev_color = (qw(light2 dark2));
+
+ # page body
+ my @rev_color = qw(light2 dark2);
my $num_colors = scalar(@rev_color);
my $current_color = 0;
- my $last_rev;
+ my %metainfo = ();
+
print <<HTML;
<div class="page_body">
<table class="blame">
<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
HTML
- my %metainfo = ();
- while (1) {
- $_ = <$fd>;
- last unless defined $_;
+ LINE:
+ while (my $line = <$fd>) {
+ chomp $line;
+ # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
+ # no <lines in group> for subsequent lines in group of lines
my ($full_rev, $orig_lineno, $lineno, $group_size) =
- /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+ ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
if (!exists $metainfo{$full_rev}) {
$metainfo{$full_rev} = {};
}
my $meta = $metainfo{$full_rev};
- while (<$fd>) {
- last if (s/^\t//);
- if (/^(\S+) (.*)$/) {
+ my $data;
+ while ($data = <$fd>) {
+ chomp $data;
+ last if ($data =~ s/^\t//); # contents of line
+ if ($data =~ /^(\S+) (.*)$/) {
$meta->{$1} = $2;
}
}
- my $data = $_;
- chomp $data;
- my $rev = substr($full_rev, 0, 8);
+ my $short_rev = substr($full_rev, 0, 8);
my $author = $meta->{'author'};
- my %date = parse_date($meta->{'author-time'},
- $meta->{'author-tz'});
+ my %date =
+ parse_date($meta->{'author-time'}, $meta->{'author-tz'});
my $date = $date{'iso-tz'};
if ($group_size) {
- $current_color = ++$current_color % $num_colors;
+ $current_color = ($current_color + 1) % $num_colors;
}
- print "<tr class=\"$rev_color[$current_color]\">\n";
+ print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n";
if ($group_size) {
print "<td class=\"sha1\"";
print " title=\"". esc_html($author) . ", $date\"";
print $cgi->a({-href => href(action=>"commit",
hash=>$full_rev,
file_name=>$file_name)},
- esc_html($rev));
+ esc_html($short_rev));
print "</td>\n";
}
- open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
- or die_error(500, "Open git-rev-parse failed");
- my $parent_commit = <$dd>;
- close $dd;
- chomp($parent_commit);
+ my $parent_commit;
+ if (!exists $meta->{'parent'}) {
+ open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
+ or die_error(500, "Open git-rev-parse failed");
+ $parent_commit = <$dd>;
+ close $dd;
+ chomp($parent_commit);
+ $meta->{'parent'} = $parent_commit;
+ } else {
+ $parent_commit = $meta->{'parent'};
+ }
my $blamed = href(action => 'blame',
file_name => $meta->{'filename'},
hash_base => $parent_commit);
print "<td class=\"linenr\">";
print $cgi->a({ -href => "$blamed#l$orig_lineno",
- -id => "l$lineno",
-class => "linenr" },
esc_html($lineno));
print "</td>";
print "</div>";
close $fd
or print "Reading blob failed\n";
+
+ # page footer
git_footer_html();
}
my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
+ my ($patch_max) = gitweb_get_feature('patches');
+ if ($patch_max) {
+ if ($patch_max < 0 || @commitlist <= $patch_max) {
+ $paging_nav .= " ⋅ " .
+ $cgi->a({-href => href(action=>"patches", -replay=>1)},
+ "patches");
+ }
+ }
+
git_header_html();
git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
} @$parents ) .
')';
}
+ if (gitweb_check_feature('patches')) {
+ $formats_nav .= " | " .
+ $cgi->a({-href => href(action=>"patch", -replay=>1)},
+ "patch");
+ }
if (!defined $parent) {
$parent = "--root";
}
sub git_commitdiff {
- my $format = shift || 'html';
+ my %params = @_;
+ my $format = $params{-format} || 'html';
+
+ my ($patch_max) = gitweb_get_feature('patches');
+ if ($format eq 'patch') {
+ die_error(403, "Patch view not allowed") unless $patch_max;
+ }
+
$hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash)
or die_error(404, "Unknown commit object");
$formats_nav =
$cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
"raw");
+ if ($patch_max) {
+ $formats_nav .= " | " .
+ $cgi->a({-href => href(action=>"patch", -replay=>1)},
+ "patch");
+ }
if (defined $hash_parent &&
$hash_parent ne '-c' && $hash_parent ne '--cc') {
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
'-p', $hash_parent_param, $hash, "--"
or die_error(500, "Open git-diff-tree failed");
-
+ } elsif ($format eq 'patch') {
+ # For commit ranges, we limit the output to the number of
+ # patches specified in the 'patches' feature.
+ # For single commits, we limit the output to a single patch,
+ # diverging from the git-format-patch default.
+ my @commit_spec = ();
+ if ($hash_parent) {
+ if ($patch_max > 0) {
+ push @commit_spec, "-$patch_max";
+ }
+ push @commit_spec, '-n', "$hash_parent..$hash";
+ } else {
+ if ($params{-single}) {
+ push @commit_spec, '-1';
+ } else {
+ if ($patch_max > 0) {
+ push @commit_spec, "-$patch_max";
+ }
+ push @commit_spec, "-n";
+ }
+ push @commit_spec, '--root', $hash;
+ }
+ open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
+ '--stdout', @commit_spec
+ or die_error(500, "Open git-format-patch failed");
} else {
die_error(400, "Unknown commitdiff format");
}
print to_utf8($line) . "\n";
}
print "---\n\n";
+ } elsif ($format eq 'patch') {
+ my $filename = basename($project) . "-$hash.patch";
+
+ print $cgi->header(
+ -type => 'text/plain',
+ -charset => 'utf-8',
+ -expires => $expires,
+ -content_disposition => 'inline; filename="' . "$filename" . '"');
}
# write patch
print <$fd>;
close $fd
or print "Reading git-diff-tree failed\n";
+ } elsif ($format eq 'patch') {
+ local $/ = undef;
+ print <$fd>;
+ close $fd
+ or print "Reading git-format-patch failed\n";
}
}
sub git_commitdiff_plain {
- git_commitdiff('plain');
+ git_commitdiff(-format => 'plain');
+}
+
+# format-patch-style patches
+sub git_patch {
+ git_commitdiff(-format => 'patch', -single=> 1);
+}
+
+sub git_patches {
+ git_commitdiff(-format => 'patch');
}
sub git_history {
$cgi->a({-href => href(-replay=>1, page=>$page+1),
-accesskey => "n", -title => "Alt-n"}, "next");
}
+ my $patch_max = gitweb_check_feature('patches');
+ if ($patch_max) {
+ if ($patch_max < 0 || @commitlist <= $patch_max) {
+ $paging_nav .= " ⋅ " .
+ $cgi->a({-href => href(action=>"patches", -replay=>1)},
+ "patches");
+ }
+ }
git_header_html();
git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
}
if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]};
- %latest_date = parse_date($latest_commit{'author_epoch'});
+ my $latest_epoch = $latest_commit{'committer_epoch'};
+ %latest_date = parse_date($latest_epoch);
+ my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+ if (defined $if_modified) {
+ my $since;
+ if (eval { require HTTP::Date; 1; }) {
+ $since = HTTP::Date::str2time($if_modified);
+ } elsif (eval { require Time::ParseDate; 1; }) {
+ $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+ }
+ if (defined $since && $latest_epoch <= $since) {
+ print $cgi->header(
+ -type => $content_type,
+ -charset => 'utf-8',
+ -last_modified => $latest_date{'rfc2822'},
+ -status => '304 Not Modified');
+ return;
+ }
+ }
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
print "<title>$title</title>\n" .
"<link>$alt_url</link>\n" .
"<description>$descr</description>\n" .
- "<language>en</language>\n";
+ "<language>en</language>\n" .
+ # project owner is responsible for 'editorial' content
+ "<managingEditor>$owner</managingEditor>\n";
+ if (defined $logo || defined $favicon) {
+ # prefer the logo to the favicon, since RSS
+ # doesn't allow both
+ my $img = esc_url($logo || $favicon);
+ print "<image>\n" .
+ "<url>$img</url>\n" .
+ "<title>$title</title>\n" .
+ "<link>$alt_url</link>\n" .
+ "</image>\n";
+ }
+ if (%latest_date) {
+ print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
+ print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
+ }
+ print "<generator>gitweb v.$version/$git_version</generator>\n";
} elsif ($format eq 'atom') {
print <<XML;
<feed xmlns="http://www.w3.org/2005/Atom">
} else {
print "<updated>$latest_date{'iso-8601'}</updated>\n";
}
+ print "<generator version='$version/$git_version'>gitweb</generator>\n";
}
# contents
sub git_opml {
my @list = git_get_projects_list();
- print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+ print $cgi->header(
+ -type => 'text/xml',
+ -charset => 'utf-8',
+ -content_disposition => 'inline; filename="opml.xml"');
+
print <<XML;
<?xml version="1.0" encoding="utf-8"?>
<opml version="1.0">
}
my $path = esc_html(chop_str($proj{'path'}, 25, 5));
- my $rss = "$my_url?p=$proj{'path'};a=rss";
- my $html = "$my_url?p=$proj{'path'};a=summary";
+ my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
+ my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
}
print <<XML;
index 600f69f2fe2a0271f4bdf736f95f70c8f7381aa4..062b2b6f28f6332518240d2a474a7739735e1ecf 100644 (file)
--- a/grep.c
+++ b/grep.c
p->next = NULL;
}
+static int is_fixed(const char *s)
+{
+ while (*s && !is_regex_special(*s))
+ s++;
+ return !*s;
+}
+
static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
{
- int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+ int err;
+
+ if (opt->fixed || is_fixed(p->pattern))
+ p->fixed = 1;
+ if (opt->regflags & REG_ICASE)
+ p->fixed = 0;
+ if (p->fixed)
+ return;
+
+ err = regcomp(&p->regexp, p->pattern, opt->regflags);
if (err) {
char errbuf[1024];
char where[1024];
case GREP_PATTERN: /* atom */
case GREP_PATTERN_HEAD:
case GREP_PATTERN_BODY:
- if (!opt->fixed)
- compile_regexp(p, opt);
+ compile_regexp(p, opt);
break;
default:
opt->extended = 1;
static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
{
int hit = 0;
- int at_true_bol = 1;
int saved_ch = 0;
regmatch_t pmatch[10];
@@ -315,7 +329,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
}
again:
- if (!opt->fixed) {
+ if (!p->fixed) {
regex_t *exp = &p->regexp;
hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
pmatch, 0);
@@ -337,7 +351,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
* either end of the line, or at word boundary
* (i.e. the next char must not be a word char).
*/
- if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
+ if ( ((pmatch[0].rm_so == 0) ||
!word_char(bol[pmatch[0].rm_so-1])) &&
((pmatch[0].rm_eo == (eol-bol)) ||
!word_char(bol[pmatch[0].rm_eo])) )
@@ -349,10 +363,14 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
/* There could be more than one match on the
* line, and the first match might not be
* strict word match. But later ones could be!
+ * Forward to the next possible start, i.e. the
+ * next position following a non-word char.
*/
bol = pmatch[0].rm_so + bol + 1;
- at_true_bol = 0;
- goto again;
+ while (word_char(bol[-1]) && bol < eol)
+ bol++;
+ if (bol < eol)
+ goto again;
}
}
if (p->token == GREP_PATTERN_HEAD && saved_ch)
h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
break;
default:
- die("Unexpected node type (internal error) %d\n", x->node);
+ die("Unexpected node type (internal error) %d", x->node);
}
if (collect_hits)
x->hit |= h;
index 45a222d904b5898758c3c84f6ef1e0119c81f6b2..5102ce335d29811dd448e173f2e90e8d03b5f011 100644 (file)
--- a/grep.h
+++ b/grep.h
const char *pattern;
enum grep_header_field field;
regex_t regexp;
+ unsigned fixed:1;
};
enum grep_expr_node {
diff --git a/hash-object.c b/hash-object.c
index 846e91a23126b747fbea8d9a8511f708c3d70e43..37e66779ab9e14dbe5b5416d0e19b04d7f122d52 100644 (file)
--- a/hash-object.c
+++ b/hash-object.c
#include "blob.h"
#include "quote.h"
#include "parse-options.h"
+#include "exec_cmd.h"
static void hash_fd(int fd, const char *type, int write_object, const char *path)
{
type = blob_type;
+ git_extract_argv0_path(argv[0]);
+
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
diff --git a/http-push.c b/http-push.c
index cb5bf95a736c571259aef51884cc20e4169bf707..30d2d340418f7f40b77823b1b58b307985347bdf 100644 (file)
--- a/http-push.c
+++ b/http-push.c
#include "exec_cmd.h"
#include "remote.h"
#include "list-objects.h"
+#include "sigchain.h"
#include <expat.h>
char *url;
char *owner;
char *token;
+ char tmpfile_suffix[41];
time_t start_time;
long timeout;
int refreshing;
struct remote_ls_ctx *parent;
};
+/* get_dav_token_headers options */
+enum dav_header_flag {
+ DAV_HEADER_IF = (1u << 0),
+ DAV_HEADER_LOCK = (1u << 1),
+ DAV_HEADER_TIMEOUT = (1u << 2)
+};
+
+static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct curl_slist *dav_headers = NULL;
+
+ if (options & DAV_HEADER_IF) {
+ strbuf_addf(&buf, "If: (<%s>)", lock->token);
+ dav_headers = curl_slist_append(dav_headers, buf.buf);
+ strbuf_reset(&buf);
+ }
+ if (options & DAV_HEADER_LOCK) {
+ strbuf_addf(&buf, "Lock-Token: <%s>", lock->token);
+ dav_headers = curl_slist_append(dav_headers, buf.buf);
+ strbuf_reset(&buf);
+ }
+ if (options & DAV_HEADER_TIMEOUT) {
+ strbuf_addf(&buf, "Timeout: Second-%ld", lock->timeout);
+ dav_headers = curl_slist_append(dav_headers, buf.buf);
+ strbuf_reset(&buf);
+ }
+ strbuf_release(&buf);
+
+ return dav_headers;
+}
+
+static void append_remote_object_url(struct strbuf *buf, const char *url,
+ const char *hex,
+ int only_two_digit_prefix)
+{
+ strbuf_addf(buf, "%sobjects/%.*s/", url, 2, hex);
+ if (!only_two_digit_prefix)
+ strbuf_addf(buf, "%s", hex+2);
+}
+
static void finish_request(struct transfer_request *request);
static void release_request(struct transfer_request *request);
}
#ifdef USE_CURL_MULTI
+
+static char *get_remote_object_url(const char *url, const char *hex,
+ int only_two_digit_prefix)
+{
+ struct strbuf buf = STRBUF_INIT;
+ append_remote_object_url(&buf, url, hex, only_two_digit_prefix);
+ return strbuf_detach(&buf, NULL);
+}
+
static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
void *data)
{
char *filename;
char prevfile[PATH_MAX];
char *url;
- char *posn;
int prevlocal;
unsigned char prev_buf[PREV_BUF_SIZE];
ssize_t prev_read = 0;
git_SHA1_Init(&request->c);
- url = xmalloc(strlen(remote->url) + 50);
- request->url = xmalloc(strlen(remote->url) + 50);
- strcpy(url, remote->url);
- posn = url + strlen(remote->url);
- strcpy(posn, "objects/");
- posn += 8;
- memcpy(posn, hex, 2);
- posn += 2;
- *(posn++) = '/';
- strcpy(posn, hex + 2);
- strcpy(request->url, url);
+ url = get_remote_object_url(remote->url, hex, 0);
+ request->url = xstrdup(url);
/* If a previous temp file is present, process what was already
fetched. */
{
char *hex = sha1_to_hex(request->obj->sha1);
struct active_request_slot *slot;
- char *posn;
- request->url = xmalloc(strlen(remote->url) + 13);
- strcpy(request->url, remote->url);
- posn = request->url + strlen(remote->url);
- strcpy(posn, "objects/");
- posn += 8;
- memcpy(posn, hex, 2);
- posn += 2;
- strcpy(posn, "/");
+ request->url = get_remote_object_url(remote->url, hex, 1);
slot = get_active_slot();
slot->callback_func = process_response;
{
char *hex = sha1_to_hex(request->obj->sha1);
struct active_request_slot *slot;
- char *posn;
+ struct strbuf buf = STRBUF_INIT;
enum object_type type;
char hdr[50];
void *unpacked;
request->buffer.buf.len = stream.total_out;
- request->url = xmalloc(strlen(remote->url) +
- strlen(request->lock->token) + 51);
- strcpy(request->url, remote->url);
- posn = request->url + strlen(remote->url);
- strcpy(posn, "objects/");
- posn += 8;
- memcpy(posn, hex, 2);
- posn += 2;
- *(posn++) = '/';
- strcpy(posn, hex + 2);
- request->dest = xmalloc(strlen(request->url) + 14);
- sprintf(request->dest, "Destination: %s", request->url);
- posn += 38;
- *(posn++) = '_';
- strcpy(posn, request->lock->token);
+ strbuf_addstr(&buf, "Destination: ");
+ append_remote_object_url(&buf, remote->url, hex, 0);
+ request->dest = strbuf_detach(&buf, NULL);
+
+ append_remote_object_url(&buf, remote->url, hex, 0);
+ strbuf_add(&buf, request->lock->tmpfile_suffix, 41);
+ request->url = strbuf_detach(&buf, NULL);
slot = get_active_slot();
slot->callback_func = process_response;
{
struct active_request_slot *slot;
struct slot_results results;
- char *if_header;
- char timeout_header[25];
- struct curl_slist *dav_headers = NULL;
+ struct curl_slist *dav_headers;
int rc = 0;
lock->refreshing = 1;
- if_header = xmalloc(strlen(lock->token) + 25);
- sprintf(if_header, "If: (<%s>)", lock->token);
- sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout);
- dav_headers = curl_slist_append(dav_headers, if_header);
- dav_headers = curl_slist_append(dav_headers, timeout_header);
+ dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF | DAV_HEADER_TIMEOUT);
slot = get_active_slot();
slot->results = &results;
lock->refreshing = 0;
curl_slist_free_all(dav_headers);
- free(if_header);
return rc;
}
static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
{
struct remote_lock *lock = (struct remote_lock *)ctx->userData;
+ git_SHA_CTX sha_ctx;
+ unsigned char lock_token_sha1[20];
if (tag_closed && ctx->cdata) {
if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
} else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
lock->token = xmalloc(strlen(ctx->cdata) + 1);
strcpy(lock->token, ctx->cdata);
+
+ git_SHA1_Init(&sha_ctx);
+ git_SHA1_Update(&sha_ctx, lock->token, strlen(lock->token));
+ git_SHA1_Final(lock_token_sha1, &sha_ctx);
+
+ lock->tmpfile_suffix[0] = '_';
+ memcpy(lock->tmpfile_suffix + 1, sha1_to_hex(lock_token_sha1), 40);
}
}
}
struct active_request_slot *slot;
struct slot_results results;
struct remote_lock *prev = remote->locks;
- char *lock_token_header;
- struct curl_slist *dav_headers = NULL;
+ struct curl_slist *dav_headers;
int rc = 0;
- lock_token_header = xmalloc(strlen(lock->token) + 31);
- sprintf(lock_token_header, "Lock-Token: <%s>",
- lock->token);
- dav_headers = curl_slist_append(dav_headers, lock_token_header);
+ dav_headers = get_dav_token_headers(lock, DAV_HEADER_LOCK);
slot = get_active_slot();
slot->results = &results;
}
curl_slist_free_all(dav_headers);
- free(lock_token_header);
if (remote->locks == lock) {
remote->locks = lock->next;
static void remove_locks_on_signal(int signo)
{
remove_locks();
- signal(signo, SIG_DFL);
+ sigchain_pop(signo);
raise(signo);
}
{
struct active_request_slot *slot;
struct slot_results results;
- char *if_header;
struct buffer out_buffer = { STRBUF_INIT, 0 };
- struct curl_slist *dav_headers = NULL;
+ struct curl_slist *dav_headers;
- if_header = xmalloc(strlen(lock->token) + 25);
- sprintf(if_header, "If: (<%s>)", lock->token);
- dav_headers = curl_slist_append(dav_headers, if_header);
+ dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF);
strbuf_addf(&out_buffer.buf, "%s\n", sha1_to_hex(sha1));
if (start_active_slot(slot)) {
run_active_slot(slot);
strbuf_release(&out_buffer.buf);
- free(if_header);
if (results.curl_result != CURLE_OK) {
fprintf(stderr,
"PUT error: curl result=%d, HTTP code=%ld\n",
}
} else {
strbuf_release(&out_buffer.buf);
- free(if_header);
fprintf(stderr, "Unable to start PUT request\n");
return 0;
}
struct buffer buffer = { STRBUF_INIT, 0 };
struct active_request_slot *slot;
struct slot_results results;
- char *if_header;
- struct curl_slist *dav_headers = NULL;
+ struct curl_slist *dav_headers;
remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
add_remote_info_ref, &buffer.buf);
if (!aborted) {
- if_header = xmalloc(strlen(lock->token) + 25);
- sprintf(if_header, "If: (<%s>)", lock->token);
- dav_headers = curl_slist_append(dav_headers, if_header);
+ dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF);
slot = get_active_slot();
slot->results = &results;
results.curl_result, results.http_code);
}
}
- free(if_header);
}
strbuf_release(&buffer.buf);
}
struct ref *ref;
char *rewritten_url = NULL;
+ git_extract_argv0_path(argv[0]);
+
setup_git_directory();
remote = xcalloc(sizeof(*remote), 1);
goto cleanup;
}
- signal(SIGINT, remove_locks_on_signal);
- signal(SIGHUP, remove_locks_on_signal);
- signal(SIGQUIT, remove_locks_on_signal);
- signal(SIGTERM, remove_locks_on_signal);
+ sigchain_push_common(remove_locks_on_signal);
/* Check whether the remote has server info files */
remote->can_update_info_refs = 0;
diff --git a/imap-send.c b/imap-send.c
index 3703dbd1af65c30806a98f0d2d3dc14b8b0e9798..f91293c23f2bcb6d673e0316cd3736fdddd0fbe4 100644 (file)
--- a/imap-send.c
+++ b/imap-send.c
*/
#include "cache.h"
+#include "exec_cmd.h"
#ifdef NO_OPENSSL
typedef void *SSL;
#endif
len = vsnprintf(tmp, sizeof(tmp), fmt, ap);
if (len < 0)
- die("Fatal: Out of memory\n");
+ die("Fatal: Out of memory");
if (len >= sizeof(tmp))
- die("imap command overflow !\n");
+ die("imap command overflow!");
*strp = xmemdupz(tmp, len);
return len;
}
va_start(va, fmt);
if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
- die("Fatal: buffer too small. Please report a bug.\n");
+ die("Fatal: buffer too small. Please report a bug.");
va_end(va);
return ret;
}
int total, n = 0;
int nongit_ok;
+ git_extract_argv0_path(argv[0]);
+
/* init the random number generator */
arc4_init();
diff --git a/index-pack.c b/index-pack.c
index c0a3d97a1276265d3f253dc396ded9c54d2caf5c..7fee8725333860dbbd13d8de5ae7baf1ef33976d 100644 (file)
--- a/index-pack.c
+++ b/index-pack.c
#include "tree.h"
#include "progress.h"
#include "fsck.h"
+#include "exec_cmd.h"
static const char index_pack_usage[] =
"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
input_fd = 0;
if (!pack_name) {
static char tmpfile[PATH_MAX];
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/pack/tmp_pack_XXXXXX", get_object_directory());
- output_fd = xmkstemp(tmpfile);
+ output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ "pack/tmp_pack_XXXXXX");
pack_name = xstrdup(tmpfile);
} else
output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
if (output_fd < 0)
- die("unable to create %s: %s\n", pack_name, strerror(errno));
+ die("unable to create %s: %s", pack_name, strerror(errno));
pack_fd = output_fd;
} else {
input_fd = open(pack_name, O_RDONLY);
if (keep_msg) {
int keep_fd, keep_msg_len = strlen(keep_msg);
- if (!keep_name) {
- snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
- get_object_directory(), sha1_to_hex(sha1));
- keep_name = name;
- }
- keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
+ if (!keep_name)
+ keep_fd = odb_pack_keep(name, sizeof(name), sha1);
+ else
+ keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+
if (keep_fd < 0) {
if (errno != EEXIST)
- die("cannot write keep file");
+ die("cannot write keep file '%s' (%s)",
+ keep_name, strerror(errno));
} else {
if (keep_msg_len > 0) {
write_or_die(keep_fd, keep_msg, keep_msg_len);
write_or_die(keep_fd, "\n", 1);
}
if (close(keep_fd) != 0)
- die("cannot write keep file");
+ die("cannot close written keep file '%s' (%s)",
+ keep_name, strerror(errno));
report = "keep";
}
}
struct pack_idx_entry **idx_objects;
unsigned char pack_sha1[20];
+ git_extract_argv0_path(argv[0]);
+
/*
* We wish to read the repository's config file if any, and
* for that it is necessary to call setup_git_directory_gently().
diff --git a/lockfile.c b/lockfile.c
index 8e556ff8c9671864db44dc8b6f4a861bd35142a6..1db1a2fefcf40c4f6e613b7a8f750ab9c22adbc3 100644 (file)
--- a/lockfile.c
+++ b/lockfile.c
* Copyright (c) 2005, Junio C Hamano
*/
#include "cache.h"
+#include "sigchain.h"
static struct lock_file *lock_file_list;
static const char *alternate_index_output;
static void remove_lock_file_on_signal(int signo)
{
remove_lock_file();
- signal(signo, SIG_DFL);
+ sigchain_pop(signo);
raise(signo);
}
lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
if (0 <= lk->fd) {
if (!lock_file_list) {
- signal(SIGINT, remove_lock_file_on_signal);
- signal(SIGHUP, remove_lock_file_on_signal);
- signal(SIGTERM, remove_lock_file_on_signal);
- signal(SIGQUIT, remove_lock_file_on_signal);
- signal(SIGPIPE, remove_lock_file_on_signal);
+ sigchain_push_common(remove_lock_file_on_signal);
atexit(remove_lock_file);
}
lk->owner = getpid();
diff --git a/log-tree.c b/log-tree.c
index 194ddb13da09668334cd9e1f6eeef517c3346ee9..84a74e544b7bcc20c887f321e389ecf3cfb560d6 100644 (file)
--- a/log-tree.c
+++ b/log-tree.c
struct commit_list *p;
for (p = commit->parents; p ; p = p->next) {
struct commit *parent = p->item;
- printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+ printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev));
}
}
putchar('>');
}
}
- fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+ fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
show_decorations(opt, commit);
putchar('>');
}
}
- fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+ fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit),
stdout);
if (opt->print_parents)
show_parents(commit, abbrev_commit);
if (parent)
printf(" (from %s)",
- diff_unique_abbrev(parent->object.sha1,
+ find_unique_abbrev(parent->object.sha1,
abbrev_commit));
show_decorations(opt, commit);
printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
diff --git a/mailmap.c b/mailmap.c
index 88fc6f394684436967002ca477eac1e084537348..f12bb45a3f734e48f003a7a2943d168c26778603 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
#include "string-list.h"
#include "mailmap.h"
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
+#define DEBUG_MAILMAP 0
+#if DEBUG_MAILMAP
+#define debug_mm(...) fprintf(stderr, __VA_ARGS__)
+#else
+static inline void debug_mm(const char *format, ...) {}
+#endif
+
+const char *git_mailmap_file;
+
+struct mailmap_info {
+ char *name;
+ char *email;
+};
+
+struct mailmap_entry {
+ /* name and email for the simple mail-only case */
+ char *name;
+ char *email;
+
+ /* name and email for the complex mail and name matching case */
+ struct string_list namemap;
+};
+
+static void free_mailmap_info(void *p, const char *s)
+{
+ struct mailmap_info *mi = (struct mailmap_info *)p;
+ debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email);
+ free(mi->name);
+ free(mi->email);
+}
+
+static void free_mailmap_entry(void *p, const char *s)
+{
+ struct mailmap_entry *me = (struct mailmap_entry *)p;
+ debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr);
+ debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email);
+ free(me->name);
+ free(me->email);
+
+ me->namemap.strdup_strings = 1;
+ string_list_clear_func(&me->namemap, free_mailmap_info);
+}
+
+static void add_mapping(struct string_list *map,
+ char *new_name, char *new_email, char *old_name, char *old_email)
+{
+ struct mailmap_entry *me;
+ int index;
+ if (old_email == NULL) {
+ old_email = new_email;
+ new_email = NULL;
+ }
+
+ if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) {
+ /* mailmap entry exists, invert index value */
+ index = -1 - index;
+ } else {
+ /* create mailmap entry */
+ struct string_list_item *item = string_list_insert_at_index(index, old_email, map);
+ item->util = xmalloc(sizeof(struct mailmap_entry));
+ memset(item->util, 0, sizeof(struct mailmap_entry));
+ ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
+ }
+ me = (struct mailmap_entry *)map->items[index].util;
+
+ if (old_name == NULL) {
+ debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index);
+ /* Replace current name and new email for simple entry */
+ free(me->name);
+ free(me->email);
+ if (new_name)
+ me->name = xstrdup(new_name);
+ if (new_email)
+ me->email = xstrdup(new_email);
+ } else {
+ struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
+ debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
+ if (new_name)
+ mi->name = xstrdup(new_name);
+ if (new_email)
+ mi->email = xstrdup(new_email);
+ string_list_insert(old_name, &me->namemap)->util = mi;
+ }
+
+ debug_mm("mailmap: '%s' <%s> -> '%s' <%s>\n",
+ old_name, old_email, new_name, new_email);
+}
+
+static char *parse_name_and_email(char *buffer, char **name, char **email)
+{
+ char *left, *right, *nstart, *nend;
+ *name = *email = 0;
+
+ if ((left = strchr(buffer, '<')) == NULL)
+ return NULL;
+ if ((right = strchr(left+1, '>')) == NULL)
+ return NULL;
+ if (left+1 == right)
+ return NULL;
+
+ /* remove whitespace from beginning and end of name */
+ nstart = buffer;
+ while (isspace(*nstart) && nstart < left)
+ ++nstart;
+ nend = left-1;
+ while (isspace(*nend) && nend > nstart)
+ --nend;
+
+ *name = (nstart < nend ? nstart : NULL);
+ *email = left+1;
+ *(nend+1) = '\0';
+ *right++ = '\0';
+
+ return (*right == '\0' ? NULL : right);
+}
+
+static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
{
char buffer[1024];
- FILE *f = fopen(filename, "r");
+ FILE *f = (filename == NULL ? NULL : fopen(filename, "r"));
if (f == NULL)
return 1;
while (fgets(buffer, sizeof(buffer), f) != NULL) {
- char *end_of_name, *left_bracket, *right_bracket;
- char *name, *email;
- int i;
+ char *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0;
if (buffer[0] == '#') {
static const char abbrev[] = "# repo-abbrev:";
int abblen = sizeof(abbrev) - 1;
@@ -36,41 +150,49 @@ int read_mailmap(struct string_list *map, const char *filename, char **repo_abbr
}
continue;
}
- if ((left_bracket = strchr(buffer, '<')) == NULL)
- continue;
- if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
- continue;
- if (right_bracket == left_bracket + 1)
- continue;
- for (end_of_name = left_bracket;
- end_of_name != buffer && isspace(end_of_name[-1]);
- end_of_name--)
- ; /* keep on looking */
- if (end_of_name == buffer)
- continue;
- name = xmalloc(end_of_name - buffer + 1);
- strlcpy(name, buffer, end_of_name - buffer + 1);
- email = xmalloc(right_bracket - left_bracket);
- for (i = 0; i < right_bracket - left_bracket - 1; i++)
- email[i] = tolower(left_bracket[i + 1]);
- email[right_bracket - left_bracket - 1] = '\0';
- string_list_insert(email, map)->util = name;
+ if ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL)
+ parse_name_and_email(name2, &name2, &email2);
+
+ if (email1)
+ add_mapping(map, name1, email1, name2, email2);
}
fclose(f);
return 0;
}
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+int read_mailmap(struct string_list *map, char **repo_abbrev)
+{
+ map->strdup_strings = 1;
+ /* each failure returns 1, so >1 means both calls failed */
+ return read_single_mailmap(map, ".mailmap", repo_abbrev) +
+ read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1;
+}
+
+void clear_mailmap(struct string_list *map)
+{
+ debug_mm("mailmap: clearing %d entries...\n", map->nr);
+ map->strdup_strings = 1;
+ string_list_clear_func(map, free_mailmap_entry);
+ debug_mm("mailmap: cleared\n");
+}
+
+int map_user(struct string_list *map,
+ char *email, int maxlen_email, char *name, int maxlen_name)
{
char *p;
struct string_list_item *item;
+ struct mailmap_entry *me;
char buf[1024], *mailbuf;
int i;
- /* autocomplete common developers */
+ /* figure out space requirement for email */
p = strchr(email, '>');
- if (!p)
- return 0;
+ if (!p) {
+ /* email passed in might not be wrapped in <>, but end with a \0 */
+ p = memchr(email, '\0', maxlen_email);
+ if (p == 0)
+ return 0;
+ }
if (p - email + 1 < sizeof(buf))
mailbuf = buf;
else
@@ -80,13 +202,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen
for (i = 0; i < p - email; i++)
mailbuf[i] = tolower(email[i]);
mailbuf[i] = 0;
+
+ debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
item = string_list_lookup(mailbuf, map);
+ if (item != NULL) {
+ me = (struct mailmap_entry *)item->util;
+ if (me->namemap.nr) {
+ /* The item has multiple items, so we'll look up on name too */
+ /* If the name is not found, we choose the simple entry */
+ struct string_list_item *subitem = string_list_lookup(name, &me->namemap);
+ if (subitem)
+ item = subitem;
+ }
+ }
if (mailbuf != buf)
free(mailbuf);
if (item != NULL) {
- const char *realname = (const char *)item->util;
- strlcpy(name, realname, maxlen);
+ struct mailmap_info *mi = (struct mailmap_info *)item->util;
+ if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) {
+ debug_mm("map_user: -- (no simple mapping)\n");
+ return 0;
+ }
+ if (maxlen_email && mi->email)
+ strlcpy(email, mi->email, maxlen_email);
+ if (maxlen_name && mi->name)
+ strlcpy(name, mi->name, maxlen_name);
+ debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : "");
return 1;
}
+ debug_mm("map_user: --\n");
return 0;
}
+
+int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+{
+ return map_user(map, (char *)email, 0, name, maxlen);
+}
diff --git a/mailmap.h b/mailmap.h
index 6e48f83cedd13e24d50cddf47f037791ddc5ad4b..4b2ca3a7de972c10f214b38a25be522abcbbafd0 100644 (file)
--- a/mailmap.h
+++ b/mailmap.h
#ifndef MAILMAP_H
#define MAILMAP_H
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev);
+int read_mailmap(struct string_list *map, char **repo_abbrev);
+void clear_mailmap(struct string_list *map);
+
int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
+int map_user(struct string_list *mailmap,
+ char *email, int maxlen_email, char *name, int maxlen_name);
#endif
diff --git a/merge-index.c b/merge-index.c
index 7827e87a928586226570132fc8922991b1cb6c8a..aa9cf23a39ae271a53d1a0c05ac99be0e832b46a 100644 (file)
--- a/merge-index.c
+++ b/merge-index.c
#include "cache.h"
#include "run-command.h"
+#include "exec_cmd.h"
static const char *pgm;
static const char *arguments[9];
signal(SIGCHLD, SIG_DFL);
if (argc < 3)
- usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+ usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+
+ git_extract_argv0_path(argv[0]);
setup_git_directory();
read_cache();
diff --git a/merge-recursive.c b/merge-recursive.c
index b97026bd5cc1d2ef1b46a9ef3dcd7562ad52c377..ee853b990d8bfb15e0058fefbbd267bd58ed40fc 100644 (file)
--- a/merge-recursive.c
+++ b/merge-recursive.c
string_list_insert(newpath, &o->current_file_set);
free(newpath);
- return READ_TREE_RECURSIVE;
+ return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
}
static int get_files_dirs(struct merge_options *o, struct tree *tree)
diff --git a/merge-tree.c b/merge-tree.c
index 2d1413efbbc33c51fd4820933dcb54164e12d706..f01e7c81aebea84b95154c9076af66979e52715f 100644 (file)
--- a/merge-tree.c
+++ b/merge-tree.c
#include "tree-walk.h"
#include "xdiff-interface.h"
#include "blob.h"
+#include "exec_cmd.h"
-static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
+static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
static int resolve_directories = 1;
struct merge_list {
if (argc != 4)
usage(merge_tree_usage);
+ git_extract_argv0_path(argv[0]);
+
setup_git_directory();
buf1 = get_tree_descriptor(t+0, argv[1]);
index ba3d495e0715d83ffab3103e4d340a3b9ac4f4e7..99a356e9ee75cb247d80ed6dc0b251ceb0bd9e46 100644 (file)
--- a/mktag.c
+++ b/mktag.c
#include "cache.h"
#include "tag.h"
+#include "exec_cmd.h"
/*
* A signature file has a very simple fixed format: four lines
unsigned char result_sha1[20];
if (argc != 1)
- usage("git-mktag < signaturefile");
+ usage("git mktag < signaturefile");
+
+ git_extract_argv0_path(argv[0]);
setup_git_directory();
diff --git a/mktree.c b/mktree.c
index 514fd9b15a6680389bf2c1274d29c55d2c1034f7..137a0950f686691740ac87330cf0ac7bdea8b1e7 100644 (file)
--- a/mktree.c
+++ b/mktree.c
#include "cache.h"
#include "quote.h"
#include "tree.h"
+#include "exec_cmd.h"
static struct treeent {
unsigned mode;
write_sha1_file(buf.buf, buf.len, tree_type, sha1);
}
-static const char mktree_usage[] = "git-mktree [-z]";
+static const char mktree_usage[] = "git mktree [-z]";
int main(int ac, char **av)
{
unsigned char sha1[20];
int line_termination = '\n';
+ git_extract_argv0_path(av[0]);
+
setup_git_directory();
while ((1 < ac) && av[1][0] == '-') {
diff --git a/pack-redundant.c b/pack-redundant.c
index 25b81a445c8fafe0c00ce30082b7d9a7c22ccf1e..48a12bc1352ad53fbc19ec8c5982a91673a098e1 100644 (file)
--- a/pack-redundant.c
+++ b/pack-redundant.c
*/
#include "cache.h"
+#include "exec_cmd.h"
#define BLKSIZE 512
pll_free(perm_all);
}
if (perm_ok == NULL)
- die("Internal error: No complete sets found!\n");
+ die("Internal error: No complete sets found!");
/* find the permutation with the smallest size */
perm = perm_ok;
struct packed_git *p = packed_git;
if (strlen(filename) < 40)
- die("Bad pack filename: %s\n", filename);
+ die("Bad pack filename: %s", filename);
while (p) {
if (strstr(p->pack_name, filename))
return add_pack(p);
p = p->next;
}
- die("Filename %s not found in packed_git\n", filename);
+ die("Filename %s not found in packed_git", filename);
}
static void load_all(void)
unsigned char *sha1;
char buf[42]; /* 40 byte sha1 + \n + \0 */
+ git_extract_argv0_path(argv[0]);
+
setup_git_directory();
for (i = 1; i < argc; i++) {
add_pack_file(*(argv + i++));
if (local_packs == NULL)
- die("Zero packs found!\n");
+ die("Zero packs found!");
load_all_objects();
diff --git a/pack-write.c b/pack-write.c
index b426006c5851c98fce8894bd9f76cd51a7cde170..7053538f4cf44e15a788ab46dfb680ee85ce4fc2 100644 (file)
--- a/pack-write.c
+++ b/pack-write.c
if (!index_name) {
static char tmpfile[PATH_MAX];
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/pack/tmp_idx_XXXXXX", get_object_directory());
- fd = xmkstemp(tmpfile);
+ fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX");
index_name = xstrdup(tmpfile);
} else {
unlink(index_name);
char packname[46];
/*
- * The first thing we expects from index-pack's output
+ * The first thing we expect from index-pack's output
* is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
* %40s is the newly created pack SHA1 name. In the "keep"
* case, we need it to remove the corresponding .keep file
index f19ddbc87df04f117cd5e39189c8322fd5f29d68..4921843577e42b774457a61277b9bc3441d3ab6b 100644 (file)
--- a/pager.c
+++ b/pager.c
#include "cache.h"
#include "run-command.h"
+#include "sigchain.h"
/*
* This is split up from the rest of git so that we can do
finish_command(&pager_process);
}
+static void wait_for_pager_signal(int signo)
+{
+ wait_for_pager();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
void setup_pager(void)
{
const char *pager = getenv("GIT_PAGER");
close(pager_process.in);
/* this makes sure that the parent terminates after the pager */
+ sigchain_push_common(wait_for_pager_signal);
atexit(wait_for_pager);
}
diff --git a/parse-options.c b/parse-options.c
index 9eb55cc8b5182495b687e133a938937db1bc3949..4c5d09dd25aede8ea7e14886f5c17ed847a8f0d9 100644 (file)
--- a/parse-options.c
+++ b/parse-options.c
#include "git-compat-util.h"
#include "parse-options.h"
#include "cache.h"
+#include "commit.h"
#define OPT_SHORT 1
#define OPT_UNSET 2
return 0;
}
+int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+{
+ unsigned char sha1[20];
+ struct commit *commit;
+
+ if (!arg)
+ return -1;
+ if (get_sha1(arg, sha1))
+ return error("malformed object name %s", arg);
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ return error("no such commit %s", arg);
+ commit_list_insert(commit, opt->value);
+ return 0;
+}
+
/*
* This should really be OPTION_FILENAME type as a part of
* parse_options that take prefix to do this while parsing.
diff --git a/parse-options.h b/parse-options.h
index 034162ec6975cfca8dedadc4eb541212b0d97e43..912290549bcbb03b7e82750a40297fb1a70b8206 100644 (file)
--- a/parse-options.h
+++ b/parse-options.h
extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
+extern int parse_opt_with_commit(const struct option *, const char *, int);
#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")
diff --git a/patch-id.c b/patch-id.c
index 871f1d20c0e364220d23035b34685ced8737cda8..0df4cb086ba26d1f4d56b8347a6a7bcf219832f5 100644 (file)
--- a/patch-id.c
+++ b/patch-id.c
#include "cache.h"
+#include "exec_cmd.h"
static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
{
flush_current_id(patchlen, sha1, &ctx);
}
-static const char patch_id_usage[] = "git-patch-id < patch";
+static const char patch_id_usage[] = "git patch-id < patch";
int main(int argc, char **argv)
{
if (argc != 1)
usage(patch_id_usage);
+ git_extract_argv0_path(argv[0]);
+
generate_id_list();
return 0;
}
index a074aea64921eb1fb90f079ede9087e6b8109f6a..e332b504a6b8c6b229da2526c4f810b8d0fb9889 100644 (file)
--- a/path.c
+++ b/path.c
}
/*
- * path = absolute path
- * buf = buffer of at least max(2, strlen(path)+1) bytes
- * It is okay if buf == path, but they should not overlap otherwise.
+ * It is okay if dst == src, but they should not overlap otherwise.
*
- * Performs the following normalizations on path, storing the result in buf:
- * - Removes trailing slashes.
- * - Removes empty components.
+ * Performs the following normalizations on src, storing the result in dst:
+ * - Ensures that components are separated by '/' (Windows only)
+ * - Squashes sequences of '/'.
* - Removes "." components.
* - Removes ".." components, and the components the precede them.
- * "" and paths that contain only slashes are normalized to "/".
- * Returns the length of the output.
+ * Returns failure (non-zero) if a ".." component appears as first path
+ * component anytime during the normalization. Otherwise, returns success (0).
*
* Note that this function is purely textual. It does not follow symlinks,
* verify the existence of the path, or make any system calls.
*/
-int normalize_absolute_path(char *buf, const char *path)
+int normalize_path_copy(char *dst, const char *src)
{
- const char *comp_start = path, *comp_end = path;
- char *dst = buf;
- int comp_len;
- assert(buf);
- assert(path);
-
- while (*comp_start) {
- assert(*comp_start == '/');
- while (*++comp_end && *comp_end != '/')
- ; /* nothing */
- comp_len = comp_end - comp_start;
-
- if (!strncmp("/", comp_start, comp_len) ||
- !strncmp("/.", comp_start, comp_len))
- goto next;
-
- if (!strncmp("/..", comp_start, comp_len)) {
- while (dst > buf && *--dst != '/')
- ; /* nothing */
- goto next;
- }
+ char *dst0;
- memmove(dst, comp_start, comp_len);
- dst += comp_len;
- next:
- comp_start = comp_end;
+ if (has_dos_drive_prefix(src)) {
+ *dst++ = *src++;
+ *dst++ = *src++;
}
+ dst0 = dst;
- if (dst == buf)
+ if (is_dir_sep(*src)) {
*dst++ = '/';
+ while (is_dir_sep(*src))
+ src++;
+ }
+ for (;;) {
+ char c = *src;
+
+ /*
+ * A path component that begins with . could be
+ * special:
+ * (1) "." and ends -- ignore and terminate.
+ * (2) "./" -- ignore them, eat slash and continue.
+ * (3) ".." and ends -- strip one and terminate.
+ * (4) "../" -- strip one, eat slash and continue.
+ */
+ if (c == '.') {
+ if (!src[1]) {
+ /* (1) */
+ src++;
+ } else if (is_dir_sep(src[1])) {
+ /* (2) */
+ src += 2;
+ while (is_dir_sep(*src))
+ src++;
+ continue;
+ } else if (src[1] == '.') {
+ if (!src[2]) {
+ /* (3) */
+ src += 2;
+ goto up_one;
+ } else if (is_dir_sep(src[2])) {
+ /* (4) */
+ src += 3;
+ while (is_dir_sep(*src))
+ src++;
+ goto up_one;
+ }
+ }
+ }
+
+ /* copy up to the next '/', and eat all '/' */
+ while ((c = *src++) != '\0' && !is_dir_sep(c))
+ *dst++ = c;
+ if (is_dir_sep(c)) {
+ *dst++ = '/';
+ while (is_dir_sep(c))
+ c = *src++;
+ src--;
+ } else if (!c)
+ break;
+ continue;
+
+ up_one:
+ /*
+ * dst0..dst is prefix portion, and dst[-1] is '/';
+ * go up one level.
+ */
+ dst--; /* go to trailing '/' */
+ if (dst <= dst0)
+ return -1;
+ /* Windows: dst[-1] cannot be backslash anymore */
+ while (dst0 < dst && dst[-1] != '/')
+ dst--;
+ }
*dst = '\0';
- return dst - buf;
+ return 0;
}
/*
return -1;
for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
- for (colon = ceil; *colon && *colon != ':'; colon++);
+ for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
len = colon - ceil;
if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
continue;
strlcpy(buf, ceil, len+1);
- len = normalize_absolute_path(buf, buf);
- /* Strip "trailing slashes" from "/". */
- if (len == 1)
- len = 0;
+ if (normalize_path_copy(buf, buf) < 0)
+ continue;
+ len = strlen(buf);
+ if (len > 0 && buf[len-1] == '/')
+ buf[--len] = '\0';
if (!strncmp(path, buf, len) &&
path[len] == '/' &&
return max_len;
}
+
+/* strip arbitrary amount of directory separators at end of path */
+static inline int chomp_trailing_dir_sep(const char *path, int len)
+{
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+ return len;
+}
+
+/*
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
+ */
+char *strip_path_suffix(const char *path, const char *suffix)
+{
+ int path_len = strlen(path), suffix_len = strlen(suffix);
+
+ while (suffix_len) {
+ if (!path_len)
+ return NULL;
+
+ if (is_dir_sep(path[path_len - 1])) {
+ if (!is_dir_sep(suffix[suffix_len - 1]))
+ return NULL;
+ path_len = chomp_trailing_dir_sep(path, path_len);
+ suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
+ }
+ else if (path[--path_len] != suffix[--suffix_len])
+ return NULL;
+ }
+
+ if (path_len && !is_dir_sep(path[path_len - 1]))
+ return NULL;
+ return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
+}
diff --git a/pretty.c b/pretty.c
index 421d9c5bca2224777808ccc06fe4912ba8793229..6cd91491d348b9aabea902c0ea60465074e9c06e 100644 (file)
--- a/pretty.c
+++ b/pretty.c
#include "string-list.h"
#include "mailmap.h"
#include "log-tree.h"
+#include "color.h"
static char *user_format;
while (parent) {
struct commit *p = parent->item;
const char *hex = NULL;
- const char *dots;
if (abbrev)
hex = find_unique_abbrev(p->object.sha1, abbrev);
if (!hex)
hex = sha1_to_hex(p->object.sha1);
- dots = (abbrev && strlen(hex) != 40) ? "..." : "";
parent = parent->next;
- strbuf_addf(sb, " %s%s", hex, dots);
+ strbuf_addf(sb, " %s", hex);
}
strbuf_addch(sb, '\n');
}
return out;
}
-static int mailmap_name(struct strbuf *sb, const char *email)
+static int mailmap_name(char *email, int email_len, char *name, int name_len)
{
static struct string_list *mail_map;
- char buffer[1024];
-
if (!mail_map) {
mail_map = xcalloc(1, sizeof(*mail_map));
- read_mailmap(mail_map, ".mailmap", NULL);
+ read_mailmap(mail_map, NULL);
}
-
- if (!mail_map->nr)
- return -1;
-
- if (!map_email(mail_map, email, buffer, sizeof(buffer)))
- return -1;
- strbuf_addstr(sb, buffer);
- return 0;
+ return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
}
static size_t format_person_part(struct strbuf *sb, char part,
int start, end, tz = 0;
unsigned long date = 0;
char *ep;
+ const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
+ char person_name[1024];
+ char person_mail[1024];
/* advance 'end' to point to email start delimiter */
for (end = 0; end < len && msg[end] != '<'; end++)
if (end >= len - 2)
goto skip;
+ /* Seek for both name and email part */
+ name_start = msg;
+ name_end = msg+end;
+ while (name_end > name_start && isspace(*(name_end-1)))
+ name_end--;
+ mail_start = msg+end+1;
+ mail_end = mail_start;
+ while (mail_end < msg_end && *mail_end != '>')
+ mail_end++;
+ if (mail_end == msg_end)
+ goto skip;
+ end = mail_end-msg;
+
+ if (part == 'N' || part == 'E') { /* mailmap lookup */
+ strlcpy(person_name, name_start, name_end-name_start+1);
+ strlcpy(person_mail, mail_start, mail_end-mail_start+1);
+ mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
+ name_start = person_name;
+ name_end = name_start + strlen(person_name);
+ mail_start = person_mail;
+ mail_end = mail_start + strlen(person_mail);
+ }
if (part == 'n' || part == 'N') { /* name */
- while (end > 0 && isspace(msg[end - 1]))
- end--;
- if (part != 'N' || !msg[end] || !msg[end + 1] ||
- mailmap_name(sb, msg + end + 2) < 0)
- strbuf_add(sb, msg, end);
+ strbuf_add(sb, name_start, name_end-name_start);
return placeholder_len;
}
- start = ++end; /* save email start position */
-
- /* advance 'end' to point to email end delimiter */
- for ( ; end < len && msg[end] != '>'; end++)
- ; /* do nothing */
-
- if (end >= len)
- goto skip;
-
- if (part == 'e') { /* email */
- strbuf_add(sb, msg + start, end - start);
+ if (part == 'e' || part == 'E') { /* email */
+ strbuf_add(sb, mail_start, mail_end-mail_start);
return placeholder_len;
}
/* these are independent of the commit */
switch (placeholder[0]) {
case 'C':
+ if (placeholder[1] == '(') {
+ const char *end = strchr(placeholder + 2, ')');
+ char color[COLOR_MAXLEN];
+ if (!end)
+ return 0;
+ color_parse_mem(placeholder + 2,
+ end - (placeholder + 2),
+ "--pretty format", color);
+ strbuf_addstr(sb, color);
+ return end - placeholder + 1;
+ }
if (!prefixcmp(placeholder + 1, "red")) {
strbuf_addstr(sb, "\033[31m");
return 4;
diff --git a/read-cache.c b/read-cache.c
index b1475ffa0962e1f0238fdb9a6870aa4b0bfd6d3b..940ec76fdf231ac1345079ca2dc5da88925bcfb6 100644 (file)
--- a/read-cache.c
+++ b/read-cache.c
default:
die("unexpected diff status %c", p->status);
case DIFF_STATUS_UNMERGED:
+ /*
+ * ADD_CACHE_IGNORE_REMOVAL is unset if "git
+ * add -u" is calling us, In such a case, a
+ * missing work tree file needs to be removed
+ * if there is an unmerged entry at stage #2,
+ * but such a diff record is followed by
+ * another with DIFF_STATUS_DELETED (and if
+ * there is no stage #2, we won't see DELETED
+ * nor MODIFIED). We can simply continue
+ * either way.
+ */
+ if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
+ continue;
+ /*
+ * Otherwise, it is "git add path" is asking
+ * to explicitly add it; we fall through. A
+ * missing work tree file is an error and is
+ * caught by add_file_to_index() in such a
+ * case.
+ */
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
if (add_file_to_index(&the_index, path, data->flags)) {
index 33ced65a7801f8653d608a9580e237ce8df39ae2..6eb5f5384611bb5d159d892a1bfd120d72e54b9b 100644 (file)
--- a/refs.c
+++ b/refs.c
list = get_ref_dir(ref, list);
continue;
}
- if (!resolve_ref(ref, sha1, 1, &flag)) {
- error("%s points nowhere!", ref);
- continue;
- }
+ if (!resolve_ref(ref, sha1, 1, &flag))
+ hashclr(sha1);
list = add_ref(ref, sha1, flag, list, NULL);
}
free(ref);
return sort_ref_list(list);
}
+struct warn_if_dangling_data {
+ const char *refname;
+ const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct warn_if_dangling_data *d = cb_data;
+ const char *resolves_to;
+ unsigned char junk[20];
+
+ if (!(flags & REF_ISSYMREF))
+ return 0;
+
+ resolves_to = resolve_ref(refname, junk, 0, NULL);
+ if (!resolves_to || strcmp(resolves_to, d->refname))
+ return 0;
+
+ printf(d->msg_fmt, refname);
+ return 0;
+}
+
+void warn_dangling_symref(const char *msg_fmt, const char *refname)
+{
+ struct warn_if_dangling_data data = { refname, msg_fmt };
+ for_each_rawref(warn_if_dangling_symref, &data);
+}
+
static struct ref_list *get_loose_refs(void)
{
if (!cached_refs.did_loose) {
return -1;
}
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
static int do_one_ref(const char *base, each_ref_fn fn, int trim,
- void *cb_data, struct ref_list *entry)
+ int flags, void *cb_data, struct ref_list *entry)
{
if (strncmp(base, entry->name, trim))
return 0;
- if (is_null_sha1(entry->sha1))
- return 0;
- if (!has_sha1_file(entry->sha1)) {
- error("%s does not point to a valid object!", entry->name);
- return 0;
+ if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (is_null_sha1(entry->sha1))
+ return 0;
+ if (!has_sha1_file(entry->sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
+ }
}
current_ref = entry;
return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
}
static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
- void *cb_data)
+ int flags, void *cb_data)
{
int retval = 0;
struct ref_list *packed = get_packed_refs();
struct ref_list *extra;
for (extra = extra_refs; extra; extra = extra->next)
- retval = do_one_ref(base, fn, trim, cb_data, extra);
+ retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
while (packed && loose) {
struct ref_list *entry;
entry = packed;
packed = packed->next;
}
- retval = do_one_ref(base, fn, trim, cb_data, entry);
+ retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
if (retval)
goto end_each;
}
for (packed = packed ? packed : loose; packed; packed = packed->next) {
- retval = do_one_ref(base, fn, trim, cb_data, packed);
+ retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
if (retval)
goto end_each;
}
int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/", fn, 0, cb_data);
+ return do_for_each_ref("refs/", fn, 0, 0, cb_data);
}
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/tags/", fn, 10, cb_data);
+ return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data);
}
int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/heads/", fn, 11, cb_data);
+ return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data);
}
int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
+ return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data);
+}
+
+int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref("refs/", fn, 0,
+ DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
/*
@@ -1453,7 +1489,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
return 1;
}
-int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data)
{
const char *logfile;
FILE *logfp;
@@ -1464,6 +1500,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
logfp = fopen(logfile, "r");
if (!logfp)
return -1;
+
+ if (ofs) {
+ struct stat statbuf;
+ if (fstat(fileno(logfp), &statbuf) ||
+ statbuf.st_size < ofs ||
+ fseek(logfp, -ofs, SEEK_END) ||
+ fgets(buf, sizeof(buf), logfp))
+ return -1;
+ }
+
while (fgets(buf, sizeof(buf), logfp)) {
unsigned char osha1[20], nsha1[20];
char *email_end, *message;
@@ -1497,6 +1543,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
return ret;
}
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+ return for_each_recent_reflog_ent(ref, fn, 0, cb_data);
+}
+
static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
{
DIR *dir = opendir(git_path("logs/%s", base));
index 06ad26055661a9b9e475d0f8a7bd6d1cfb42e792..29bdcecd4edb5e7281a4da36a06aa05e025f38a7 100644 (file)
--- a/refs.h
+++ b/refs.h
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
+/* can be used to learn about broken ref and symref */
+extern int for_each_rawref(each_ref_fn, void *);
+
+extern void warn_dangling_symref(const char *msg_fmt, const char *refname);
+
/*
* Extra refs will be listed by for_each_ref() before any actual refs
* for the duration of this process or until clear_extra_refs() is
/* iterate over reflog entries */
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data);
/*
* Calls the specified function for each reflog file until it returns nonzero,
diff --git a/remote.c b/remote.c
index 570e11286ea295e825a23b1d5ed40c9fbd02be57..d7079c6dd871dc1b482d347d013438fe30cc0908 100644 (file)
--- a/remote.c
+++ b/remote.c
#include "commit.h"
#include "diff.h"
#include "revision.h"
+#include "dir.h"
static struct refspec s_tag_refspec = {
0,
@@ -634,10 +635,7 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
static int valid_remote_nick(const char *name)
{
- if (!name[0] || /* not empty */
- (name[0] == '.' && /* not "." */
- (!name[1] || /* not ".." */
- (name[1] == '.' && !name[2]))))
+ if (!name[0] || is_dot_or_dotdot(name))
return 0;
return !strchr(name, '/'); /* no slash */
}
diff --git a/rerere.c b/rerere.c
index 718fb526dd1fbbc5de18784586d37d6069807b77..3518207c178904b91ce28f8d3bf2ba0ee560d0e7 100644 (file)
--- a/rerere.c
+++ b/rerere.c
hex = xstrdup(sha1_to_hex(sha1));
string_list_insert(path, rr)->util = hex;
if (mkdir(git_path("rr-cache/%s", hex), 0755))
- continue;;
+ continue;
handle_file(path, NULL, rr_path(hex, "preimage"));
fprintf(stderr, "Recorded preimage for '%s'\n", path);
}
diff --git a/revision.c b/revision.c
index c0df714f6cb0825196d1e79cbc53a042529e2d26..286e416b757fa8df731330992fca96773082f75d 100644 (file)
--- a/revision.c
+++ b/revision.c
@@ -183,8 +183,11 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
if (!tag->tagged)
die("bad tag");
object = parse_object(tag->tagged->sha1);
- if (!object)
+ if (!object) {
+ if (flags & UNINTERESTING)
+ return NULL;
die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+ }
}
/*
while (parent) {
struct commit *p = parent->item;
parent = parent->next;
+ if (p)
+ p->object.flags |= UNINTERESTING;
if (parse_commit(p) < 0)
- return -1;
- p->object.flags |= UNINTERESTING;
+ continue;
if (p->parents)
mark_parents_uninteresting(p);
if (p->object.flags & SEEN)
diff --git a/run-command.c b/run-command.c
index c90cdc50e3165bcdb798c85b2dc7b929a9b0a144..b05c734d05e99cd009a0df26f0fc95fa13ae6f25 100644 (file)
--- a/run-command.c
+++ b/run-command.c
} else {
execvp(cmd->argv[0], (char *const*) cmd->argv);
}
- die("exec %s failed.", cmd->argv[0]);
+ trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
+ strerror(errno));
+ exit(127);
}
#else
int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */
#endif
if (cmd->pid < 0) {
+ int err = errno;
if (need_in)
close_pair(fdin);
else if (cmd->in)
close(cmd->out);
if (need_err)
close_pair(fderr);
- return -ERR_RUN_COMMAND_FORK;
+ return err == ENOENT ?
+ -ERR_RUN_COMMAND_EXEC :
+ -ERR_RUN_COMMAND_FORK;
}
if (need_in)
if (!WIFEXITED(status))
return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
code = WEXITSTATUS(status);
- if (code)
+ switch (code) {
+ case 127:
+ return -ERR_RUN_COMMAND_EXEC;
+ case 0:
+ return 0;
+ default:
return -code;
- return 0;
+ }
}
}
#endif
return ret;
}
+
+int run_hook(const char *index_file, const char *name, ...)
+{
+ struct child_process hook;
+ const char **argv = NULL, *env[2];
+ char index[PATH_MAX];
+ va_list args;
+ int ret;
+ size_t i = 0, alloc = 0;
+
+ if (access(git_path("hooks/%s", name), X_OK) < 0)
+ return 0;
+
+ va_start(args, name);
+ ALLOC_GROW(argv, i + 1, alloc);
+ argv[i++] = git_path("hooks/%s", name);
+ while (argv[i-1]) {
+ ALLOC_GROW(argv, i + 1, alloc);
+ argv[i++] = va_arg(args, const char *);
+ }
+ va_end(args);
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ if (index_file) {
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ env[0] = index;
+ env[1] = NULL;
+ hook.env = env;
+ }
+
+ ret = start_command(&hook);
+ free(argv);
+ if (ret) {
+ warning("Could not spawn %s", argv[0]);
+ return ret;
+ }
+ ret = finish_command(&hook);
+ if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL)
+ warning("%s exited due to uncaught signal", argv[0]);
+
+ return ret;
+}
diff --git a/run-command.h b/run-command.h
index a8b0c209e9b7487ce3c2b214cbebe7ad4f97fd11..15e870a65eb037cd49d1e01251711915da06d260 100644 (file)
--- a/run-command.h
+++ b/run-command.h
ERR_RUN_COMMAND_WAITPID_SIGNAL,
ERR_RUN_COMMAND_WAITPID_NOEXIT,
};
+#define IS_RUN_COMMAND_ERR(x) ((x) <= -ERR_RUN_COMMAND_FORK)
struct child_process {
const char **argv;
int finish_command(struct child_process *);
int run_command(struct child_process *);
+extern int run_hook(const char *index_file, const char *name, ...);
+
#define RUN_COMMAND_NO_STDIN 1
#define RUN_GIT_CMD 2 /*If this is to be git sub-command */
#define RUN_COMMAND_STDOUT_TO_STDERR 4
index dfda532adc16f5e6d25d7cfc5add3e0e2b6a5209..6c2deda18492acb5a8597563d6843f9d0dd232c0 100644 (file)
--- a/setup.c
+++ b/setup.c
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-static int sanitary_path_copy(char *dst, const char *src)
-{
- char *dst0;
-
- if (has_dos_drive_prefix(src)) {
- *dst++ = *src++;
- *dst++ = *src++;
- }
- dst0 = dst;
-
- if (is_dir_sep(*src)) {
- *dst++ = '/';
- while (is_dir_sep(*src))
- src++;
- }
-
- for (;;) {
- char c = *src;
-
- /*
- * A path component that begins with . could be
- * special:
- * (1) "." and ends -- ignore and terminate.
- * (2) "./" -- ignore them, eat slash and continue.
- * (3) ".." and ends -- strip one and terminate.
- * (4) "../" -- strip one, eat slash and continue.
- */
- if (c == '.') {
- if (!src[1]) {
- /* (1) */
- src++;
- } else if (is_dir_sep(src[1])) {
- /* (2) */
- src += 2;
- while (is_dir_sep(*src))
- src++;
- continue;
- } else if (src[1] == '.') {
- if (!src[2]) {
- /* (3) */
- src += 2;
- goto up_one;
- } else if (is_dir_sep(src[2])) {
- /* (4) */
- src += 3;
- while (is_dir_sep(*src))
- src++;
- goto up_one;
- }
- }
- }
-
- /* copy up to the next '/', and eat all '/' */
- while ((c = *src++) != '\0' && !is_dir_sep(c))
- *dst++ = c;
- if (is_dir_sep(c)) {
- *dst++ = '/';
- while (is_dir_sep(c))
- c = *src++;
- src--;
- } else if (!c)
- break;
- continue;
-
- up_one:
- /*
- * dst0..dst is prefix portion, and dst[-1] is '/';
- * go up one level.
- */
- dst -= 2; /* go past trailing '/' if any */
- if (dst < dst0)
- return -1;
- while (1) {
- if (dst <= dst0)
- break;
- c = *dst--;
- if (c == '/') { /* MinGW: cannot be '\\' anymore */
- dst += 2;
- break;
- }
- }
- }
- *dst = '\0';
- return 0;
-}
-
const char *prefix_path(const char *prefix, int len, const char *path)
{
const char *orig = path;
memcpy(sanitized, prefix, len);
strcpy(sanitized + len, path);
}
- if (sanitary_path_copy(sanitized, sanitized))
+ if (normalize_path_copy(sanitized, sanitized))
goto error_out;
if (is_absolute_path(orig)) {
const char *work_tree = get_git_work_tree();
diff --git a/sha1_file.c b/sha1_file.c
index b63f842cbdaf01e8a3b0ba35cbfc1ad0cbac6913..032300c4c6434701e802df729cd74245e543de20 100644 (file)
--- a/sha1_file.c
+++ b/sha1_file.c
if (p->pack_fd == -1 && open_packed_git(p))
die("packfile %s cannot be accessed", p->pack_name);
- /* Since packfiles end in a hash of their content and its
+ /* Since packfiles end in a hash of their content and it's
* pointless to ask for an offset into the middle of that
* hash, and the in_window function above wouldn't match
* don't allow an offset too close to the end of the file.
delta_base_cache_lru.prev = &ent->lru;
}
+static void *read_object(const unsigned char *sha1, enum object_type *type,
+ unsigned long *size);
+
static void *unpack_delta_entry(struct packed_git *p,
struct pack_window **w_curs,
off_t curpos,
return 0;
}
-void *read_object(const unsigned char *sha1, enum object_type *type,
- unsigned long *size)
+static void *read_object(const unsigned char *sha1, enum object_type *type,
+ unsigned long *size)
{
unsigned long mapsize;
void *map, *buf;
diff --git a/sha1_name.c b/sha1_name.c
index 722fc35a6d98e1c260c6e14738ad3b2d41d924aa..2f75179f4c6c1d05bdd7594b23dcf77007c26751 100644 (file)
--- a/sha1_name.c
+++ b/sha1_name.c
return slash;
}
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is of the form @{-<n>}.
+ */
+static char *substitute_nth_last_branch(const char **string, int *len)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret = interpret_nth_last_branch(*string, &buf);
+
+ if (ret == *len) {
+ size_t size;
+ *string = strbuf_detach(&buf, &size);
+ *len = size;
+ return (char *)*string;
+ }
+
+ return NULL;
+}
+
int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
{
+ char *last_branch = substitute_nth_last_branch(&str, &len);
const char **p, *r;
int refs_found = 0;
char fullref[PATH_MAX];
unsigned char sha1_from_ref[20];
unsigned char *this_result;
+ int flag;
this_result = refs_found ? sha1_from_ref : sha1;
mksnpath(fullref, sizeof(fullref), *p, len, str);
- r = resolve_ref(fullref, this_result, 1, NULL);
+ r = resolve_ref(fullref, this_result, 1, &flag);
if (r) {
if (!refs_found++)
*ref = xstrdup(r);
if (!warn_ambiguous_refs)
break;
- }
+ } else if ((flag & REF_ISSYMREF) &&
+ (len != 4 || strcmp(str, "HEAD")))
+ warning("ignoring dangling symref %s.", fullref);
}
+ free(last_branch);
return refs_found;
}
int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
{
+ char *last_branch = substitute_nth_last_branch(&str, &len);
const char **p;
int logs_found = 0;
if (!warn_ambiguous_refs)
break;
}
+ free(last_branch);
return logs_found;
}
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* basic@{time or number} format to query ref-log */
+ /* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (len && str[len-1] == '}') {
- for (at = 0; at < len - 1; at++) {
+ for (at = len-2; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
reflog_len = (len-1) - (at+2);
len = at;
return -1;
if (!len && reflog_len) {
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+ /* try the @{-N} syntax for n-th checkout */
+ ret = interpret_nth_last_branch(str+at, &buf);
+ if (ret > 0) {
+ /* substitute this branch name and restart */
+ return get_sha1_1(buf.buf, buf.len, sha1);
+ } else if (ret == 0) {
+ return -1;
+ }
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
} else if (reflog_len)
return 0;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
static int get_parent(const char *name, int len,
unsigned char *result, int idx)
{
return retval;
}
+struct grab_nth_branch_switch_cbdata {
+ long cnt, alloc;
+ struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ const char *match = NULL, *target = NULL;
+ size_t len;
+ int nth;
+
+ if (!prefixcmp(message, "checkout: moving from ")) {
+ match = message + strlen("checkout: moving from ");
+ target = strstr(match, " to ");
+ }
+
+ if (!match || !target)
+ return 0;
+
+ len = target - match;
+ nth = cb->cnt++ % cb->alloc;
+ strbuf_reset(&cb->buf[nth]);
+ strbuf_add(&cb->buf[nth], match, len);
+ return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns the number of characters parsed if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_nth_last_branch(const char *name, struct strbuf *buf)
+{
+ long nth;
+ int i, retval;
+ struct grab_nth_branch_switch_cbdata cb;
+ const char *brace;
+ char *num_end;
+
+ if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+ return -1;
+ brace = strchr(name, '}');
+ if (!brace)
+ return -1;
+ nth = strtol(name+3, &num_end, 10);
+ if (num_end != brace)
+ return -1;
+ if (nth <= 0)
+ return -1;
+ cb.alloc = nth;
+ cb.buf = xmalloc(nth * sizeof(struct strbuf));
+ for (i = 0; i < nth; i++)
+ strbuf_init(&cb.buf[i], 20);
+ cb.cnt = 0;
+ retval = 0;
+ for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+ if (cb.cnt < nth) {
+ cb.cnt = 0;
+ for (i = 0; i < nth; i++)
+ strbuf_release(&cb.buf[i]);
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ }
+ if (cb.cnt < nth)
+ goto release_return;
+ i = cb.cnt % nth;
+ strbuf_reset(buf);
+ strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+ retval = brace-name+1;
+
+release_return:
+ for (i = 0; i < nth; i++)
+ strbuf_release(&cb.buf[i]);
+ free(cb.buf);
+
+ return retval;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
diff --git a/sigchain.c b/sigchain.c
--- /dev/null
+++ b/sigchain.c
@@ -0,0 +1,52 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define SIGCHAIN_MAX_SIGNALS 32
+
+struct sigchain_signal {
+ sigchain_fun *old;
+ int n;
+ int alloc;
+};
+static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];
+
+static void check_signum(int sig)
+{
+ if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
+ die("BUG: signal out of range: %d", sig);
+}
+
+int sigchain_push(int sig, sigchain_fun f)
+{
+ struct sigchain_signal *s = signals + sig;
+ check_signum(sig);
+
+ ALLOC_GROW(s->old, s->n + 1, s->alloc);
+ s->old[s->n] = signal(sig, f);
+ if (s->old[s->n] == SIG_ERR)
+ return -1;
+ s->n++;
+ return 0;
+}
+
+int sigchain_pop(int sig)
+{
+ struct sigchain_signal *s = signals + sig;
+ check_signum(sig);
+ if (s->n < 1)
+ return 0;
+
+ if (signal(sig, s->old[s->n - 1]) == SIG_ERR)
+ return -1;
+ s->n--;
+ return 0;
+}
+
+void sigchain_push_common(sigchain_fun f)
+{
+ sigchain_push(SIGINT, f);
+ sigchain_push(SIGHUP, f);
+ sigchain_push(SIGTERM, f);
+ sigchain_push(SIGQUIT, f);
+ sigchain_push(SIGPIPE, f);
+}
diff --git a/sigchain.h b/sigchain.h
--- /dev/null
+++ b/sigchain.h
@@ -0,0 +1,11 @@
+#ifndef SIGCHAIN_H
+#define SIGCHAIN_H
+
+typedef void (*sigchain_fun)(int);
+
+int sigchain_push(int sig, sigchain_fun f);
+int sigchain_pop(int sig);
+
+void sigchain_push_common(sigchain_fun f);
+
+#endif /* SIGCHAIN_H */
diff --git a/strbuf.c b/strbuf.c
index bdf49544d47b97475800b104ed9efe8f846db265..6ed06840b856a91f6d215c9a862e064f521384f0 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
{
size_t res;
+ size_t oldalloc = sb->alloc;
strbuf_grow(sb, size);
res = fread(sb->buf + sb->len, 1, size, f);
- if (res > 0) {
+ if (res > 0)
strbuf_setlen(sb, sb->len + res);
- }
+ else if (res < 0 && oldalloc == 0)
+ strbuf_release(sb);
return res;
}
ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
{
size_t oldlen = sb->len;
+ size_t oldalloc = sb->alloc;
strbuf_grow(sb, hint ? hint : 8192);
for (;;) {
cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
if (cnt < 0) {
- strbuf_setlen(sb, oldlen);
+ if (oldalloc == 0)
+ strbuf_release(sb);
+ else
+ strbuf_setlen(sb, oldlen);
return -1;
}
if (!cnt)
int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
{
+ size_t oldalloc = sb->alloc;
+
if (hint < 32)
hint = 32;
/* .. the buffer was too small - try again */
hint *= 2;
}
- strbuf_release(sb);
+ if (oldalloc == 0)
+ strbuf_release(sb);
return -1;
}
diff --git a/string-list.c b/string-list.c
index ddd83c8c76112cecd5d23668aaca467601855a72..15e14cf47a586613a62970b393c269728ffbef93 100644 (file)
--- a/string-list.c
+++ b/string-list.c
}
/* returns -1-index if already exists */
-static int add_entry(struct string_list *list, const char *string)
+static int add_entry(int insert_at, struct string_list *list, const char *string)
{
- int exact_match;
- int index = get_entry_index(list, string, &exact_match);
+ int exact_match = 0;
+ int index = insert_at != -1 ? insert_at : get_entry_index(list, string, &exact_match);
if (exact_match)
return -1 - index;
struct string_list_item *string_list_insert(const char *string, struct string_list *list)
{
- int index = add_entry(list, string);
+ return string_list_insert_at_index(-1, string, list);
+}
+
+struct string_list_item *string_list_insert_at_index(int insert_at,
+ const char *string, struct string_list *list)
+{
+ int index = add_entry(insert_at, list, string);
if (index < 0)
index = -1 - index;
return exact_match;
}
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+ int negative_existing_index)
+{
+ int exact_match;
+ int index = get_entry_index(list, string, &exact_match);
+ if (exact_match)
+ index = -1 - (negative_existing_index ? index : 0);
+ return index;
+}
+
struct string_list_item *string_list_lookup(const char *string, struct string_list *list)
{
int exact_match, i = get_entry_index(list, string, &exact_match);
list->nr = list->alloc = 0;
}
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc)
+{
+ if (list->items) {
+ int i;
+ if (clearfunc) {
+ for (i = 0; i < list->nr; i++)
+ clearfunc(list->items[i].util, list->items[i].string);
+ }
+ if (list->strdup_strings) {
+ for (i = 0; i < list->nr; i++)
+ free(list->items[i].string);
+ }
+ free(list->items);
+ }
+ list->items = NULL;
+ list->nr = list->alloc = 0;
+}
+
+
void print_string_list(const char *text, const struct string_list *p)
{
int i;
diff --git a/string-list.h b/string-list.h
index 4d6a7051fe5bccf04a0d0c32a90e5cf9c00dba3c..d32ba05202880733dd76f5465a0ae16753d1fba6 100644 (file)
--- a/string-list.h
+++ b/string-list.h
void print_string_list(const char *text, const struct string_list *p);
void string_list_clear(struct string_list *list, int free_util);
+/* Use this function to call a custom clear function on each util pointer */
+/* The string associated with the util pointer is passed as the second argument */
+typedef void (*string_list_clear_func_t)(void *p, const char *str);
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
+
/* Use these functions only on sorted lists: */
int string_list_has_string(const struct string_list *list, const char *string);
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+ int negative_existing_index);
struct string_list_item *string_list_insert(const char *string, struct string_list *list);
+struct string_list_item *string_list_insert_at_index(int insert_at,
+ const char *string, struct string_list *list);
struct string_list_item *string_list_lookup(const char *string, struct string_list *list);
/* Use these functions only on unsorted lists: */
diff --git a/symlinks.c b/symlinks.c
index 5a5e781a15d7d9cb60797958433eca896b31ec85..f262b7c44b387f4c60901124c30585b3c059fa73 100644 (file)
--- a/symlinks.c
+++ b/symlinks.c
#include "cache.h"
-struct pathname {
+static struct cache_def {
+ char path[PATH_MAX + 1];
int len;
- char path[PATH_MAX];
-};
+ int flags;
+ int track_flags;
+ int prefix_len_stat_func;
+} cache;
-/* Return matching pathname prefix length, or zero if not matching */
-static inline int match_pathname(int len, const char *name, struct pathname *match)
+/*
+ * Returns the length (on a path component basis) of the longest
+ * common prefix match of 'name' and the cached path string.
+ */
+static inline int longest_match_lstat_cache(int len, const char *name,
+ int *previous_slash)
{
- int match_len = match->len;
- return (len > match_len &&
- name[match_len] == '/' &&
- !memcmp(name, match->path, match_len)) ? match_len : 0;
+ int max_len, match_len = 0, match_len_prev = 0, i = 0;
+
+ max_len = len < cache.len ? len : cache.len;
+ while (i < max_len && name[i] == cache.path[i]) {
+ if (name[i] == '/') {
+ match_len_prev = match_len;
+ match_len = i;
+ }
+ i++;
+ }
+ /* Is the cached path string a substring of 'name'? */
+ if (i == cache.len && cache.len < len && name[cache.len] == '/') {
+ match_len_prev = match_len;
+ match_len = cache.len;
+ /* Is 'name' a substring of the cached path string? */
+ } else if ((i == len && len < cache.len && cache.path[len] == '/') ||
+ (i == len && len == cache.len)) {
+ match_len_prev = match_len;
+ match_len = len;
+ }
+ *previous_slash = match_len_prev;
+ return match_len;
}
-static inline void set_pathname(int len, const char *name, struct pathname *match)
+static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func)
{
- if (len < PATH_MAX) {
- match->len = len;
- memcpy(match->path, name, len);
- match->path[len] = 0;
- }
+ cache.path[0] = '\0';
+ cache.len = 0;
+ cache.flags = 0;
+ cache.track_flags = track_flags;
+ cache.prefix_len_stat_func = prefix_len_stat_func;
}
-int has_symlink_leading_path(int len, const char *name)
+#define FL_DIR (1 << 0)
+#define FL_NOENT (1 << 1)
+#define FL_SYMLINK (1 << 2)
+#define FL_LSTATERR (1 << 3)
+#define FL_ERR (1 << 4)
+#define FL_FULLPATH (1 << 5)
+
+/*
+ * Check if name 'name' of length 'len' has a symlink leading
+ * component, or if the directory exists and is real, or not.
+ *
+ * To speed up the check, some information is allowed to be cached.
+ * This can be indicated by the 'track_flags' argument, which also can
+ * be used to indicate that we should check the full path.
+ *
+ * The 'prefix_len_stat_func' parameter can be used to set the length
+ * of the prefix, where the cache should use the stat() function
+ * instead of the lstat() function to test each path component.
+ */
+static int lstat_cache(int len, const char *name,
+ int track_flags, int prefix_len_stat_func)
{
- static struct pathname link, nonlink;
- char path[PATH_MAX];
+ int match_len, last_slash, last_slash_dir, previous_slash;
+ int match_flags, ret_flags, save_flags, max_len, ret;
struct stat st;
- char *sp;
- int known_dir;
- /*
- * See if the last known symlink cache matches.
- */
- if (match_pathname(len, name, &link))
- return 1;
+ if (cache.track_flags != track_flags ||
+ cache.prefix_len_stat_func != prefix_len_stat_func) {
+ /*
+ * As a safeguard we clear the cache if the values of
+ * track_flags and/or prefix_len_stat_func does not
+ * match with the last supplied values.
+ */
+ reset_lstat_cache(track_flags, prefix_len_stat_func);
+ match_len = last_slash = 0;
+ } else {
+ /*
+ * Check to see if we have a match from the cache for
+ * the 2 "excluding" path types.
+ */
+ match_len = last_slash =
+ longest_match_lstat_cache(len, name, &previous_slash);
+ match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK);
+ if (match_flags && match_len == cache.len)
+ return match_flags;
+ /*
+ * If we now have match_len > 0, we would know that
+ * the matched part will always be a directory.
+ *
+ * Also, if we are tracking directories and 'name' is
+ * a substring of the cache on a path component basis,
+ * we can return immediately.
+ */
+ match_flags = track_flags & FL_DIR;
+ if (match_flags && len == match_len)
+ return match_flags;
+ }
/*
- * Get rid of the last known directory part
+ * Okay, no match from the cache so far, so now we have to
+ * check the rest of the path components.
*/
- known_dir = match_pathname(len, name, &nonlink);
-
- while ((sp = strchr(name + known_dir + 1, '/')) != NULL) {
- int thislen = sp - name ;
- memcpy(path, name, thislen);
- path[thislen] = 0;
-
- if (lstat(path, &st))
- return 0;
- if (S_ISDIR(st.st_mode)) {
- set_pathname(thislen, path, &nonlink);
- known_dir = thislen;
+ ret_flags = FL_DIR;
+ last_slash_dir = last_slash;
+ max_len = len < PATH_MAX ? len : PATH_MAX;
+ while (match_len < max_len) {
+ do {
+ cache.path[match_len] = name[match_len];
+ match_len++;
+ } while (match_len < max_len && name[match_len] != '/');
+ if (match_len >= max_len && !(track_flags & FL_FULLPATH))
+ break;
+ last_slash = match_len;
+ cache.path[last_slash] = '\0';
+
+ if (last_slash <= prefix_len_stat_func)
+ ret = stat(cache.path, &st);
+ else
+ ret = lstat(cache.path, &st);
+
+ if (ret) {
+ ret_flags = FL_LSTATERR;
+ if (errno == ENOENT)
+ ret_flags |= FL_NOENT;
+ } else if (S_ISDIR(st.st_mode)) {
+ last_slash_dir = last_slash;
continue;
- }
- if (S_ISLNK(st.st_mode)) {
- set_pathname(thislen, path, &link);
- return 1;
+ } else if (S_ISLNK(st.st_mode)) {
+ ret_flags = FL_SYMLINK;
+ } else {
+ ret_flags = FL_ERR;
}
break;
}
- return 0;
+
+ /*
+ * At the end update the cache. Note that max 3 different
+ * path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached
+ * for the moment!
+ */
+ save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);
+ if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) {
+ cache.path[last_slash] = '\0';
+ cache.len = last_slash;
+ cache.flags = save_flags;
+ } else if (track_flags & FL_DIR &&
+ last_slash_dir > 0 && last_slash_dir <= PATH_MAX) {
+ /*
+ * We have a separate test for the directory case,
+ * since it could be that we have found a symlink or a
+ * non-existing directory and the track_flags says
+ * that we cannot cache this fact, so the cache would
+ * then have been left empty in this case.
+ *
+ * But if we are allowed to track real directories, we
+ * can still cache the path components before the last
+ * one (the found symlink or non-existing component).
+ */
+ cache.path[last_slash_dir] = '\0';
+ cache.len = last_slash_dir;
+ cache.flags = FL_DIR;
+ } else {
+ reset_lstat_cache(track_flags, prefix_len_stat_func);
+ }
+ return ret_flags;
+}
+
+/*
+ * Invalidate the given 'name' from the cache, if 'name' matches
+ * completely with the cache.
+ */
+void invalidate_lstat_cache(int len, const char *name)
+{
+ int match_len, previous_slash;
+
+ match_len = longest_match_lstat_cache(len, name, &previous_slash);
+ if (len == match_len) {
+ if ((cache.track_flags & FL_DIR) && previous_slash > 0) {
+ cache.path[previous_slash] = '\0';
+ cache.len = previous_slash;
+ cache.flags = FL_DIR;
+ } else
+ reset_lstat_cache(cache.track_flags,
+ cache.prefix_len_stat_func);
+ }
+}
+
+/*
+ * Completely clear the contents of the cache
+ */
+void clear_lstat_cache(void)
+{
+ reset_lstat_cache(0, 0);
+}
+
+#define USE_ONLY_LSTAT 0
+
+/*
+ * Return non-zero if path 'name' has a leading symlink component
+ */
+int has_symlink_leading_path(int len, const char *name)
+{
+ return lstat_cache(len, name,
+ FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) &
+ FL_SYMLINK;
+}
+
+/*
+ * Return non-zero if path 'name' has a leading symlink component or
+ * if some leading path component does not exists.
+ */
+int has_symlink_or_noent_leading_path(int len, const char *name)
+{
+ return lstat_cache(len, name,
+ FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &
+ (FL_SYMLINK|FL_NOENT);
+}
+
+/*
+ * Return non-zero if all path components of 'name' exists as a
+ * directory. If prefix_len > 0, we will test with the stat()
+ * function instead of the lstat() function for a prefix length of
+ * 'prefix_len', thus we then allow for symlinks in the prefix part as
+ * long as those points to real existing directories.
+ */
+int has_dirs_only_path(int len, const char *name, int prefix_len)
+{
+ return lstat_cache(len, name,
+ FL_DIR|FL_FULLPATH, prefix_len) &
+ FL_DIR;
}
diff --git a/t/README b/t/README
index 8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f..f208cf1db972d580d977c356bb589cf6147247a7 100644 (file)
--- a/t/README
+++ b/t/README
is to summarize successes and failures in the test script and
exit with an appropriate error code.
+ - test_tick
+
+ Make commit and tag names consistent by setting the author and
+ committer times to defined stated. Subsequent calls will
+ advance the times by a fixed amount.
+
+ - test_commit <message> [<filename> [<contents>]]
+
+ Creates a commit with the given message, committing the given
+ file with the given contents (default for both is to reuse the
+ message string), and adds a tag (again reusing the message
+ string as name). Calls test_tick to make the SHA-1s
+ reproducible.
+
+ - test_merge <message> <commit-or-tag>
+
+ Merges the given rev using the given message. Like test_commit,
+ creates a tag and calls test_tick before committing.
Tips for Writing Tests
----------------------
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index 6ac312b9059394b44cd6e106f9da6394674ee54a..86cdebc727c4964899a71366151a7782558153c7 100644 (file)
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
exit
fi
-LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'}
+HTTPD_PARA=""
+
+case $(uname) in
+ Darwin)
+ DEFAULT_HTTPD_PATH='/usr/sbin/httpd'
+ DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2'
+ HTTPD_PARA="$HTTPD_PARA -DDarwin"
+ ;;
+ *)
+ DEFAULT_HTTPD_PATH='/usr/sbin/apache2'
+ DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+ ;;
+esac
+
+LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"}
LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'}
TEST_PATH="$TEST_DIRECTORY"/lib-httpd
if ! test -x "$LIB_HTTPD_PATH"
then
- say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
- test_done
- exit
+ say "skipping test, no web server found at '$LIB_HTTPD_PATH'"
+ test_done
+ exit
fi
HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \
exit
fi
- LIB_HTTPD_MODULE_PATH='/usr/lib/apache2/modules'
+ LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH"
fi
else
error "Could not identify web server at '$LIB_HTTPD_PATH'"
fi
-HTTPD_PARA=""
-
prepare_httpd() {
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
start_httpd() {
prepare_httpd
- trap 'stop_httpd; die' exit
+ trap 'stop_httpd; die' EXIT
"$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
-f "$TEST_PATH/apache.conf" $HTTPD_PARA \
}
stop_httpd() {
- trap 'die' exit
+ trap 'die' EXIT
"$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
- -f "$TEST_PATH/apache.conf" -k stop
+ -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
}
index fdb19a50f11c8c71c9f7addcceeab8847558cc49..af6e5e1d6ae503dfadc94feef4d9cb4b623163ec 100644 (file)
--- a/t/lib-httpd/apache.conf
+++ b/t/lib-httpd/apache.conf
CustomLog access.log common
ErrorLog error.log
+<IfDefine Darwin>
+ LoadModule log_config_module modules/mod_log_config.so
+ LockFile accept.lock
+ PidFile httpd.pid
+</IfDefine>
+
<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
--- /dev/null
+++ b/t/lib-rebase.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# After setting the fake editor with this function, you can
+#
+# - override the commit message with $FAKE_COMMIT_MESSAGE,
+# - amend the commit message with $FAKE_COMMIT_AMEND
+# - check that non-commit messages have a certain line count with $EXPECT_COUNT
+# - rewrite a rebase -i script with $FAKE_LINES in the form
+#
+# "[<lineno1>] [<lineno2>]..."
+#
+# If a line number is prefixed with "squash" or "edit", the respective line's
+# command will be replaced with the specified one.
+
+set_fake_editor () {
+ echo "#!$SHELL_PATH" >fake-editor.sh
+ cat >> fake-editor.sh <<\EOF
+case "$1" in
+*/COMMIT_EDITMSG)
+ test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
+ test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
+ exit
+ ;;
+esac
+test -z "$EXPECT_COUNT" ||
+ test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
+ exit
+test -z "$FAKE_LINES" && exit
+grep -v '^#' < "$1" > "$1".tmp
+rm -f "$1"
+cat "$1".tmp
+action=pick
+for line in $FAKE_LINES; do
+ case $line in
+ squash|edit)
+ action="$line";;
+ *)
+ echo sed -n "${line}s/^pick/$action/p"
+ sed -n "${line}p" < "$1".tmp
+ sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
+ action=pick;;
+ esac
+done
+EOF
+
+ test_set_editor "$(pwd)/fake-editor.sh"
+ chmod a+x fake-editor.sh
+}
diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh
--- /dev/null
+++ b/t/t0005-signals.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='signals work as we expect'
+. ./test-lib.sh
+
+cat >expect <<EOF
+three
+two
+one
+EOF
+
+test_expect_success 'sigchain works' '
+ test-sigchain >actual
+ case "$?" in
+ 143) true ;; # POSIX w/ SIGTERM=15
+ 3) true ;; # Windows
+ *) false ;;
+ esac &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 6e7501f352ee97636280357da54c50d73ceb0138..8336114f9820aa5972d38efe316de7d9e39bcc5c 100755 (executable)
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
. ./test-lib.sh
norm_abs() {
- test_expect_success "normalize absolute" \
- "test \$(test-path-utils normalize_absolute_path '$1') = '$2'"
+ test_expect_success "normalize absolute: $1 => $2" \
+ "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'"
}
ancestor() {
- test_expect_success "longest ancestor" \
- "test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'"
+ test_expect_success "longest ancestor: $1 $2 => $3" \
+ "test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'"
}
-norm_abs "" /
+norm_abs "" ""
norm_abs / /
norm_abs // /
norm_abs /// /
norm_abs /. /
norm_abs /./ /
-norm_abs /./.. /
-norm_abs /../. /
-norm_abs /./../.// /
+norm_abs /./.. ++failed++
+norm_abs /../. ++failed++
+norm_abs /./../.// ++failed++
norm_abs /dir/.. /
norm_abs /dir/sub/../.. /
+norm_abs /dir/sub/../../.. ++failed++
norm_abs /dir /dir
-norm_abs /dir// /dir
+norm_abs /dir// /dir/
norm_abs /./dir /dir
-norm_abs /dir/. /dir
-norm_abs /dir///./ /dir
-norm_abs /dir//sub/.. /dir
-norm_abs /dir/sub/../ /dir
-norm_abs //dir/sub/../. /dir
-norm_abs /dir/s1/../s2/ /dir/s2
-norm_abs /d1/s1///s2/..//../s3/ /d1/s3
+norm_abs /dir/. /dir/
+norm_abs /dir///./ /dir/
+norm_abs /dir//sub/.. /dir/
+norm_abs /dir/sub/../ /dir/
+norm_abs //dir/sub/../. /dir/
+norm_abs /dir/s1/../s2/ /dir/s2/
+norm_abs /d1/s1///s2/..//../s3/ /d1/s3/
norm_abs /d1/s1//../s2/../../d2 /d2
norm_abs /d1/.../d2 /d1/.../d2
norm_abs /d1/..././../d2 /d1/d2
ancestor /foo/bar //foo/./::/bar 4
ancestor /foo/bar ::/bar -1
+test_expect_success 'strip_path_suffix' '
+ test c:/msysgit = $(test-path-utils strip_path_suffix \
+ c:/msysgit/libexec//git-core libexec/git-core)
+'
test_done
diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh
--- /dev/null
+++ b/t/t0070-fundamental.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+test_description='check that the most basic functions work
+
+
+Verify wrappers and compatibility functions.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'character classes (isspace, isalpha etc.)' '
+ test-ctype
+'
+
+test_done
diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh
--- /dev/null
+++ b/t/t0100-previous.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='previous branch syntax @{-n}'
+
+. ./test-lib.sh
+
+test_expect_success 'branch -d @{-1}' '
+ test_commit A &&
+ git checkout -b junk &&
+ git checkout - &&
+ test "$(git symbolic-ref HEAD)" = refs/heads/master &&
+ git branch -d @{-1} &&
+ test_must_fail git rev-parse --verify refs/heads/junk
+'
+
+test_expect_success 'branch -d @{-12} when there is not enough switches yet' '
+ git reflog expire --expire=now &&
+ git checkout -b junk2 &&
+ git checkout - &&
+ test "$(git symbolic-ref HEAD)" = refs/heads/master &&
+ test_must_fail git branch -d @{-12} &&
+ git rev-parse --verify refs/heads/master
+'
+
+test_expect_success 'merge @{-1}' '
+ git checkout A &&
+ test_commit B &&
+ git checkout A &&
+ test_commit C &&
+ git branch -f master B &&
+ git branch -f other &&
+ git checkout other &&
+ git checkout master &&
+ git merge @{-1} &&
+ git cat-file commit HEAD | grep "Merge branch '\''other'\''"
+'
+
+test_expect_success 'merge @{-1} when there is not enough switches yet' '
+ git reflog expire --expire=now &&
+ git checkout -f master &&
+ git reset --hard B &&
+ git branch -f other C &&
+ git checkout other &&
+ git checkout master &&
+ test_must_fail git merge @{-12}
+'
+
+test_done
+
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
--- /dev/null
+++ b/t/t1401-symbolic-ref.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='basic symbolic-ref tests'
+. ./test-lib.sh
+
+# If the tests munging HEAD fail, they can break detection of
+# the git repo, meaning that further tests will operate on
+# the surrounding git repo instead of the trash directory.
+reset_to_sane() {
+ echo ref: refs/heads/foo >.git/HEAD
+}
+
+test_expect_success 'symbolic-ref writes HEAD' '
+ git symbolic-ref HEAD refs/heads/foo &&
+ echo ref: refs/heads/foo >expect &&
+ test_cmp expect .git/HEAD
+'
+
+test_expect_success 'symbolic-ref reads HEAD' '
+ echo refs/heads/foo >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'symbolic-ref refuses non-ref for HEAD' '
+ test_must_fail git symbolic-ref HEAD foo
+'
+reset_to_sane
+
+test_expect_success 'symbolic-ref refuses bare sha1' '
+ echo content >file && git add file && git commit -m one
+ test_must_fail git symbolic-ref HEAD `git rev-parse HEAD`
+'
+reset_to_sane
+
+test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
--- /dev/null
+++ b/t/t1450-fsck.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='git fsck random collection of tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_commit A fileA one &&
+ git checkout HEAD^0 &&
+ test_commit B fileB two &&
+ git tag -d A B &&
+ git reflog expire --expire=now --all
+'
+
+test_expect_success 'HEAD is part of refs' '
+ test 0 = $(git fsck | wc -l)
+'
+
+test_expect_success 'loose objects borrowed from alternate are not missing' '
+ mkdir another &&
+ (
+ cd another &&
+ git init &&
+ echo ../../../.git/objects >.git/objects/info/alternates &&
+ test_commit C fileC one &&
+ git fsck >out &&
+ ! grep "missing blob" out
+ )
+'
+
+test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 85da4caa7ed1b8bcaca7b21e218f2d1839d2db82..48ee07779d64147c3eb8325a3b3f9579f8ec41d8 100755 (executable)
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
"test '$1' = \"\$(git rev-parse --show-prefix)\""
shift
[ $# -eq 0 ] && return
+
+ test_expect_success "$name: git-dir" \
+ "test '$1' = \"\$(git rev-parse --git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
}
-# label is-bare is-inside-git is-inside-work prefix
+# label is-bare is-inside-git is-inside-work prefix git-dir
+
+ROOT=$(pwd)
-test_rev_parse toplevel false false true ''
+test_rev_parse toplevel false false true '' .git
cd .git || exit 1
-test_rev_parse .git/ false true false ''
+test_rev_parse .git/ false true false '' .
cd objects || exit 1
-test_rev_parse .git/objects/ false true false ''
+test_rev_parse .git/objects/ false true false '' "$ROOT/.git"
cd ../.. || exit 1
mkdir -p sub/dir || exit 1
cd sub/dir || exit 1
-test_rev_parse subdirectory false false true sub/dir/
+test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git"
cd ../.. || exit 1
git config core.bare true
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index 27dc6c55d5f50a7fd30388b60230482bad6be2d8..f6a6f839a18de4c3775ea965f164d0d20f2bbe9b 100755 (executable)
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
cd ../../../.. || exit 1
-test_expect_success 'detecting gitdir when cwd is in a subdir of gitdir' '
- (expected=$(pwd)/repo.git &&
- cd repo.git/refs &&
- unset GIT_DIR &&
- test "$expected" = "$(git rev-parse --git-dir)")
-'
-
test_expect_success 'repo finds its work tree' '
(cd repo.git &&
: > work/sub/dir/untracked &&
index 91b704a3a4ce6771071d19bd84aa228856fe6875..e377d48902cd5fd539a0ce4cb1fb32ceb8732632 100755 (executable)
--- a/t/t1504-ceiling-dirs.sh
+++ b/t/t1504-ceiling-dirs.sh
test_prefix subdir_ceil_at_subdi_slash "sub/dir/"
-GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub"
+GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub"
test_fail second_of_two
-GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar"
+GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:/bar"
test_fail first_of_two
-GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar"
+GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub:/bar"
test_fail second_of_three
diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+test_description='test @{-N} syntax'
+
+. ./test-lib.sh
+
+
+make_commit () {
+ echo "$1" > "$1" &&
+ git add "$1" &&
+ git commit -m "$1"
+}
+
+
+test_expect_success 'setup' '
+
+ make_commit 1 &&
+ git branch side &&
+ make_commit 2 &&
+ make_commit 3 &&
+ git checkout side &&
+ make_commit 4 &&
+ git merge master &&
+ git checkout master
+
+'
+
+# 1 -- 2 -- 3 master
+# \ \
+# \ \
+# --- 4 --- 5 side
+#
+# and 'side' should be the last branch
+
+test_rev_equivalent () {
+
+ git rev-parse "$1" > expect &&
+ git rev-parse "$2" > output &&
+ test_cmp expect output
+
+}
+
+test_expect_success '@{-1} works' '
+ test_rev_equivalent side @{-1}
+'
+
+test_expect_success '@{-1}~2 works' '
+ test_rev_equivalent side~2 @{-1}~2
+'
+
+test_expect_success '@{-1}^2 works' '
+ test_rev_equivalent side^2 @{-1}^2
+'
+
+test_expect_success '@{-1}@{1} works' '
+ test_rev_equivalent side@{1} @{-1}@{1}
+'
+
+test_expect_success '@{-2} works' '
+ test_rev_equivalent master @{-2}
+'
+
+test_expect_success '@{-3} fails' '
+ test_must_fail git rev-parse @{-3}
+'
+
+test_done
+
+
diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
--- /dev/null
+++ b/t/t2012-checkout-last.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+test_description='checkout can switch to last branch'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo hello >world &&
+ git add world &&
+ git commit -m initial &&
+ git branch other &&
+ echo "hello again" >>world &&
+ git add world &&
+ git commit -m second
+'
+
+test_expect_success '"checkout -" does not work initially' '
+ test_must_fail git checkout -
+'
+
+test_expect_success 'first branch switch' '
+ git checkout other
+'
+
+test_expect_success '"checkout -" switches back' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master"
+'
+
+test_expect_success '"checkout -" switches forth' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success 'detach HEAD' '
+ git checkout $(git rev-parse HEAD)
+'
+
+test_expect_success '"checkout -" attaches again' '
+ git checkout - &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other"
+'
+
+test_expect_success '"checkout -" detaches again' '
+ git checkout - &&
+ test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+ test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'more switches' '
+ for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+ do
+ git checkout -b branch$i
+ done
+'
+
+more_switches () {
+ for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+ do
+ git checkout branch$i
+ done
+}
+
+test_expect_success 'switch to the last' '
+ more_switches &&
+ git checkout @{-1} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2"
+'
+
+test_expect_success 'switch to second from the last' '
+ more_switches &&
+ git checkout @{-2} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3"
+'
+
+test_expect_success 'switch to third from the last' '
+ more_switches &&
+ git checkout @{-3} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4"
+'
+
+test_expect_success 'switch to fourth from the last' '
+ more_switches &&
+ git checkout @{-4} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5"
+'
+
+test_expect_success 'switch to twelfth from the last' '
+ more_switches &&
+ git checkout @{-12} &&
+ test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13"
+'
+
+test_done
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index cd9231cf614c4326518632e514ccc68a5dc59223..b2ddf5ace3581bc2a7056c61b9d43499b2657b65 100755 (executable)
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
only the updates to dir/sub.
Also tested are "git add -u" without limiting, and "git add -u"
-without contents changes.'
+without contents changes, and other conditions'
. ./test-lib.sh
'
+test_expect_success 'add -u resolves unmerged paths' '
+ git reset --hard &&
+ one=$(echo 1 | git hash-object -w --stdin) &&
+ two=$(echo 2 | git hash-object -w --stdin) &&
+ three=$(echo 3 | git hash-object -w --stdin) &&
+ {
+ for path in path1 path2
+ do
+ echo "100644 $one 1 $path"
+ echo "100644 $two 2 $path"
+ echo "100644 $three 3 $path"
+ done
+ echo "100644 $one 1 path3"
+ echo "100644 $one 1 path4"
+ echo "100644 $one 3 path5"
+ echo "100644 $one 3 path6"
+ } |
+ git update-index --index-info &&
+ echo 3 >path1 &&
+ echo 2 >path3 &&
+ echo 2 >path5 &&
+ git add -u &&
+ git ls-files -s "path?" >actual &&
+ {
+ echo "100644 $three 0 path1"
+ echo "100644 $one 1 path3"
+ echo "100644 $one 1 path4"
+ echo "100644 $one 3 path5"
+ echo "100644 $one 3 path6"
+ } >expect &&
+ test_cmp expect actual &&
+
+ # Bonus tests. Explicit resolving
+ git add path3 path5 &&
+ test_must_fail git add path4 &&
+ test_must_fail git add path6 &&
+ git rm path4 &&
+ git rm path6 &&
+
+ git ls-files -s "path?" >actual &&
+ {
+ echo "100644 $three 0 path1"
+ echo "100644 $two 0 path3"
+ echo "100644 $two 0 path5"
+ } >expect
+
+'
+
test_done
index e42cbfe6c61951c6887a363cb668d26a7adcf20c..293dc353b1601e137f86dfe441fff57b96c92753 100755 (executable)
cd '"'$1'"' &&
. git-sh-setup &&
cd_to_toplevel &&
- [ "$(unset PWD; /bin/pwd)" = "$TOPLEVEL" ]
+ [ "$(pwd -P)" = "$TOPLEVEL" ]
)
'
}
-TOPLEVEL="$(unset PWD; /bin/pwd)/repo"
+TOPLEVEL="$(pwd -P)/repo"
mkdir -p repo/sub/dir
mv .git repo/
SUBDIRECTORY_OK=1
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index b7a670ef401429a50eb97e5f874c9e2c3897fd7d..8c0c5f59827544b47cc7a75f0c3f39cfcecf0a7e 100755 (executable)
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
test_expect_success \
'prepare repository with topic branches' \
- 'echo First > A &&
+ 'git config core.logAllRefUpdates true &&
+ echo First > A &&
git update-index --add A &&
git commit -m "Add A." &&
git checkout -b my-topic-branch &&
GIT_TRACE=1 git rebase master
'
+test_expect_success 'HEAD was detached during rebase' '
+ test $(git rev-parse HEAD@{1}) != $(git rev-parse modechange@{1})
+'
+
+test_expect_success 'Show verbose error when HEAD could not be detached' '
+ : > B &&
+ test_must_fail git rebase topic 2> output.err > output.out &&
+ grep "Untracked working tree file .B. would be overwritten" output.err
+'
+
test_done
index 2cc8e7abe1b9244b2d6520cdbb0e0769e2a6d986..603b003edff6d32fe8725f119778658c76c806fb 100755 (executable)
'
. ./test-lib.sh
+. ../lib-rebase.sh
+
+set_fake_editor
+
# set up two branches like this:
#
# A - B - C - D - E
git tag I
'
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
- test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
- test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
- exit
- ;;
-esac
-test -z "$EXPECT_COUNT" ||
- test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
- exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
- case $line in
- squash|edit)
- action="$line";;
- *)
- echo sed -n "${line}s/^pick/$action/p"
- sed -n "${line}p" < "$1".tmp
- sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
- action=pick;;
- esac
-done
-EOF
-
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
-
test_expect_success 'no changes are a nop' '
git rebase -i F &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
'
+test_expect_success 'submodule rebase setup' '
+ git checkout A &&
+ mkdir sub &&
+ (
+ cd sub && git init && >elif &&
+ git add elif && git commit -m "submodule initial"
+ ) &&
+ echo 1 >file1 &&
+ git add file1 sub
+ test_tick &&
+ git commit -m "One" &&
+ echo 2 >file1 &&
+ test_tick &&
+ git commit -a -m "Two" &&
+ (
+ cd sub && echo 3 >elif &&
+ git commit -a -m "submodule second"
+ ) &&
+ test_tick &&
+ git commit -a -m "Three changes submodule"
+'
+
+test_expect_success 'submodule rebase -i' '
+ FAKE_LINES="1 squash 2 3" git rebase -i A
+'
+
test_done
diff --git a/t/t3409-rebase-hook.sh b/t/t3409-rebase-hook.sh
index 1f1b85067773e40262c2ace48a67b60179663931..098b75507bf2ef6906f3f4fd4c532bb4c55ce7bd 100755 (executable)
--- a/t/t3409-rebase-hook.sh
+++ b/t/t3409-rebase-hook.sh
test_expect_success 'pre-rebase hook stops rebase (2)' '
git checkout test &&
git reset --hard side &&
- EDITOR=true test_must_fail git rebase -i master &&
+ (
+ EDITOR=:
+ export EDITOR
+ test_must_fail git rebase -i master
+ ) &&
test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
test 0 = $(git rev-list HEAD...side | wc -l)
'
index 5816415aafe02f03ede5e1afb6f2e92dedc4b3da..c49143a1a45d6949253e2daf0e57f60029041e60 100755 (executable)
# where B, D and G touch the same file.
test_expect_success 'setup' '
- : > file1 &&
- git add file1 &&
- test_tick &&
- git commit -m A &&
- git tag A &&
- echo 1 > file1 &&
- test_tick &&
- git commit -m B file1 &&
- : > file2 &&
- git add file2 &&
- test_tick &&
- git commit -m C &&
- echo 2 > file1 &&
- test_tick &&
- git commit -m D file1 &&
- : > file3 &&
- git add file3 &&
- test_tick &&
- git commit -m E &&
- git tag E &&
- git checkout -b branch1 A &&
- : > file4 &&
- git add file4 &&
- test_tick &&
- git commit -m F &&
- git tag F &&
- echo 3 > file1 &&
- test_tick &&
- git commit -m G file1 &&
- git tag G &&
- : > file5 &&
- git add file5 &&
- test_tick &&
- git commit -m H &&
- git tag H &&
- git checkout -b branch2 F &&
- : > file6 &&
- git add file6 &&
- test_tick &&
- git commit -m I &&
- git tag I
+ test_commit A file1 &&
+ test_commit B file1 1 &&
+ test_commit C file2 &&
+ test_commit D file1 2 &&
+ test_commit E file3 &&
+ git checkout A &&
+ test_commit F file4 &&
+ test_commit G file1 3 &&
+ test_commit H file5 &&
+ git checkout F &&
+ test_commit I file6
'
# A - B - C - D - E
# I -- G2 -- J -- K I -- K
# G2 = same changes as G
test_expect_success 'skip same-resolution merges with -p' '
- git checkout branch1 &&
+ git checkout H &&
! git merge E &&
- echo 23 > file1 &&
- git add file1 &&
- git commit -m L &&
- git checkout branch2 &&
- echo 3 > file1 &&
- git commit -a -m G2 &&
+ test_commit L file1 23 &&
+ git checkout I &&
+ test_commit G2 file1 3 &&
! git merge E &&
- echo 23 > file1 &&
- git add file1 &&
- git commit -m J &&
- echo file7 > file7 &&
- git add file7 &&
- git commit -m K &&
- GIT_EDITOR=: git rebase -i -p branch1 &&
- test $(git rev-parse branch2^^) = $(git rev-parse branch1) &&
+ test_commit J file1 23 &&
+ test_commit K file7 file7 &&
+ git rebase -i -p L &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
test "23" = "$(cat file1)" &&
- test "" = "$(cat file6)" &&
- test "file7" = "$(cat file7)" &&
-
- git checkout branch1 &&
- git reset --hard H &&
- git checkout branch2 &&
- git reset --hard I
+ test "I" = "$(cat file6)" &&
+ test "file7" = "$(cat file7)"
'
# A - B - C - D - E
# \ \ \
-# F - G - H -- L \ --> L
-# \ | \
-# I -- G2 -- J -- K I -- G2 -- K
+# F - G - H -- L2 \ --> L2
+# \ | \
+# I -- G3 --- J2 -- K2 I -- G3 -- K2
# G2 = different changes as G
test_expect_success 'keep different-resolution merges with -p' '
- git checkout branch1 &&
+ git checkout H &&
! git merge E &&
- echo 23 > file1 &&
- git add file1 &&
- git commit -m L &&
- git checkout branch2 &&
- echo 4 > file1 &&
- git commit -a -m G2 &&
+ test_commit L2 file1 23 &&
+ git checkout I &&
+ test_commit G3 file1 4 &&
! git merge E &&
- echo 24 > file1 &&
- git add file1 &&
- git commit -m J &&
- echo file7 > file7 &&
- git add file7 &&
- git commit -m K &&
- ! GIT_EDITOR=: git rebase -i -p branch1 &&
+ test_commit J2 file1 24 &&
+ test_commit K2 file7 file7 &&
+ test_must_fail git rebase -i -p L2 &&
echo 234 > file1 &&
git add file1 &&
- GIT_EDITOR=: git rebase --continue &&
- test $(git rev-parse branch2^^^) = $(git rev-parse branch1) &&
+ git rebase --continue &&
+ test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
test "234" = "$(cat file1)" &&
- test "" = "$(cat file6)" &&
- test "file7" = "$(cat file7)" &&
-
- git checkout branch1 &&
- git reset --hard H &&
- git checkout branch2 &&
- git reset --hard I
+ test "I" = "$(cat file6)" &&
+ test "file7" = "$(cat file7)"
'
test_done
index aacfaae843395e383d999e570a6bc9942e4c1c0b..6533505218a51db369d03357603c2ad1926ce448 100755 (executable)
test_description='git rebase preserve merges
-This test runs git rebase with and tries to squash a commit from after a merge
-to before the merge.
+This test runs git rebase with -p and tries to squash a commit from after
+a merge to before the merge.
'
. ./test-lib.sh
-# Copy/paste from t3404-rebase-interactive.sh
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
- test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
- test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
- exit
- ;;
-esac
-test -z "$EXPECT_COUNT" ||
- test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
- exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
- case $line in
- squash|edit)
- action="$line";;
- *)
- echo sed -n "${line}s/^pick/$action/p"
- sed -n "${line}p" < "$1".tmp
- sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
- action=pick;;
- esac
-done
-EOF
+. ../lib-rebase.sh
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
+set_fake_editor
# set up two branches like this:
#
# -- C1 --
test_expect_success 'setup' '
- touch a &&
- touch b &&
- git add a &&
- git commit -m A1 &&
- git tag A1
- git add b &&
- git commit -m B1 &&
- git tag B1 &&
- git checkout -b branch &&
- touch c &&
- git add c &&
- git commit -m C1 &&
- git checkout master &&
- touch d &&
- git add d &&
- git commit -m D1 &&
- git merge branch &&
- touch f &&
- git add f &&
- git commit -m F1 &&
- git tag F1
+ test_commit A1 &&
+ test_commit B1 &&
+ test_commit C1 &&
+ git reset --hard B1 &&
+ test_commit D1 &&
+ test_merge E1 C1 &&
+ test_commit F1
'
# Should result in:
#
test_expect_success 'squash F1 into D1' '
FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
- test "$(git rev-parse HEAD^2)" = "$(git rev-parse branch)" &&
+ test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
git tag E2
'
# And rebase G1..M1 onto E2
test_expect_success 'rebase two levels of merge' '
- git checkout -b branch2 A1 &&
- touch g &&
- git add g &&
- git commit -m G1 &&
- git checkout -b branch3 &&
- touch h
- git add h &&
- git commit -m H1 &&
- git checkout -b branch4 &&
- touch i &&
- git add i &&
- git commit -m I1 &&
- git tag I1 &&
- git checkout branch3 &&
- touch j &&
- git add j &&
- git commit -m J1 &&
- git merge I1 --no-commit &&
- git commit -m K1 &&
- git tag K1 &&
- git checkout branch2 &&
- touch l &&
- git add l &&
- git commit -m L1 &&
- git merge K1 --no-commit &&
- git commit -m M1 &&
+ test_commit G1 &&
+ test_commit H1 &&
+ test_commit I1 &&
+ git checkout -b branch3 H1 &&
+ test_commit J1 &&
+ test_merge K1 I1 &&
+ git checkout -b branch2 G1 &&
+ test_commit L1 &&
+ test_merge M1 K1 &&
GIT_EDITOR=: git rebase -i -p E2 &&
test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
--- /dev/null
+++ b/t/t3412-rebase-root.sh
@@ -0,0 +1,280 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+log_with_names () {
+ git rev-list --topo-order --parents --pretty="tformat:%s" HEAD |
+ git name-rev --stdin --name-only --refs=refs/heads/$1
+}
+
+
+test_expect_success 'prepare repository' '
+ test_commit 1 A &&
+ test_commit 2 A &&
+ git symbolic-ref HEAD refs/heads/other &&
+ rm .git/index &&
+ test_commit 3 B &&
+ test_commit 1b A 1 &&
+ test_commit 4 B
+'
+
+test_expect_success 'rebase --root expects --onto' '
+ test_must_fail git rebase --root
+'
+
+test_expect_success 'setup pre-rebase hook' '
+ mkdir -p .git/hooks &&
+ cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+ chmod +x .git/hooks/pre-rebase
+'
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+ git checkout -b work &&
+ git rebase --root --onto master &&
+ git log --pretty=tformat:"%s" > rebased &&
+ test_cmp expect rebased
+'
+
+test_expect_success 'pre-rebase got correct input (1)' '
+ test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+ git branch work2 other &&
+ git rebase --root --onto master work2 &&
+ git log --pretty=tformat:"%s" > rebased2 &&
+ test_cmp expect rebased2
+'
+
+test_expect_success 'pre-rebase got correct input (2)' '
+ test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'rebase -i --root --onto <newbase>' '
+ git checkout -b work3 other &&
+ git rebase -i --root --onto master &&
+ git log --pretty=tformat:"%s" > rebased3 &&
+ test_cmp expect rebased3
+'
+
+test_expect_success 'pre-rebase got correct input (3)' '
+ test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
+ git branch work4 other &&
+ git rebase -i --root --onto master work4 &&
+ git log --pretty=tformat:"%s" > rebased4 &&
+ test_cmp expect rebased4
+'
+
+test_expect_success 'pre-rebase got correct input (4)' '
+ test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
+'
+
+test_expect_success 'rebase -i -p with linear history' '
+ git checkout -b work5 other &&
+ git rebase -i -p --root --onto master &&
+ git log --pretty=tformat:"%s" > rebased5 &&
+ test_cmp expect rebased5
+'
+
+test_expect_success 'pre-rebase got correct input (5)' '
+ test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'set up merge history' '
+ git checkout other^ &&
+ git checkout -b side &&
+ test_commit 5 C &&
+ git checkout other &&
+ git merge side
+'
+
+cat > expect-side <<'EOF'
+commit work6 work6~1 work6^2
+Merge branch 'side' into other
+commit work6^2 work6~2
+5
+commit work6~1 work6~2
+4
+commit work6~2 work6~3
+3
+commit work6~3 work6~4
+2
+commit work6~4
+1
+EOF
+
+test_expect_success 'rebase -i -p with merge' '
+ git checkout -b work6 other &&
+ git rebase -i -p --root --onto master &&
+ log_with_names work6 > rebased6 &&
+ test_cmp expect-side rebased6
+'
+
+test_expect_success 'set up second root and merge' '
+ git symbolic-ref HEAD refs/heads/third &&
+ rm .git/index &&
+ rm A B C &&
+ test_commit 6 D &&
+ git checkout other &&
+ git merge third
+'
+
+cat > expect-third <<'EOF'
+commit work7 work7~1 work7^2
+Merge branch 'third' into other
+commit work7^2 work7~4
+6
+commit work7~1 work7~2 work7~1^2
+Merge branch 'side' into other
+commit work7~1^2 work7~3
+5
+commit work7~2 work7~3
+4
+commit work7~3 work7~4
+3
+commit work7~4 work7~5
+2
+commit work7~5
+1
+EOF
+
+test_expect_success 'rebase -i -p with two roots' '
+ git checkout -b work7 other &&
+ git rebase -i -p --root --onto master &&
+ log_with_names work7 > rebased7 &&
+ test_cmp expect-third rebased7
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+ mkdir -p .git/hooks &&
+ cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+ chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+ git checkout -b stops1 other &&
+ test_must_fail git rebase --root --onto master &&
+ test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+ test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_expect_success 'pre-rebase hook stops rebase -i' '
+ git checkout -b stops2 other &&
+ test_must_fail git rebase --root --onto master &&
+ test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+ test 0 = $(git rev-list other...stops2 | wc -l)
+'
+
+test_expect_success 'remove pre-rebase hook' '
+ rm -f .git/hooks/pre-rebase
+'
+
+test_expect_success 'set up a conflict' '
+ git checkout master &&
+ echo conflict > B &&
+ git add B &&
+ git commit -m conflict
+'
+
+test_expect_success 'rebase --root with conflict (first part)' '
+ git checkout -b conflict1 other &&
+ test_must_fail git rebase --root --onto master &&
+ git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+ echo 3 > B &&
+ git add B
+'
+
+cat > expect-conflict <<EOF
+6
+5
+4
+3
+conflict
+2
+1
+EOF
+
+test_expect_success 'rebase --root with conflict (second part)' '
+ git rebase --continue &&
+ git log --pretty=tformat:"%s" > conflict1 &&
+ test_cmp expect-conflict conflict1
+'
+
+test_expect_success 'rebase -i --root with conflict (first part)' '
+ git checkout -b conflict2 other &&
+ test_must_fail git rebase -i --root --onto master &&
+ git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+ echo 3 > B &&
+ git add B
+'
+
+test_expect_success 'rebase -i --root with conflict (second part)' '
+ git rebase --continue &&
+ git log --pretty=tformat:"%s" > conflict2 &&
+ test_cmp expect-conflict conflict2
+'
+
+cat >expect-conflict-p <<\EOF
+commit conflict3 conflict3~1 conflict3^2
+Merge branch 'third' into other
+commit conflict3^2 conflict3~4
+6
+commit conflict3~1 conflict3~2 conflict3~1^2
+Merge branch 'side' into other
+commit conflict3~1^2 conflict3~3
+5
+commit conflict3~2 conflict3~3
+4
+commit conflict3~3 conflict3~4
+3
+commit conflict3~4 conflict3~5
+conflict
+commit conflict3~5 conflict3~6
+2
+commit conflict3~6
+1
+EOF
+
+test_expect_success 'rebase -i -p --root with conflict (first part)' '
+ git checkout -b conflict3 other &&
+ test_must_fail git rebase -i -p --root --onto master &&
+ git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+ echo 3 > B &&
+ git add B
+'
+
+test_expect_success 'rebase -i -p --root with conflict (second part)' '
+ git rebase --continue &&
+ log_with_names conflict3 >out &&
+ test_cmp expect-conflict-p out
+'
+
+test_done
index 02efecae3ad06e5a62553e990fc0934dd0c65eab..9055c8b318aa8cfa8b89fd19b20e94a7435ee155 100755 (executable)
--- a/t/t4011-diff-symlink.sh
+++ b/t/t4011-diff-symlink.sh
git diff-index -M -p $tree > current &&
compare_diff_patch current expected'
+test_expect_success \
+ 'diff symlinks with non-existing targets' \
+ 'ln -s narf pinky &&
+ ln -s take\ over brain &&
+ test_must_fail git diff --no-index pinky brain > output 2> output.err &&
+ grep narf output &&
+ ! grep error output.err'
test_done
diff --git a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
index 3ceb8e73c5d4e963a2c08eddf41197431ad52178..bd7f5c0f70571d0aa0f9b9c50a343a322831976f 100644 (file)
$ git log --patch-with-stat --summary master -- dir/
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index 43d77761f988b7f43f46a5d8fd85d406ef9caa42..14595a614c362da4515f8d5b783bb0e0f079c72b 100644 (file)
$ git log --patch-with-stat master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_
index 5187a26816723476b049c44bb3801816e8672cdf..5a4e72765d316b855a8c2beee8f417cd050de323 100644 (file)
$ git log --patch-with-stat master -- dir/
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
index c9640976a8f3bd724004f945b00d71f43f00c8ea..df0aaa9f2ca780ce62951a5667ed9fe3aa005e41 100644 (file)
$ git log --root --cc --patch-with-stat --summary master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
index ad050af55ffa6983bc08ef5919dd20229584fd59..c11b5f2c7f3e6846643b7d7e0e214993f4a2cf0d 100644 (file)
$ git log --root --patch-with-stat --summary master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_master
index 628c6c03bc6195268c3e7e003478fd042ef36db2..5f0c98f9ce3d9d067cfcb662084b975a19c90f03 100644 (file)
$ git log --root --patch-with-stat master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
index 5d4e0f13b59652b170c0b2498319f970d071f774..e62c368dc64dae87c6c168462b175712f197e7af 100644 (file)
$ git log --root -c --patch-with-stat --summary master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index 217a2eb203718b91ccd79a04e0369f4c5d2905aa..b42c334439b71cdb391d832d498b90a22aad2656 100644 (file)
$ git log --root -p master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index e17ccfc2340eadbb8c393de0aa2793f6f899ba56..e8f46159da1e5ca68524f5657010d06667a9c94b 100644 (file)
$ git log --root master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index f8fefef2c3869337ba048d5cd6d95169dc60cf00..bf1326dc36629096fc4e4102375c8a9c550391aa 100644 (file)
$ git log -p master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index e9d9e7b40a08d803901ac001e4bf9f0c37ad8b15..a8f6ce5abd642e51672eb3e0ae5ea66a08aa74d2 100644 (file)
--- a/t/t4013/diff.log_master
+++ b/t/t4013/diff.log_master
$ git log master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index 9e6e1f27105ca50262e5eb3f2ff132b46c12fe45..fb08ce0e46d16d223269754295cc4a8dc4364268 100644 (file)
--- a/t/t4013/diff.show_master
+++ b/t/t4013/diff.show_master
$ git show master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
index 5facf2543db0135ceb3aafdc5ffe0395b429f44c..e96ff1fb8c11ff8c6774cb249f45744ed05a97c3 100644 (file)
$ git whatchanged --root --cc --patch-with-stat --summary master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
index 10f6767e498b0d5bdadd73067e4ef2dd5f028e89..c0aff68ef68c2d956492e7f9817b9bf5afe58005 100644 (file)
$ git whatchanged --root -c --patch-with-stat --summary master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
index caea292f15437f50fd324496ff511971cc9d1d0c..281680d95afacb291d56d9d7f23efed1f3169e93 100755 (executable)
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual
'
+test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' '
+ echo anotherfile > file2 &&
+ git add file2 &&
+ git commit -m "added 2nd file" &&
+ echo modified >file2 &&
+ GIT_EXTERNAL_DIFF=echo git diff
+'
+
test_done
diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='diff hunk fusing'
+
+. ./test-lib.sh
+
+f() {
+ echo $1
+ i=1
+ while test $i -le $2
+ do
+ echo $i
+ i=$(expr $i + 1)
+ done
+ echo $3
+}
+
+t() {
+ case $# in
+ 4) hunks=$4; cmd="diff -U$3";;
+ 5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";;
+ esac
+ label="$cmd, $1 common $2"
+ file=f$1
+ expected=expected.$file.$3.$hunks
+
+ if ! test -f $file
+ then
+ f A $1 B >$file
+ git add $file
+ git commit -q -m. $file
+ f X $1 Y >$file
+ fi
+
+ test_expect_success "$label: count hunks ($hunks)" "
+ test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks
+ "
+
+ test -f $expected &&
+ test_expect_success "$label: check output" "
+ git $cmd $file | grep -v '^index ' >actual &&
+ test_cmp $expected actual
+ "
+}
+
+cat <<EOF >expected.f1.0.1 || exit 1
+diff --git a/f1 b/f1
+--- a/f1
++++ b/f1
+@@ -1,3 +1,3 @@
+-A
++X
+ 1
+-B
++Y
+EOF
+
+cat <<EOF >expected.f1.0.2 || exit 1
+diff --git a/f1 b/f1
+--- a/f1
++++ b/f1
+@@ -1 +1 @@
+-A
++X
+@@ -3 +3 @@ A
+-B
++Y
+EOF
+
+# common lines ctx intrctx hunks
+t 1 line 0 2
+t 1 line 0 0 2
+t 1 line 0 1 1
+t 1 line 0 2 1
+t 1 line 1 1
+
+t 2 lines 0 2
+t 2 lines 0 0 2
+t 2 lines 0 1 2
+t 2 lines 0 2 1
+t 2 lines 1 1
+
+t 3 lines 1 2
+t 3 lines 1 0 2
+t 3 lines 1 1 1
+t 3 lines 1 2 1
+
+t 9 lines 3 2
+t 9 lines 3 2 2
+t 9 lines 3 3 1
+
+test_done
diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh
--- /dev/null
+++ b/t/t4033-diff-patience.sh
@@ -0,0 +1,168 @@
+#!/bin/sh
+
+test_description='patience diff algorithm'
+
+. ./test-lib.sh
+
+cat >file1 <<\EOF
+#include <stdio.h>
+
+// Frobs foo heartily
+int frobnitz(int foo)
+{
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+ printf("Your answer is: ");
+ printf("%d\n", foo);
+ }
+}
+
+int fact(int n)
+{
+ if(n > 1)
+ {
+ return fact(n-1) * n;
+ }
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ frobnitz(fact(10));
+}
+EOF
+
+cat >file2 <<\EOF
+#include <stdio.h>
+
+int fib(int n)
+{
+ if(n > 2)
+ {
+ return fib(n-1) + fib(n-2);
+ }
+ return 1;
+}
+
+// Frobs foo heartily
+int frobnitz(int foo)
+{
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+ printf("%d\n", foo);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ frobnitz(fib(10));
+}
+EOF
+
+cat >expect <<\EOF
+diff --git a/file1 b/file2
+index 6faa5a3..e3af329 100644
+--- a/file1
++++ b/file2
+@@ -1,26 +1,25 @@
+ #include <stdio.h>
+
++int fib(int n)
++{
++ if(n > 2)
++ {
++ return fib(n-1) + fib(n-2);
++ }
++ return 1;
++}
++
+ // Frobs foo heartily
+ int frobnitz(int foo)
+ {
+ int i;
+ for(i = 0; i < 10; i++)
+ {
+- printf("Your answer is: ");
+ printf("%d\n", foo);
+ }
+ }
+
+-int fact(int n)
+-{
+- if(n > 1)
+- {
+- return fact(n-1) * n;
+- }
+- return 1;
+-}
+-
+ int main(int argc, char **argv)
+ {
+- frobnitz(fact(10));
++ frobnitz(fib(10));
+ }
+EOF
+
+test_expect_success 'patience diff' '
+
+ test_must_fail git diff --no-index --patience file1 file2 > output &&
+ test_cmp expect output
+
+'
+
+test_expect_success 'patience diff output is valid' '
+
+ mv file2 expect &&
+ git apply < output &&
+ test_cmp expect file2
+
+'
+
+cat >uniq1 <<\EOF
+1
+2
+3
+4
+5
+6
+EOF
+
+cat >uniq2 <<\EOF
+a
+b
+c
+d
+e
+f
+EOF
+
+cat >expect <<\EOF
+diff --git a/uniq1 b/uniq2
+index b414108..0fdf397 100644
+--- a/uniq1
++++ b/uniq2
+@@ -1,6 +1,6 @@
+-1
+-2
+-3
+-4
+-5
+-6
++a
++b
++c
++d
++e
++f
+EOF
+
+test_expect_success 'completely different files' '
+
+ test_must_fail git diff --no-index --patience uniq1 uniq2 > output &&
+ test_cmp expect output
+
+'
+
+test_done
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
--- /dev/null
+++ b/t/t4034-diff-words.sh
@@ -0,0 +1,200 @@
+#!/bin/sh
+
+test_description='word diff colors'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ git config diff.color.old red
+ git config diff.color.new green
+
+'
+
+decrypt_color () {
+ sed \
+ -e 's/.\[1m/<WHITE>/g' \
+ -e 's/.\[31m/<RED>/g' \
+ -e 's/.\[32m/<GREEN>/g' \
+ -e 's/.\[36m/<BROWN>/g' \
+ -e 's/.\[m/<RESET>/g'
+}
+
+word_diff () {
+ test_must_fail git diff --no-index "$@" pre post > output &&
+ decrypt_color < output > output.decrypted &&
+ test_cmp expect output.decrypted
+}
+
+cat > pre <<\EOF
+h(4)
+
+a = b + c
+EOF
+
+cat > post <<\EOF
+h(4),hh[44]
+
+a = b + c
+
+aa = a
+
+aeff = aeff * ( aaa )
+EOF
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
+
+test_expect_success 'word diff with runs of whitespace' '
+
+ word_diff --color-words
+
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+h(4),<GREEN>hh<RESET>[44]
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa<RESET> )
+EOF
+cp expect expect.letter-runs-are-words
+
+test_expect_success 'word diff with a regular expression' '
+
+ word_diff --color-words="[a-z]+"
+
+'
+
+test_expect_success 'set a diff driver' '
+ git config diff.testdriver.wordRegex "[^[:space:]]" &&
+ cat <<EOF > .gitattributes
+pre diff=testdriver
+post diff=testdriver
+EOF
+'
+
+test_expect_success 'option overrides .gitattributes' '
+
+ word_diff --color-words="[a-z]+"
+
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+h(4)<GREEN>,hh[44]<RESET>
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa )<RESET>
+EOF
+cp expect expect.non-whitespace-is-word
+
+test_expect_success 'use regex supplied by driver' '
+
+ word_diff --color-words
+
+'
+
+test_expect_success 'set diff.wordRegex option' '
+ git config diff.wordRegex "[[:alnum:]]+"
+'
+
+cp expect.letter-runs-are-words expect
+
+test_expect_success 'command-line overrides config' '
+ word_diff --color-words="[a-z]+"
+'
+
+cp expect.non-whitespace-is-word expect
+
+test_expect_success '.gitattributes override config' '
+ word_diff --color-words
+'
+
+test_expect_success 'remove diff driver regex' '
+ git config --unset diff.testdriver.wordRegex
+'
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 330b04f..5ed8eff 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1,3 +1,7 @@<RESET>
+h(4),<GREEN>hh[44<RESET>]
+<RESET>
+a = b + c<RESET>
+
+<GREEN>aa = a<RESET>
+
+<GREEN>aeff = aeff * ( aaa<RESET> )
+EOF
+
+test_expect_success 'use configured regex' '
+ word_diff --color-words
+'
+
+echo 'aaa (aaa)' > pre
+echo 'aaa (aaa) aaa' > post
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index c29453b..be22f37 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1 +1 @@<RESET>
+aaa (aaa) <GREEN>aaa<RESET>
+EOF
+
+test_expect_success 'test parsing words for newline' '
+
+ word_diff --color-words="a+"
+
+
+'
+
+echo '(:' > pre
+echo '(' > post
+
+cat > expect <<\EOF
+<WHITE>diff --git a/pre b/post<RESET>
+<WHITE>index 289cb9d..2d06f37 100644<RESET>
+<WHITE>--- a/pre<RESET>
+<WHITE>+++ b/post<RESET>
+<BROWN>@@ -1 +1 @@<RESET>
+(<RED>:<RESET>
+EOF
+
+test_expect_success 'test when words are only removed at the end' '
+
+ word_diff --color-words=.
+
+'
+
+test_done
diff --git a/t/t4106-apply-stdin.sh b/t/t4106-apply-stdin.sh
--- /dev/null
+++ b/t/t4106-apply-stdin.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test_description='git apply --numstat - <patch'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo hello >text &&
+ git add text &&
+ echo goodbye >text &&
+ git diff >patch
+'
+
+test_expect_success 'git apply --numstat - < patch' '
+ echo "1 1 text" >expect &&
+ git apply --numstat - <patch >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git apply --numstat - < patch patch' '
+ for i in 1 2; do echo "1 1 text"; done >expect &&
+ git apply --numstat - < patch patch >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 796f795267dee1eaf63b10fb3e5e14ce0431bebd..5e65afa0c10d02e50c79b550a3c142d8ff1f0674 100755 (executable)
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -257,4 +257,37 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' '
test -z "$(git diff second)"
'
+test_expect_success 'am --committer-date-is-author-date' '
+ git checkout first &&
+ test_tick &&
+ git am --committer-date-is-author-date patch1 &&
+ git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+ at=$(sed -ne "/^author /s/.*> //p" head1) &&
+ ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
+ test "$at" = "$ct"
+'
+
+test_expect_success 'am without --committer-date-is-author-date' '
+ git checkout first &&
+ test_tick &&
+ git am patch1 &&
+ git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+ at=$(sed -ne "/^author /s/.*> //p" head1) &&
+ ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
+ test "$at" != "$ct"
+'
+
+# This checks for +0000 because TZ is set to UTC and that should
+# show up when the current time is used. The date in message is set
+# by test_tick that uses -0700 timezone; if this feature does not
+# work, we will see that instead of +0000.
+test_expect_success 'am --ignore-date' '
+ git checkout first &&
+ test_tick &&
+ git am --ignore-date patch1 &&
+ git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+ at=$(sed -ne "/^author /s/.*> //p" head1) &&
+ echo "$at" | grep "+0000"
+'
+
test_done
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 0ab925c4e4710a560f0d35e47ccdda8ddb2b8212..7b976ee36db140550dab33ea990ada8e52dfb13e 100755 (executable)
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
test_tick &&
git commit -m second &&
- mkdir a &&
- echo ni >a/two &&
- git add a/two &&
+ git mv one ichi &&
test_tick &&
git commit -m third &&
- echo san >a/three &&
- git add a/three &&
+ cp ichi ein &&
+ git add ein &&
test_tick &&
git commit -m fourth &&
- git rm a/three &&
+ mkdir a &&
+ echo ni >a/two &&
+ git add a/two &&
+ test_tick &&
+ git commit -m fifth &&
+
+ git rm a/two &&
test_tick &&
- git commit -m fifth
+ git commit -m sixth
'
test_expect_success 'diff-filter=A' '
actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) &&
- expect=$(echo fourth ; echo third ; echo initial) &&
+ expect=$(echo fifth ; echo fourth ; echo third ; echo initial) &&
test "$actual" = "$expect" || {
echo Oops
echo "Actual: $actual"
test_expect_success 'diff-filter=D' '
actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) &&
- expect=$(echo fifth) &&
+ expect=$(echo sixth ; echo third) &&
+ test "$actual" = "$expect" || {
+ echo Oops
+ echo "Actual: $actual"
+ false
+ }
+
+'
+
+test_expect_success 'diff-filter=R' '
+
+ actual=$(git log -M --pretty="format:%s" --diff-filter=R HEAD) &&
+ expect=$(echo third) &&
+ test "$actual" = "$expect" || {
+ echo Oops
+ echo "Actual: $actual"
+ false
+ }
+
+'
+
+test_expect_success 'diff-filter=C' '
+
+ actual=$(git log -C -C --pretty="format:%s" --diff-filter=C HEAD) &&
+ expect=$(echo fourth) &&
+ test "$actual" = "$expect" || {
+ echo Oops
+ echo "Actual: $actual"
+ false
+ }
+
+'
+
+test_expect_success 'git log --follow' '
+
+ actual=$(git log --follow --pretty="format:%s" ichi) &&
+ expect=$(echo third ; echo second ; echo initial) &&
test "$actual" = "$expect" || {
echo Oops
echo "Actual: $actual"
test_expect_success 'setup case sensitivity tests' '
echo case >one &&
test_tick &&
+ git add one
git commit -a -m Second
'
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
--- /dev/null
+++ b/t/t4203-mailmap.sh
@@ -0,0 +1,215 @@
+#!/bin/sh
+
+test_description='.mailmap configurations'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ echo one >one &&
+ git add one &&
+ test_tick &&
+ git commit -m initial &&
+ echo two >>one &&
+ git add one &&
+ git commit --author "nick1 <bugs@company.xx>" -m second
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+ initial
+
+nick1 (1):
+ second
+
+EOF
+
+test_expect_success 'No mailmap' '
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+ initial
+
+nick1 (1):
+ second
+
+EOF
+
+test_expect_success 'default .mailmap' '
+ echo "Repo Guy <author@example.com>" > .mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+# Using a mailmap file in a subdirectory of the repo here, but
+# could just as well have been a file outside of the repository
+cat >expect <<\EOF
+Internal Guy (1):
+ second
+
+Repo Guy (1):
+ initial
+
+EOF
+test_expect_success 'mailmap.file set' '
+ mkdir internal_mailmap &&
+ echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap &&
+ git config mailmap.file internal_mailmap/.mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<\EOF
+External Guy (1):
+ initial
+
+Internal Guy (1):
+ second
+
+EOF
+test_expect_success 'mailmap.file override' '
+ echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap &&
+ git config mailmap.file internal_mailmap/.mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+ initial
+
+nick1 (1):
+ second
+
+EOF
+
+test_expect_success 'mailmap.file non-existant' '
+ rm internal_mailmap/.mailmap &&
+ rmdir internal_mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+ initial
+
+nick1 (1):
+ second
+
+EOF
+test_expect_success 'No mailmap files, but configured' '
+ rm .mailmap &&
+ git shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
+# Extended mailmap configurations should give us the following output for shortlog
+cat >expect <<\EOF
+A U Thor <author@example.com> (1):
+ initial
+
+CTO <cto@company.xx> (1):
+ seventh
+
+Other Author <other@author.xx> (2):
+ third
+ fourth
+
+Santa Claus <santa.claus@northpole.xx> (2):
+ fifth
+ sixth
+
+Some Dude <some@dude.xx> (1):
+ second
+
+EOF
+
+test_expect_success 'Shortlog output (complex mapping)' '
+ echo three >>one &&
+ git add one &&
+ test_tick &&
+ git commit --author "nick2 <bugs@company.xx>" -m third &&
+
+ echo four >>one &&
+ git add one &&
+ test_tick &&
+ git commit --author "nick2 <nick2@company.xx>" -m fourth &&
+
+ echo five >>one &&
+ git add one &&
+ test_tick &&
+ git commit --author "santa <me@company.xx>" -m fifth &&
+
+ echo six >>one &&
+ git add one &&
+ test_tick &&
+ git commit --author "claus <me@company.xx>" -m sixth &&
+
+ echo seven >>one &&
+ git add one &&
+ test_tick &&
+ git commit --author "CTO <cto@coompany.xx>" -m seventh &&
+
+ mkdir internal_mailmap &&
+ echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
+ echo "<cto@company.xx> <cto@coompany.xx>" >> internal_mailmap/.mailmap &&
+ echo "Some Dude <some@dude.xx> nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+ echo "Other Author <other@author.xx> nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+ echo "Other Author <other@author.xx> <nick2@company.xx>" >> internal_mailmap/.mailmap &&
+ echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+ echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+
+ git shortlog -e HEAD >actual &&
+ test_cmp expect actual
+
+'
+
+# git log with --pretty format which uses the name and email mailmap placemarkers
+cat >expect <<\EOF
+Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author A U Thor <author@example.com> maps to A U Thor <author@example.com>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+EOF
+
+test_expect_success 'Log output (complex mapping)' '
+ git log --pretty=format:"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n" >actual &&
+ test_cmp expect actual
+'
+
+# git blame
+cat >expect <<\EOF
+^3a2fdcb (A U Thor 2005-04-07 15:13:13 -0700 1) one
+7de6f99b (Some Dude 2005-04-07 15:13:13 -0700 2) two
+5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three
+ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four
+5ab6d4fa (Santa Claus 2005-04-07 15:16:13 -0700 5) five
+38a42d8b (Santa Claus 2005-04-07 15:17:13 -0700 6) six
+8ddc0386 (CTO 2005-04-07 15:18:13 -0700 7) seven
+EOF
+
+test_expect_success 'Blame output (complex mapping)' '
+ git blame one >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4252-am-options.sh b/t/t4252-am-options.sh
index 3ab9e8e6e3635ce54b19cec7987ab976fd994309..f603c1b1336c4a00889177376d9b51077c9cc2ac 100755 (executable)
--- a/t/t4252-am-options.sh
+++ b/t/t4252-am-options.sh
#!/bin/sh
-test_description='git am not losing options'
+test_description='git am with options and not losing them'
. ./test-lib.sh
tm="$TEST_DIRECTORY/t4252"
grep "^Three$" file-2
'
+test_expect_success 'interrupted am --directory="frotz nitfol"' '
+ rm -rf .git/rebase-apply &&
+ git reset --hard initial &&
+ test_must_fail git am --directory="frotz nitfol" "$tm"/am-test-5-? &&
+ git am --skip &&
+ grep One "frotz nitfol/file-5"
+'
+
+test_expect_success 'apply to a funny path' '
+ with_sq="with'\''sq"
+ rm -fr .git/rebase-apply &&
+ git reset --hard initial &&
+ git am --directory="$with_sq" "$tm"/am-test-5-2 &&
+ test -f "$with_sq/file-5"
+'
+
+test_expect_success 'am --reject' '
+ rm -rf .git/rebase-apply &&
+ git reset --hard initial &&
+ test_must_fail git am --reject "$tm"/am-test-6-1 &&
+ grep "@@ -1,3 +1,3 @@" file-2.rej &&
+ test_must_fail git diff-files --exit-code --quiet file-2 &&
+ grep "[-]-reject" .git/rebase-apply/apply-opt
+'
+
test_done
diff --git a/t/t4252/am-test-5-1 b/t/t4252/am-test-5-1
--- /dev/null
+++ b/t/t4252/am-test-5-1
@@ -0,0 +1,20 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Six
+
+Applying this patch with --directory='frotz nitfol' should fail
+
+diff --git i/junk/file-2 w/junk/file-2
+index 06e567b..b6f3a16 100644
+--- i/junk/file-2
++++ w/junk/file-2
+@@ -1,7 +1,7 @@
+ One
+ 2
+-3
++Three
+ 4
+ 5
+-6
++Six
+ 7
diff --git a/t/t4252/am-test-5-2 b/t/t4252/am-test-5-2
--- /dev/null
+++ b/t/t4252/am-test-5-2
@@ -0,0 +1,15 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Six
+
+Applying this patch with --directory='frotz nitfol' should succeed
+
+diff --git i/file-5 w/file-5
+new file mode 100644
+index 000000..1d6ed9f
+--- /dev/null
++++ w/file-5
+@@ -0,0 +1,3 @@
++One
++two
++three
diff --git a/t/t4252/am-test-6-1 b/t/t4252/am-test-6-1
--- /dev/null
+++ b/t/t4252/am-test-6-1
@@ -0,0 +1,21 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Huh
+
+Should fail and leave rejects
+
+diff --git i/file-2 w/file-2
+index 06e567b..b6f3a16 100644
+--- i/file-2
++++ w/file-2
+@@ -1,3 +1,3 @@
+-0
++One
+ 2
+ 3
+@@ -4,4 +4,4 @@
+ 4
+ 5
+-6
++Six
+ 7
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index fe14589427643b9bb7759c597935da724adf1a64..e70ea94a1368dc045469808d30c717aa2b8bb158 100755 (executable)
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last &&
last=`cat last` &&
echo total is $last &&
- test `cat last` = 11'
+ test `cat last` = 13'
for mail in `echo 00*`
do
'
done
+
+test_expect_success 'split box with rfc2047 samples' \
+ 'mkdir rfc2047 &&
+ git mailsplit -orfc2047 "$TEST_DIRECTORY"/t5100/rfc2047-samples.mbox \
+ >rfc2047/last &&
+ last=`cat rfc2047/last` &&
+ echo total is $last &&
+ test `cat rfc2047/last` = 11'
+
+for mail in `echo rfc2047/00*`
+do
+ test_expect_success "mailinfo $mail" '
+ git mailinfo -u $mail-msg $mail-patch <$mail >$mail-info &&
+ echo msg &&
+ test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-msg &&
+ echo patch &&
+ test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-patch &&
+ echo info &&
+ test_cmp "$TEST_DIRECTORY"/t5100/rfc2047-info-$(basename $mail) $mail-info
+ '
+done
+
test_expect_success 'respect NULs' '
git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain &&
diff --git a/t/t5100/empty b/t/t5100/empty
diff --git a/t/t5100/info0001 b/t/t5100/info0001
index 8c052777e0d216e84bb8464b1ceaff1bc7721154..f951538acc0152987d0e296ab0ea73b738275bdb 100644 (file)
--- a/t/t5100/info0001
+++ b/t/t5100/info0001
-Author: A U Thor
+Author: A (zzz) U Thor (Comment)
Email: a.u.thor@example.com
Subject: a commit.
Date: Fri, 9 Jun 2006 00:44:16 -0700
diff --git a/t/t5100/info0012 b/t/t5100/info0012
--- /dev/null
+++ b/t/t5100/info0012
@@ -0,0 +1,5 @@
+Author: Dmitriy Blinov
+Email: bda@mnsspb.ru
+Subject: Изменён список пакетов необходимых для сборки
+Date: Wed, 12 Nov 2008 17:54:41 +0300
+
diff --git a/t/t5100/info0013 b/t/t5100/info0013
--- /dev/null
+++ b/t/t5100/info0013
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: a patch
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/msg0012 b/t/t5100/msg0012
--- /dev/null
+++ b/t/t5100/msg0012
@@ -0,0 +1,7 @@
+textlive-* исправлены на texlive-*
+docutils заменён на python-docutils
+
+Действительно, оказалось, что rest2web вытягивает за собой
+python-docutils. В то время как сам rest2web не нужен.
+
+Signed-off-by: Dmitriy Blinov <bda@mnsspb.ru>
diff --git a/t/t5100/msg0013 b/t/t5100/msg0013
diff --git a/t/t5100/patch0012 b/t/t5100/patch0012
--- /dev/null
+++ b/t/t5100/patch0012
@@ -0,0 +1,30 @@
+---
+ howto/build_navy.txt | 6 +++---
+ 1 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/howto/build_navy.txt b/howto/build_navy.txt
+index 3fd3afb..0ee807e 100644
+--- a/howto/build_navy.txt
++++ b/howto/build_navy.txt
+@@ -119,8 +119,8 @@
+ - libxv-dev
+ - libusplash-dev
+ - latex-make
+- - textlive-lang-cyrillic
+- - textlive-latex-extra
++ - texlive-lang-cyrillic
++ - texlive-latex-extra
+ - dia
+ - python-pyrex
+ - libtool
+@@ -128,7 +128,7 @@
+ - sox
+ - cython
+ - imagemagick
+- - docutils
++ - python-docutils
+
+ #. на машине dinar: добавить свой открытый ssh-ключ в authorized_keys2 пользователя ddev
+ #. на своей машине: отредактировать /etc/sudoers (команда ``visudo``) примерно следующим образом::
+--
+1.5.6.5
diff --git a/t/t5100/patch0013 b/t/t5100/patch0013
diff --git a/t/t5100/rfc2047-info-0001 b/t/t5100/rfc2047-info-0001
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Keith Moore
+Email: moore@cs.utk.edu
+Subject: If you can read this you understand the example.
+
diff --git a/t/t5100/rfc2047-info-0002 b/t/t5100/rfc2047-info-0002
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Olle Järnefors
+Email: ojarnef@admin.kth.se
+Subject: Time for ISO 10646?
+
diff --git a/t/t5100/rfc2047-info-0003 b/t/t5100/rfc2047-info-0003
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Patrik Fältström
+Email: paf@nada.kth.se
+Subject: RFC-HDR care and feeding
+
diff --git a/t/t5100/rfc2047-info-0004 b/t/t5100/rfc2047-info-0004
--- /dev/null
@@ -0,0 +1,4 @@
+Author: Nathaniel Borenstein (םולש ןב ילטפנ)
+Email: nsb@thumper.bellcore.com
+Subject: Test of new header generator
+
diff --git a/t/t5100/rfc2047-info-0005 b/t/t5100/rfc2047-info-0005
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a)
+
diff --git a/t/t5100/rfc2047-info-0006 b/t/t5100/rfc2047-info-0006
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a b)
+
diff --git a/t/t5100/rfc2047-info-0007 b/t/t5100/rfc2047-info-0007
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (ab)
+
diff --git a/t/t5100/rfc2047-info-0008 b/t/t5100/rfc2047-info-0008
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (ab)
+
diff --git a/t/t5100/rfc2047-info-0009 b/t/t5100/rfc2047-info-0009
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (ab)
+
diff --git a/t/t5100/rfc2047-info-0010 b/t/t5100/rfc2047-info-0010
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a b)
+
diff --git a/t/t5100/rfc2047-info-0011 b/t/t5100/rfc2047-info-0011
--- /dev/null
@@ -0,0 +1,2 @@
+Subject: (a b)
+
diff --git a/t/t5100/rfc2047-samples.mbox b/t/t5100/rfc2047-samples.mbox
--- /dev/null
@@ -0,0 +1,48 @@
+From nobody Mon Sep 17 00:00:00 2001
+From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>
+To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
+CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
+Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
+ =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
+
+From nobody Mon Sep 17 00:00:00 2001
+From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se>
+To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se
+Subject: Time for ISO 10646?
+
+From nobody Mon Sep 17 00:00:00 2001
+To: Dave Crocker <dcrocker@mordor.stanford.edu>
+Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se
+From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se>
+Subject: Re: RFC-HDR care and feeding
+
+From nobody Mon Sep 17 00:00:00 2001
+From: Nathaniel Borenstein <nsb@thumper.bellcore.com>
+ (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=)
+To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed
+ <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu>
+Subject: Test of new header generator
+MIME-Version: 1.0
+Content-type: text/plain; charset=ISO-8859-1
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= b)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?=
+ =?ISO-8859-1?Q?b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a_b?=)
+
+From nobody Mon Sep 17 00:00:00 2001
+Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=)
diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox
index 4bf7947b418963e9b15e393fc738e515b3d2141d..c5ad206b40e1fcf79019cebdfd848d72c17cefcc 100644 (file)
--- a/t/t5100/sample.mbox
+++ b/t/t5100/sample.mbox
From nobody Mon Sep 17 00:00:00 2001
-From: A U Thor <a.u.thor@example.com>
+From: A (zzz)
+ U
+ Thor
+ <a.u.thor@example.com> (Comment)
Date: Fri, 9 Jun 2006 00:44:16 -0700
Subject: [PATCH] a commit.
--=-=-=--
+From bda@mnsspb.ru Wed Nov 12 17:54:41 2008
+From: Dmitriy Blinov <bda@mnsspb.ru>
+To: navy-patches@dinar.mns.mnsspb.ru
+Date: Wed, 12 Nov 2008 17:54:41 +0300
+Message-Id: <1226501681-24923-1-git-send-email-bda@mnsspb.ru>
+X-Mailer: git-send-email 1.5.6.5
+MIME-Version: 1.0
+Content-Type: text/plain;
+ charset=utf-8
+Content-Transfer-Encoding: 8bit
+Subject: [Navy-patches] [PATCH]
+ =?utf-8?b?0JjQt9C80LXQvdGR0L0g0YHQv9C40YHQvtC6INC/0LA=?=
+ =?utf-8?b?0LrQtdGC0L7QsiDQvdC10L7QsdGF0L7QtNC40LzRi9GFINC00LvRjyA=?=
+ =?utf-8?b?0YHQsdC+0YDQutC4?=
+
+textlive-* исправлены на texlive-*
+docutils заменён на python-docutils
+
+Действительно, оказалось, что rest2web вытягивает за собой
+python-docutils. В то время как сам rest2web не нужен.
+
+Signed-off-by: Dmitriy Blinov <bda@mnsspb.ru>
+---
+ howto/build_navy.txt | 6 +++---
+ 1 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/howto/build_navy.txt b/howto/build_navy.txt
+index 3fd3afb..0ee807e 100644
+--- a/howto/build_navy.txt
++++ b/howto/build_navy.txt
+@@ -119,8 +119,8 @@
+ - libxv-dev
+ - libusplash-dev
+ - latex-make
+- - textlive-lang-cyrillic
+- - textlive-latex-extra
++ - texlive-lang-cyrillic
++ - texlive-latex-extra
+ - dia
+ - python-pyrex
+ - libtool
+@@ -128,7 +128,7 @@
+ - sox
+ - cython
+ - imagemagick
+- - docutils
++ - python-docutils
+
+ #. на машине dinar: добавить свой открытый ssh-ключ в authorized_keys2 пользователя ddev
+ #. на своей машине: отредактировать /etc/sudoers (команда ``visudo``) примерно следующим образом::
+--
+1.5.6.5
+From nobody Mon Sep 17 00:00:00 2001
+From: <a.u.thor@example.com> (A U Thor)
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+Subject: [PATCH] a patch
+
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index 04522857abb716b8866e0f5153ec33b3ac780536..ccfc64c6eef7e0aba7bd8a8496427470e9020309 100755 (executable)
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
unset GIT_OBJECT_DIRECTORY
+test_expect_success 'survive missing objects/pack directory' '
+ (
+ rm -fr missing-pack &&
+ mkdir missing-pack &&
+ cd missing-pack &&
+ git init &&
+ GOP=.git/objects/pack
+ rm -fr $GOP &&
+ git index-pack --stdin --keep=test <../test-3-${packname_3}.pack &&
+ test -f $GOP/pack-${packname_3}.pack &&
+ test_cmp $GOP/pack-${packname_3}.pack ../test-3-${packname_3}.pack &&
+ test -f $GOP/pack-${packname_3}.idx &&
+ test_cmp $GOP/pack-${packname_3}.idx ../test-3-${packname_3}.idx &&
+ test -f $GOP/pack-${packname_3}.keep
+ )
+'
+
test_expect_success \
'verify pack' \
'git verify-pack test-1-${packname_1}.idx \
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index 884e24253a0a9d262b39ae96ea5c03ecb7ba4072..e6f70d474f2f855d3b2b40eed526e4873b3288d5 100755 (executable)
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
'setup' \
'rm -rf .git
git init &&
+ git config pack.threads 1 &&
i=1 &&
while test $i -le 100
do
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index 771c0a06a4bd01cf40628ffae379b5d992802ef6..55ed7c7935c007573761842cea5978a784c865b3 100755 (executable)
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
'
+test_expect_success 'gc --no-prune' '
+
+ before=$(git count-objects | sed "s/ .*//") &&
+ BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+ BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+ test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE &&
+ test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+ git config gc.pruneExpire 2.days.ago &&
+ git gc --no-prune &&
+ test 1 = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc respects gc.pruneExpire' '
+
+ git config gc.pruneExpire 5002.days.ago &&
+ git gc &&
+ test -f $BLOB_FILE &&
+ git config gc.pruneExpire 5000.days.ago &&
+ git gc &&
+ test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc --prune=<date>' '
+
+ BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+ BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+ test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+ git gc --prune=5002.days.ago &&
+ test -f $BLOB_FILE &&
+ git gc --prune=5000.days.ago &&
+ test ! -f $BLOB_FILE
+
+'
+
test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index b21317d68527988d0c2939e7173098b08bfbb64f..f2d5581b12f7d70c9f346da75dced81e12bd4c7f 100755 (executable)
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
done &&
git update-ref HEAD "$commit" &&
git clone ./. victim &&
- cd victim &&
- git log &&
- cd .. &&
+ ( cd victim && git log ) &&
git update-ref HEAD "$zero" &&
parent=$zero &&
i=0 &&
'
test_expect_success 'pack the destination repository' '
+ (
cd victim &&
git repack -a -d &&
- git prune &&
- cd ..
+ git prune
+ )
'
-test_expect_success \
- 'pushing rewound head should not barf but require --force' '
- # should not fail but refuse to update.
- if git send-pack ./victim/.git/ master
- then
- # now it should fail with Pasky patch
- echo >&2 Gaah, it should have failed.
- false
- else
- echo >&2 Thanks, it correctly failed.
- true
- fi &&
- if cmp victim/.git/refs/heads/master .git/refs/heads/master
- then
- # should have been left as it was!
- false
- else
- true
- fi &&
+test_expect_success 'refuse pushing rewound head without --force' '
+ pushed_head=$(git rev-parse --verify master) &&
+ victim_orig=$(cd victim && git rev-parse --verify master) &&
+ test_must_fail git send-pack ./victim master &&
+ victim_head=$(cd victim && git rev-parse --verify master) &&
+ test "$victim_head" = "$victim_orig" &&
# this should update
- git send-pack --force ./victim/.git/ master &&
- cmp victim/.git/refs/heads/master .git/refs/heads/master
+ git send-pack --force ./victim master &&
+ victim_head=$(cd victim && git rev-parse --verify master) &&
+ test "$victim_head" = "$pushed_head"
'
test_expect_success \
'push can be used to delete a ref' '
- cd victim &&
- git branch extra master &&
- cd .. &&
- test -f victim/.git/refs/heads/extra &&
- git send-pack ./victim/.git/ :extra master &&
- ! test -f victim/.git/refs/heads/extra
+ ( cd victim && git branch extra master ) &&
+ git send-pack ./victim :extra master &&
+ ( cd victim &&
+ test_must_fail git rev-parse --verify extra )
'
-unset GIT_CONFIG
-HOME=`pwd`/no-such-directory
-export HOME ;# this way we force the victim/.git/config to be used.
-
-test_expect_success \
- 'pushing a delete should be denied with denyDeletes' '
- cd victim &&
- git config receive.denyDeletes true &&
- git branch extra master &&
- cd .. &&
- test -f victim/.git/refs/heads/extra &&
- test_must_fail git send-pack ./victim/.git/ :extra master
+test_expect_success 'refuse deleting push with denyDeletes' '
+ (
+ cd victim &&
+ ( git branch -D extra || : ) &&
+ git config receive.denyDeletes true &&
+ git branch extra master
+ ) &&
+ test_must_fail git send-pack ./victim :extra master
'
-rm -f victim/.git/refs/heads/extra
-test_expect_success \
- 'pushing with --force should be denied with denyNonFastforwards' '
- cd victim &&
- git config receive.denyNonFastforwards true &&
- cd .. &&
- git update-ref refs/heads/master master^ || return 1
- git send-pack --force ./victim/.git/ master && return 1
- ! test_cmp .git/refs/heads/master victim/.git/refs/heads/master
+test_expect_success 'denyNonFastforwards trumps --force' '
+ (
+ cd victim &&
+ ( git branch -D extra || : ) &&
+ git config receive.denyNonFastforwards true
+ ) &&
+ victim_orig=$(cd victim && git rev-parse --verify master) &&
+ test_must_fail git send-pack --force ./victim master^:master &&
+ victim_head=$(cd victim && git rev-parse --verify master) &&
+ test "$victim_orig" = "$victim_head"
'
-test_expect_success \
- 'pushing does not include non-head refs' '
- mkdir parent && cd parent &&
- git init && touch file && git add file && git commit -m add &&
- cd .. &&
- git clone parent child && cd child && git push --all &&
- cd ../parent &&
- git branch -a >branches && ! grep origin/master branches
+test_expect_success 'push --all excludes remote tracking hierarchy' '
+ mkdir parent &&
+ (
+ cd parent &&
+ git init && : >file && git add file && git commit -m add
+ ) &&
+ git clone parent child &&
+ (
+ cd child && git push --all
+ ) &&
+ (
+ cd parent &&
+ test -z "$(git for-each-ref refs/remotes/origin)"
+ )
'
rewound_push_setup() {
rm -rf parent child &&
- mkdir parent && cd parent &&
- git init && echo one >file && git add file && git commit -m one &&
- echo two >file && git commit -a -m two &&
- cd .. &&
- git clone parent child && cd child && git reset --hard HEAD^
+ mkdir parent &&
+ (
+ cd parent &&
+ git init &&
+ echo one >file && git add file && git commit -m one &&
+ echo two >file && git commit -a -m two
+ ) &&
+ git clone parent child &&
+ (
+ cd child && git reset --hard HEAD^
+ )
}
rewound_push_succeeded() {
fi
}
-test_expect_success \
- 'pushing explicit refspecs respects forcing' '
+test_expect_success 'pushing explicit refspecs respects forcing' '
rewound_push_setup &&
- if git send-pack ../parent/.git refs/heads/master:refs/heads/master
- then
- false
- else
- true
- fi && rewound_push_failed &&
- git send-pack ../parent/.git +refs/heads/master:refs/heads/master &&
- rewound_push_succeeded
+ parent_orig=$(cd parent && git rev-parse --verify master) &&
+ (
+ cd child &&
+ test_must_fail git send-pack ../parent \
+ refs/heads/master:refs/heads/master
+ ) &&
+ parent_head=$(cd parent && git rev-parse --verify master) &&
+ test "$parent_orig" = "$parent_head" &&
+ (
+ cd child &&
+ git send-pack ../parent \
+ +refs/heads/master:refs/heads/master
+ ) &&
+ parent_head=$(cd parent && git rev-parse --verify master) &&
+ child_head=$(cd parent && git rev-parse --verify master) &&
+ test "$parent_head" = "$child_head"
'
-test_expect_success \
- 'pushing wildcard refspecs respects forcing' '
+test_expect_success 'pushing wildcard refspecs respects forcing' '
rewound_push_setup &&
- if git send-pack ../parent/.git refs/heads/*:refs/heads/*
- then
- false
- else
- true
- fi && rewound_push_failed &&
- git send-pack ../parent/.git +refs/heads/*:refs/heads/* &&
- rewound_push_succeeded
+ parent_orig=$(cd parent && git rev-parse --verify master) &&
+ (
+ cd child &&
+ test_must_fail git send-pack ../parent \
+ "refs/heads/*:refs/heads/*"
+ ) &&
+ parent_head=$(cd parent && git rev-parse --verify master) &&
+ test "$parent_orig" = "$parent_head" &&
+ (
+ cd child &&
+ git send-pack ../parent \
+ "+refs/heads/*:refs/heads/*"
+ ) &&
+ parent_head=$(cd parent && git rev-parse --verify master) &&
+ child_head=$(cd parent && git rev-parse --verify master) &&
+ test "$parent_head" = "$child_head"
+'
+
+test_expect_success 'warn pushing to delete current branch' '
+ rewound_push_setup &&
+ (
+ cd child &&
+ git send-pack ../parent :refs/heads/master 2>errs
+ ) &&
+ grep "warning: to refuse deleting" child/errs &&
+ (
+ cd parent &&
+ test_must_fail git rev-parse --verify master
+ )
'
test_done
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 1f59960d90c31f02768666c86654a97e1fad9305..eb637184a00007c61e6d92f7b5546eed6ec5a0ae 100755 (executable)
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
)
'
+test_expect_success 'remove remote protects non-remote branches' '
+(
+ cd test &&
+ (cat >expect1 <<EOF
+Note: A non-remote branch was not removed; to delete it, use:
+ git branch -d master
+EOF
+ cat >expect2 <<EOF
+Note: Non-remote branches were not removed; to delete them, use:
+ git branch -d foobranch
+ git branch -d master
+EOF
+) &&
+ git tag footag
+ git config --add remote.oops.fetch "+refs/*:refs/*" &&
+ git remote rm oops 2>actual1 &&
+ git branch foobranch &&
+ git config --add remote.oops.fetch "+refs/*:refs/*" &&
+ git remote rm oops 2>actual2 &&
+ git branch -d foobranch &&
+ git tag -d footag &&
+ test_cmp expect1 actual1 &&
+ test_cmp expect2 actual2
+)
+'
+
cat > test/expect << EOF
* remote origin
URL: $(pwd)/one
test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin")
'
+test_expect_success 'remote prune to cause a dangling symref' '
+ git clone one seven &&
+ (
+ cd one &&
+ git checkout side2 &&
+ git branch -D master
+ ) &&
+ (
+ cd seven &&
+ git remote prune origin
+ ) 2>err &&
+ grep "has become dangling" err &&
+
+ : And the dangling symref will not cause other annoying errors
+ (
+ cd seven &&
+ git branch -a
+ ) 2>err &&
+ ! grep "points nowhere" err
+ (
+ cd seven &&
+ test_must_fail git branch nomore origin
+ ) 2>err &&
+ grep "dangling symref" err
+'
+
test_done
+
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 4426df9226535c55eacff80217c4ab70f77639b6..89649e7a9b2dc138bb9618d4e5770e76f62dc7e0 100755 (executable)
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
git checkout master &&
git config receive.denyCurrentBranch warn) &&
git push testrepo master 2>stderr &&
- grep "warning.*this may cause confusion" stderr
+ grep "warning: updating the current branch" stderr
'
test_expect_success 'deny push to HEAD of non-bare repository' '
git config receive.denyCurrentBranch true &&
git config core.bare true) &&
git push testrepo master 2>stderr &&
- ! grep "warning.*this may cause confusion" stderr
+ ! grep "warning: updating the current branch" stderr
'
test_expect_success 'allow push to HEAD of non-bare repository (config)' '
git config receive.denyCurrentBranch false
) &&
git push testrepo master 2>stderr &&
- ! grep "warning.*this may cause confusion" stderr
+ ! grep "warning: updating the current branch" stderr
'
test_expect_success 'fetch with branches' '
index 6dfc55ad61246e1520a4909e02603b5328fee2a2..96be5236a2faf7178edc3c60094ecc37da741a65 100755 (executable)
)
'
+test_expect_success 'alice works and pushes yet again' '
+ (
+ # Alice does not care what Bob does. She does not
+ # even have to be aware of his existence. She just
+ # keeps working and pushing
+ cd alice-work &&
+ echo more and more alice >file &&
+ git commit -a -m sixth.1 &&
+ echo more and more alice >>file &&
+ git commit -a -m sixth.2 &&
+ echo more and more alice >>file &&
+ git commit -a -m sixth.3 &&
+ git push ../alice-pub
+ )
+'
+
+test_expect_success 'bob works and pushes again' '
+ (
+ cd alice-pub &&
+ git cat-file commit master >../bob-work/commit
+ )
+ (
+ # This time Bob does not pull from Alice, and
+ # the master branch at her public repository points
+ # at a commit Bob does not fully know about, but
+ # he happens to have the commit object (but not the
+ # necessary tree) in his repository from Alice.
+ # This should not prevent the push by Bob from
+ # succeeding.
+ cd bob-work &&
+ git hash-object -t commit -w commit &&
+ echo even more bob >file &&
+ git commit -a -m seventh &&
+ git push ../bob-pub
+ )
+'
+
test_done
diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh
index c236b5e83beed8997e8f6c3a50f0bb74f73f3f33..10e5fd0d5a5c38eaa102d88e607f2585e1157526 100755 (executable)
--- a/t/t5540-http-push.sh
+++ b/t/t5540-http-push.sh
'
+x1="[0-9a-f]"
+x2="$x1$x1"
+x5="$x1$x1$x1$x1$x1"
+x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1"
+x40="$x38$x2"
+
+test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' '
+ sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log |
+ grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] "
+
+'
+
stop_httpd
test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 78a3fa639c97b7dce054d89c74c67a855d0d7954..44793f2eee0e0c14802ecedfe0637cc6855f51b6 100755 (executable)
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
'
+test_expect_success 'clone to an existing empty directory' '
+ mkdir target-3 &&
+ git clone src target-3 &&
+ T=$( cd target-3 && git rev-parse HEAD ) &&
+ S=$( cd src && git rev-parse HEAD ) &&
+ test "$T" = "$S"
+'
+
+test_expect_success 'clone to an existing non-empty directory' '
+ mkdir target-4 &&
+ >target-4/Fakefile &&
+ test_must_fail git clone src target-4
+'
+
+test_expect_success 'clone to an existing path' '
+ >target-5 &&
+ test_must_fail git clone src target-5
+'
+
+test_expect_success 'clone a void' '
+ mkdir src-0 &&
+ (
+ cd src-0 && git init
+ ) &&
+ git clone src-0 target-6 &&
+ (
+ cd src-0 && test_commit A
+ ) &&
+ git clone src-0 target-7 &&
+ # There is no reason to insist they are bit-for-bit
+ # identical, but this test should suffice for now.
+ test_cmp target-6/.git/config target-7/.git/config
+'
+
test_done
diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh
index fe0fda282ca4f085f506c743848966fa6bb93591..3559d179647035a6ad8783aabfbecb8edcd4c254 100755 (executable)
--- a/t/t5701-clone-local.sh
+++ b/t/t5701-clone-local.sh
test ! -e .git/refs/heads/master
'
+test_expect_success 'clone empty repository' '
+ cd "$D" &&
+ mkdir empty &&
+ (cd empty && git init) &&
+ git clone empty empty-clone &&
+ test_tick &&
+ (cd empty-clone
+ echo "content" >> foo &&
+ git add foo &&
+ git commit -m "Initial commit" &&
+ git push origin master &&
+ expected=$(git rev-parse master) &&
+ actual=$(git --git-dir=../empty/.git rev-parse master) &&
+ test $actual = $expected)
+'
+
test_done
diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh
--- /dev/null
+++ b/t/t5704-bundle.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='some bundle related tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ : > file &&
+ git add file &&
+ test_tick &&
+ git commit -m initial &&
+ test_tick &&
+ git tag -m tag tag &&
+ : > file2 &&
+ git add file2 &&
+ : > file3 &&
+ test_tick &&
+ git commit -m second &&
+ git add file3 &&
+ test_tick &&
+ git commit -m third
+
+'
+
+test_expect_success 'tags can be excluded by rev-list options' '
+
+ git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
+ git ls-remote bundle > output &&
+ ! grep tag output
+
+'
+
+test_done
index 86bf7e14ba5bb9a76ef00cb5a3564e326f674a18..59d1f6283bec5cf7740d988dfd090c1463389c71 100755 (executable)
test_format() {
cat >expect.$1
test_expect_success "format $1" "
-git rev-list --pretty=format:$2 master >output.$1 &&
+git rev-list --pretty=format:'$2' master >output.$1 &&
test_cmp expect.$1 output.$1
"
}
\e[31mfoo\e[32mbar\e[34mbaz\e[mxyzzy
EOF
+test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+\e[1;31;43mfoo\e[m
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+\e[1;31;43mfoo\e[m
+EOF
+
cat >commit-msg <<'EOF'
Test printing of complex bodies
index dd7eac84ea191fb797075eca3706498edad68b32..052a6c90f5a184ddc82f2db1a2907a1b1104166c 100755 (executable)
fi
'
+# $HASH1 is good, $HASH4 is both skipped and bad, we skip $HASH3
+# and $HASH2 is good,
+# so we should not be able to tell the first bad commit
+# among $HASH3 and $HASH4
+test_expect_success 'bisect skip: with commit both bad and skipped' '
+ git bisect start &&
+ git bisect skip &&
+ git bisect bad &&
+ git bisect good $HASH1 &&
+ git bisect skip &&
+ if git bisect good > my_bisect_log.txt
+ then
+ echo Oops, should have failed.
+ false
+ else
+ test $? -eq 2 &&
+ grep "first bad commit could be any of" my_bisect_log.txt &&
+ ! grep $HASH1 my_bisect_log.txt &&
+ ! grep $HASH2 my_bisect_log.txt &&
+ grep $HASH3 my_bisect_log.txt &&
+ grep $HASH4 my_bisect_log.txt &&
+ git bisect reset
+ fi
+'
+
# We want to automatically find the commit that
# introduced "Another" into hello.
test_expect_success \
index b0a9d7d536314ec842b141c09ba0d6f8b06b6288..329c851685b1c663ee88d45a5d21d452a293fa8e 100755 (executable)
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
'
test_expect_success 'rewrite bare repository identically' '
- (git config core.bare true && cd .git && git filter-branch branch)
+ (git config core.bare true && cd .git &&
+ git filter-branch branch > filter-output 2>&1 &&
+ ! fgrep fatal filter-output)
'
git config core.bare false
test_expect_success 'result is really identical' '
test $H = $(git rev-parse HEAD)
'
+TRASHDIR=$(pwd)
+test_expect_success 'correct GIT_DIR while using -d' '
+ mkdir drepo &&
+ ( cd drepo &&
+ git init &&
+ test_commit drepo &&
+ git filter-branch -d "$TRASHDIR/dfoo" \
+ --index-filter "cp \"$TRASHDIR\"/dfoo/backup-refs \"$TRASHDIR\"" \
+ ) &&
+ grep drepo "$TRASHDIR/backup-refs"
+'
+
+test_expect_success 'Fail if commit filter fails' '
+ test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD
+'
+
test_expect_success 'rewrite, renaming a specific file' '
git filter-branch -f --tree-filter "mv d doh || :" HEAD
'
test_cmp expect actual
'
+test_expect_success 'Prune empty commits' '
+ git rev-list HEAD > expect &&
+ make_commit to_remove &&
+ git filter-branch -f --index-filter "git update-index --remove to_remove" --prune-empty HEAD &&
+ git rev-list HEAD > actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index f377fea4bb1d32c95096defb9fb08b291b67dbff..69501e2711f26a7c498d545520dcf47c00d9b1e1 100755 (executable)
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
git cat-file tag tag-from-subdir-2 | grep "in sub directory"
'
+# create a few more commits to test --contains
+
+hash1=$(git rev-parse HEAD)
+
+test_expect_success 'creating second commit and tag' '
+ echo foo-2.0 >foo &&
+ git add foo &&
+ git commit -m second
+ git tag v2.0
+'
+
+hash2=$(git rev-parse HEAD)
+
+test_expect_success 'creating third commit without tag' '
+ echo foo-dev >foo &&
+ git add foo &&
+ git commit -m third
+'
+
+hash3=$(git rev-parse HEAD)
+
+# simple linear checks of --continue
+
+cat > expected <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+v2.0
+EOF
+
+test_expect_success 'checking that first commit is in all tags (hash)' "
+ git tag -l --contains $hash1 v* >actual
+ test_cmp expected actual
+"
+
+# other ways of specifying the commit
+test_expect_success 'checking that first commit is in all tags (tag)' "
+ git tag -l --contains v1.0 v* >actual
+ test_cmp expected actual
+"
+
+test_expect_success 'checking that first commit is in all tags (relative)' "
+ git tag -l --contains HEAD~2 v* >actual
+ test_cmp expected actual
+"
+
+cat > expected <<EOF
+v2.0
+EOF
+
+test_expect_success 'checking that second commit only has one tag' "
+ git tag -l --contains $hash2 v* >actual
+ test_cmp expected actual
+"
+
+
+cat > expected <<EOF
+EOF
+
+test_expect_success 'checking that third commit has no tags' "
+ git tag -l --contains $hash3 v* >actual
+ test_cmp expected actual
+"
+
+# how about a simple merge?
+
+test_expect_success 'creating simple branch' '
+ git branch stable v2.0 &&
+ git checkout stable &&
+ echo foo-3.0 > foo &&
+ git commit foo -m fourth
+ git tag v3.0
+'
+
+hash4=$(git rev-parse HEAD)
+
+cat > expected <<EOF
+v3.0
+EOF
+
+test_expect_success 'checking that branch head only has one tag' "
+ git tag -l --contains $hash4 v* >actual
+ test_cmp expected actual
+"
+
+test_expect_success 'merging original branch into this branch' '
+ git merge --strategy=ours master &&
+ git tag v4.0
+'
+
+cat > expected <<EOF
+v4.0
+EOF
+
+test_expect_success 'checking that original branch head has one tag now' "
+ git tag -l --contains $hash3 v* >actual
+ test_cmp expected actual
+"
+
+cat > expected <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+v2.0
+v3.0
+v4.0
+EOF
+
+test_expect_success 'checking that initial commit is in all tags' "
+ git tag -l --contains $hash1 v* >actual
+ test_cmp expected actual
+"
+
# mixing modes and options:
test_expect_success 'mixing incompatibles modes and options is forbidden' '
index 2ec7ac6a510c5b83bc1ee6ce428379beb7a8b5ef..b8cb2df6670a18ebf54fecbb97a48d0c07a06e2b 100755 (executable)
'
+test_expect_success 'ls-files gracefully handles trailing slash' '
+
+ test "init" = "$(git ls-files init/)"
+
+'
+
+test_expect_success 'submodule <invalid-path> warns' '
+
+ git submodule no-such-submodule 2> output.err &&
+ grep "^error: .*no-such-submodule" output.err
+
+'
+
test_done
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
index 6e18a96319bef5c4ddfbc86ce79b02b364f04387..5998baf27b5ad0fb32bf0fd42023d029a65d0e6b 100755 (executable)
--- a/t/t7500-commit.sh
+++ b/t/t7500-commit.sh
test_expect_success '--signoff' '
echo "yet another content *narf*" >> foo &&
- echo "zort" | (
- test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit -s -F - foo
- ) &&
+ echo "zort" | git commit -s -F - foo &&
git cat-file commit HEAD | sed "1,/^$/d" > output &&
test_cmp expect output
'
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 63bfc6d8b3f6917cad1b51a73a84ba61762b220d..b4e2b4db8421c24cb714fcad7cd6ec00ea88c016 100755 (executable)
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
"showing committed revisions" \
"git rev-list HEAD >current"
+cat >editor <<\EOF
+#!/bin/sh
+sed -e "s/good/bad/g" < "$1" > "$1-"
+mv "$1-" "$1"
+EOF
+chmod 755 editor
+
+cat >msg <<EOF
+A good commit message.
+EOF
+
+test_expect_success \
+ 'editor not invoked if -F is given' '
+ echo "moo" >file &&
+ VISUAL=./editor git commit -a -F msg &&
+ git show -s --pretty=format:"%s" | grep -q good &&
+ echo "quack" >file &&
+ echo "Another good message." | VISUAL=./editor git commit -a -F - &&
+ git show -s --pretty=format:"%s" | grep -q good
+ '
# We could just check the head sha1, but checking each commit makes it
# easier to isolate bugs.
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
index 09fa5f115c9fabe1fec60a5597439b2c7f9ded6d..e768c3eb2d48a9af2fc92729a6c20126f67ec866 100755 (executable)
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
. ./test-lib.sh
+# All the mergetool test work by checking out a temporary branch based
+# off 'branch1' and then merging in master and checking the results of
+# running mergetool
+
test_expect_success 'setup' '
echo master >file1 &&
- git add file1 &&
+ mkdir subdir &&
+ echo master sub >subdir/file3 &&
+ git add file1 subdir/file3 &&
git commit -m "added file1" &&
+
git checkout -b branch1 master &&
echo branch1 change >file1 &&
echo branch1 newfile >file2 &&
- git add file1 file2 &&
+ echo branch1 sub >subdir/file3 &&
+ git add file1 file2 subdir/file3 &&
git commit -m "branch1 changes" &&
- git checkout -b branch2 master &&
- echo branch2 change >file1 &&
- echo branch2 newfile >file2 &&
- git add file1 file2 &&
- git commit -m "branch2 changes" &&
+
git checkout master &&
echo master updated >file1 &&
echo master new >file2 &&
- git add file1 file2 &&
- git commit -m "master updates"
-'
+ echo master new sub >subdir/file3 &&
+ git add file1 file2 subdir/file3 &&
+ git commit -m "master updates" &&
-test_expect_success 'custom mergetool' '
git config merge.tool mytool &&
git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" &&
- git config mergetool.mytool.trustExitCode true &&
- git checkout branch1 &&
+ git config mergetool.mytool.trustExitCode true
+'
+
+test_expect_success 'custom mergetool' '
+ git checkout -b test1 branch1 &&
test_must_fail git merge master >/dev/null 2>&1 &&
- ( yes "" | git mergetool file1>/dev/null 2>&1 ) &&
- ( yes "" | git mergetool file2>/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
test "$(cat file1)" = "master updated" &&
test "$(cat file2)" = "master new" &&
- git commit -m "branch1 resolved with mergetool"
+ test "$(cat subdir/file3)" = "master new sub" &&
+ git commit -m "branch1 resolved with mergetool"
'
+test_expect_success 'mergetool crlf' '
+ git config core.autocrlf true &&
+ git checkout -b test2 branch1
+ test_must_fail git merge master >/dev/null 2>&1 &&
+ ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+ ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
+ test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" &&
+ test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" &&
+ test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" &&
+ git commit -m "branch1 resolved with mergetool - autocrlf" &&
+ git config core.autocrlf false &&
+ git reset --hard
+'
+
+test_expect_success 'mergetool in subdir' '
+ git checkout -b test3 branch1
+ cd subdir && (
+ test_must_fail git merge master >/dev/null 2>&1 &&
+ ( yes "" | git mergetool file3 >/dev/null 2>&1 ) &&
+ test "$(cat file3)" = "master new sub" )
+'
+
+# We can't merge files from parent directories when running mergetool
+# from a subdir. Is this a bug?
+#
+#test_expect_failure 'mergetool in subdir' '
+# cd subdir && (
+# ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
+# ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+# test "$(cat ../file1)" = "master updated" &&
+# test "$(cat ../file2)" = "master new" &&
+# git commit -m "branch1 resolved with mergetool - subdir" )
+#'
+
test_done
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 3f602ea7de498e9e60bd61c8f58b66947c9923fe..f5682d66db2832311774fb68b7264002dfeb091f 100755 (executable)
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
done
'
+test_expect_failure 'packed obs in alt ODB are repacked when local repo has packs' '
+ rm -f .git/objects/pack/* &&
+ echo new_content >> file1 &&
+ git add file1 &&
+ git commit -m more_content &&
+ git repack &&
+ git repack -a -d &&
+ myidx=$(ls -1 .git/objects/pack/*.idx) &&
+ test -f "$myidx" &&
+ for p in alt_objects/pack/*.idx; do
+ git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p"
+ done | while read sha1 rest; do
+ if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then
+ echo "Missing object in local pack: $sha1"
+ return 1
+ fi
+ done
+'
+
test_done
index 63a8225ae5c7ffc265e28c9bf17bbec87ab339d8..5babdf26e625933268b911cc6e81f6a448f7f78d 100755 (executable)
compare_mtimes ()
{
- perl -e 'my $reference = shift;
- foreach my $file (@ARGV) {
- exit(1) unless(-f $file && -M $file == -M $reference);
- }
- exit(0);
- ' -- "$@"
+ read tref rest &&
+ while read t rest; do
+ test "$tref" = "$t" || break
+ done
}
test_expect_success '-A without -d option leaves unreachable objects packed' '
tmppack=".git/objects/pack/tmp_pack" &&
ln "$packfile" "$tmppack" &&
git repack -A -l -d &&
- compare_mtimes "$tmppack" "$fsha1path" "$csha1path" "$tsha1path"
+ test-chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \
+ > mtimes &&
+ compare_mtimes < mtimes
'
test_done
index 8a9dde44d57b792d7142f082aa18274a13d532f3..9c7b1ad18bca943e6c5b6e50f55d914ccadbfce8 100755 (executable)
}
compare_svn_head_with () {
- LC_ALL=en_US.UTF-8 svn log --limit 1 `git svn info --url` | \
- sed -e 1,3d -e "/^-\{1,\}\$/d" >current &&
+ # extract just the log message and strip out committer info.
+ # don't use --limit here since svn 1.1.x doesn't have it,
+ LC_ALL=en_US.UTF-8 svn log `git svn info --url` | perl -w -e '
+ use bytes;
+ $/ = ("-"x72) . "\n";
+ my @x = <STDIN>;
+ @x = split(/\n/, $x[1]);
+ splice(@x, 0, 2);
+ $x[-1] = "";
+ print join("\n", @x);
+ ' > current &&
test_cmp current "$1"
}
diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Eric Wong
+#
+
+test_description='git svn authors file tests'
+
+. ./lib-git-svn.sh
+
+cat > svn-authors <<EOF
+aa = AAAAAAA AAAAAAA <aa@example.com>
+bb = BBBBBBB BBBBBBB <bb@example.com>
+EOF
+
+test_expect_success 'setup svnrepo' '
+ for i in aa bb cc dd
+ do
+ svn mkdir -m $i --username $i "$svnrepo"/$i
+ done
+ '
+
+test_expect_success 'start import with incomplete authors file' '
+ ! git svn clone --authors-file=svn-authors "$svnrepo" x
+ '
+
+test_expect_success 'imported 2 revisions successfully' '
+ (
+ cd x
+ test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 2 &&
+ git rev-list -1 --pretty=raw refs/remotes/git-svn | \
+ grep "^author BBBBBBB BBBBBBB <bb@example\.com> " &&
+ git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \
+ grep "^author AAAAAAA AAAAAAA <aa@example\.com> "
+ )
+ '
+
+cat >> svn-authors <<EOF
+cc = CCCCCCC CCCCCCC <cc@example.com>
+dd = DDDDDDD DDDDDDD <dd@example.com>
+EOF
+
+test_expect_success 'continues to import once authors have been added' '
+ (
+ cd x
+ git svn fetch --authors-file=../svn-authors &&
+ test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 4 &&
+ git rev-list -1 --pretty=raw refs/remotes/git-svn | \
+ grep "^author DDDDDDD DDDDDDD <dd@example\.com> " &&
+ git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \
+ grep "^author CCCCCCC CCCCCCC <cc@example\.com> "
+ )
+ '
+
+test_expect_success 'authors-file against globs' '
+ svn mkdir -m globs --username aa \
+ "$svnrepo"/aa/trunk "$svnrepo"/aa/branches "$svnrepo"/aa/tags &&
+ git svn clone --authors-file=svn-authors -s "$svnrepo"/aa aa-work &&
+ for i in bb ee cc
+ do
+ branch="aa/branches/$i"
+ svn mkdir -m "$branch" --username $i "$svnrepo/$branch"
+ done
+ '
+
+test_expect_success 'fetch fails on ee' '
+ ( cd aa-work && ! git svn fetch --authors-file=../svn-authors )
+ '
+
+tmp_config_get () {
+ GIT_CONFIG=.git/svn/.metadata git config --get "$1"
+}
+
+test_expect_success 'failure happened without negative side effects' '
+ (
+ cd aa-work &&
+ test 6 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" &&
+ test 6 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`"
+ )
+ '
+
+cat >> svn-authors <<EOF
+ee = EEEEEEE EEEEEEE <ee@example.com>
+EOF
+
+test_expect_success 'fetch continues after authors-file is fixed' '
+ (
+ cd aa-work &&
+ git svn fetch --authors-file=../svn-authors &&
+ test 8 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" &&
+ test 8 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`"
+ )
+ '
+
+test_done
diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh
--- /dev/null
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+test_description='test that git handles an svn repository with empty symlinks'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile' '
+ svnadmin load "$rawsvnrepo" <<EOF
+SVN-fs-dump-format-version: 2
+
+UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-26T07:17:27.590577Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 4
+test
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-26T07:18:03.511836Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 33
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 13
+bar => doink
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-27T03:55:31.601672Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9
+Content-length: 10
+
+link doink
+
+EOF
+'
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
+test_expect_success 'enable broken symlink workaround' \
+ '(cd x && git config svn.brokenSymlinkWorkaround true)'
+test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+ '(cd x && git svn rebase)'
+test_expect_success '"bar" becomes a symlink' 'test -L x/bar'
+
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y'
+test_expect_success 'disable broken symlink workaround' \
+ '(cd y && git config svn.brokenSymlinkWorkaround false)'
+test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+ '(cd y && git svn rebase)'
+test_expect_success '"bar" does not become a symlink' '! test -L y/bar'
+
+# svn.brokenSymlinkWorkaround is unset
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" z'
+test_expect_success '"bar" is an empty file' 'test -f z/bar && ! test -s z/bar'
+test_expect_success 'get "bar" => symlink fix from svn' \
+ '(cd z && git svn rebase)'
+test_expect_success '"bar" does not become a symlink' '! test -L z/bar'
+
+
+test_done
diff --git a/t/t9132-git-svn-broken-symlink.sh b/t/t9132-git-svn-broken-symlink.sh
--- /dev/null
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+test_description='test that git handles an svn repository with empty symlinks'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile' '
+ svnadmin load "$rawsvnrepo" <<EOF
+SVN-fs-dump-format-version: 2
+
+UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-26T07:17:27.590577Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 4
+test
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-26T07:18:03.511836Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 4
+Text-content-md5: 912ec803b2ce49e4a541068d495ab570
+Content-length: 37
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+asdf
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 13
+bar => doink
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2008-11-27T03:55:31.601672Z
+PROPS-END
+
+Node-path: bar
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9
+Content-length: 10
+
+link doink
+
+EOF
+'
+
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
+
+test_expect_success '"bar" is a symlink that points to "asdf"' '
+ test -L x/bar &&
+ (cd x && test xasdf = x"`git cat-file blob HEAD:bar`")
+'
+
+test_expect_success 'get "bar" => symlink fix from svn' '
+ (cd x && git svn rebase)
+'
+
+test_expect_success '"bar" remains a proper symlink' '
+ test -L x/bar &&
+ (cd x && test xdoink = x"`git cat-file blob HEAD:bar`")
+'
+
+test_done
diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Eric Wong
+#
+
+test_description='git svn property tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup repo with a git repo inside it' '
+ svn co "$svnrepo" s &&
+ (
+ cd s &&
+ git init &&
+ test -f .git/HEAD &&
+ > .git/a &&
+ echo a > a &&
+ svn add .git a &&
+ svn commit -m "create a nested git repo" &&
+ svn up &&
+ echo hi >> .git/a &&
+ svn commit -m "modify .git/a" &&
+ svn up
+ )
+'
+
+test_expect_success 'clone an SVN repo containing a git repo' '
+ git svn clone "$svnrepo" g &&
+ echo a > expect &&
+ test_cmp expect g/a
+'
+
+test_expect_success 'SVN-side change outside of .git' '
+ (
+ cd s &&
+ echo b >> a &&
+ svn commit -m "SVN-side change outside of .git" &&
+ svn up &&
+ svn log -v | fgrep "SVN-side change outside of .git"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+ (
+ cd g &&
+ git svn rebase &&
+ echo a > expect &&
+ echo b >> expect &&
+ test_cmp a expect &&
+ rm expect
+ )
+'
+
+test_expect_success 'SVN-side change inside of .git' '
+ (
+ cd s &&
+ git add a &&
+ git commit -m "add a inside an SVN repo" &&
+ git log &&
+ svn add --force .git &&
+ svn commit -m "SVN-side change inside of .git" &&
+ svn up &&
+ svn log -v | fgrep "SVN-side change inside of .git"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+ (
+ cd g &&
+ git svn rebase &&
+ echo a > expect &&
+ echo b >> expect &&
+ test_cmp a expect &&
+ rm expect
+ )
+'
+
+test_expect_success 'SVN-side change in and out of .git' '
+ (
+ cd s &&
+ echo c >> a &&
+ git add a &&
+ git commit -m "add a inside an SVN repo" &&
+ svn commit -m "SVN-side change in and out of .git" &&
+ svn up &&
+ svn log -v | fgrep "SVN-side change in and out of .git"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo again' '
+ (
+ cd g &&
+ git svn rebase &&
+ echo a > expect &&
+ echo b >> expect &&
+ echo c >> expect &&
+ test_cmp a expect &&
+ rm expect
+ )
+'
+
+test_done
diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Vitaly Shukela
+# Copyright (c) 2009 Eric Wong
+#
+
+test_description='git svn property tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup test repository' '
+ svn co "$svnrepo" s &&
+ (
+ cd s &&
+ mkdir qqq www &&
+ echo test_qqq > qqq/test_qqq.txt &&
+ echo test_www > www/test_www.txt &&
+ svn add qqq &&
+ svn add www &&
+ svn commit -m "create some files" &&
+ svn up &&
+ echo hi >> www/test_www.txt &&
+ svn commit -m "modify www/test_www.txt" &&
+ svn up
+ )
+'
+
+test_expect_success 'clone an SVN repository with ignored www directory' '
+ git svn clone --ignore-paths="^www" "$svnrepo" g &&
+ echo test_qqq > expect &&
+ for i in g/*/*.txt; do cat $i >> expect2; done &&
+ test_cmp expect expect2
+'
+
+test_expect_success 'SVN-side change outside of www' '
+ (
+ cd s &&
+ echo b >> qqq/test_qqq.txt &&
+ svn commit -m "SVN-side change outside of www" &&
+ svn up &&
+ svn log -v | fgrep "SVN-side change outside of www"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+ (
+ cd g &&
+ git svn rebase --ignore-paths="^www" &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'SVN-side change inside of ignored www' '
+ (
+ cd s &&
+ echo zaq >> www/test_www.txt
+ svn commit -m "SVN-side change inside of www/test_www.txt" &&
+ svn up &&
+ svn log -v | fgrep "SVN-side change inside of www/test_www.txt"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo' '
+ (
+ cd g &&
+ git svn rebase --ignore-paths="^www" &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'SVN-side change in and out of ignored www' '
+ (
+ cd s &&
+ echo cvf >> www/test_www.txt
+ echo ygg >> qqq/test_qqq.txt
+ svn commit -m "SVN-side change in and out of ignored www" &&
+ svn up &&
+ svn log -v | fgrep "SVN-side change in and out of ignored www"
+ )
+'
+
+test_expect_success 'update git svn-cloned repo again' '
+ (
+ cd g &&
+ git svn rebase --ignore-paths="^www" &&
+ printf "test_qqq\nb\nygg\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_done
diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+test_description='test moved svn branch with missing empty files'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile' '
+ svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9135/svn.dump"
+ '
+
+test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
+
+test_expect_success 'test that b1 exists and is empty' '
+ (cd x && test -f b1 && ! test -s b1)
+ '
+
+test_done
diff --git a/t/t9135/svn.dump b/t/t9135/svn.dump
--- /dev/null
+++ b/t/t9135/svn.dump
@@ -0,0 +1,192 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1f80e919-e9e3-4d80-a3ae-d9f21095e27b
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-10T19:23:16.424027Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 20
+init standard layout
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:17.195072Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 18
+branch-b off trunk
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:19.160095Z
+PROPS-END
+
+Node-path: branches/branch-b
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 17
+add empty file b1
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:20.194568Z
+PROPS-END
+
+Node-path: branches/branch-b/b1
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 8
+branch-c
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.169100Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 23
+oops, wrong branchpoint
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.253557Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 127
+Content-length: 127
+
+K 7
+svn:log
+V 24
+branch-c off of branch-b
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-10T19:23:21.314659Z
+PROPS-END
+
+Node-path: branches/branch-c
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch-b
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
diff --git a/t/t9136-git-svn-recreated-branch-empty-file.sh b/t/t9136-git-svn-recreated-branch-empty-file.sh
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+test_description='test recreated svn branch with empty files'
+
+. ./lib-git-svn.sh
+test_expect_success 'load svn dumpfile' '
+ svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9136/svn.dump"
+ '
+
+test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x'
+
+test_done
diff --git a/t/t9136/svn.dump b/t/t9136/svn.dump
--- /dev/null
+++ b/t/t9136/svn.dump
@@ -0,0 +1,192 @@
+SVN-fs-dump-format-version: 2
+
+UUID: eecae021-8f16-48da-969d-79beb8ae6ea5
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-22T00:50:56.292890Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 4
+init
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:57.192384Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 3
+1.0
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.124724Z
+PROPS-END
+
+Node-path: tags/1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 9
+1.0.1-bad
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.151727Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: tags/1.0
+
+
+Revision-number: 4
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 9
+Wrong tag
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.167427Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 10
+1.0-branch
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.184498Z
+PROPS-END
+
+Node-path: branches/1.0
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/1.0
+
+
+Revision-number: 6
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 10
+1.0.1-good
+K 10
+svn:author
+V 8
+john.doe
+K 8
+svn:date
+V 27
+2009-02-22T00:50:58.200695Z
+PROPS-END
+
+Node-path: tags/1.0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/1.0
+
+
index 43cd6eecbac70f1ab63f19bf972f17c1f8188d5e..6ed10d0933daa493e7a32296b38e9a32a23a725a 100755 (executable)
GATEWAY_INTERFACE="CGI/1.1"
HTTP_ACCEPT="*/*"
REQUEST_METHOD="GET"
+ SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl"
QUERY_STRING=""$1""
PATH_INFO=""$2""
- export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD QUERY_STRING PATH_INFO
+ export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \
+ SCRIPT_NAME QUERY_STRING PATH_INFO
GITWEB_CONFIG=$(pwd)/gitweb_config.perl
export GITWEB_CONFIG
# written to web server logs, so we are not interested in that:
# we are interested only in properly formatted errors/warnings
rm -f gitweb.log &&
- perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \
+ perl -- "$SCRIPT_NAME" \
>/dev/null 2>gitweb.log &&
if grep "^[[]" gitweb.log >/dev/null 2>&1; then false; else true; fi
\$feature{'snapshot'}{'override'} = 1;
EOF
+test_expect_success \
+ 'config override: tree view, features not overridden in repo config' \
+ 'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
test_expect_success \
'config override: tree view, features disabled in repo config' \
'git config gitweb.blame no &&
test_debug 'cat gitweb.log'
test_expect_success \
- 'config override: tree view, features enabled in repo config' \
+ 'config override: tree view, features enabled in repo config (1)' \
'git config gitweb.blame yes &&
git config gitweb.snapshot "zip,tgz, tbz2" &&
gitweb_run "p=.git;a=tree"'
test_debug 'cat gitweb.log'
+cat >.git/config <<\EOF
+# testing noval and alternate separator
+[gitweb]
+ blame
+ snapshot = zip tgz
+EOF
+test_expect_success \
+ 'config override: tree view, features enabled in repo config (2)' \
+ 'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
# ----------------------------------------------------------------------
# non-ASCII in README.html
diff --git a/t/test-lib.sh b/t/test-lib.sh
index fc0e1932e6397403c4efec753e8f4e23fb3cd26a..0c455929e4e52795e616c3e2635f5899dff665c2 100644 (file)
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
error () {
say_color error "error: $*"
- trap - exit
+ trap - EXIT
exit 1
}
exit 1
}
-trap 'die' exit
+trap 'die' EXIT
# The semantics of the editor variables are that of invoking
# sh -c "$EDITOR \"$@\"" files ...
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
}
+# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+#
+# This will commit a file with the given contents and the given commit
+# message. It will also add a tag with <message> as name.
+#
+# Both <file> and <contents> default to <message>.
+
+test_commit () {
+ file=${2:-"$1.t"}
+ echo "${3-$1}" > "$file" &&
+ git add "$file" &&
+ test_tick &&
+ git commit -m "$1" &&
+ git tag "$1"
+}
+
+# Call test_merge with the arguments "<message> <commit>", where <commit>
+# can be a tag pointing to the commit-to-merge.
+
+test_merge () {
+ test_tick &&
+ git merge -m "$1" "$2" &&
+ git tag "$1"
+}
+
# You are not expected to call test_ok_ and test_failure_ directly, use
# the text_expect_* functions instead.
say_color error "FAIL $test_count: $1"
shift
echo "$@" | sed -e 's/^/ /'
- test "$immediate" = "" || { trap - exit; exit 1; }
+ test "$immediate" = "" || { trap - EXIT; exit 1; }
}
test_known_broken_ok_ () {
}
test_done () {
- trap - exit
+ trap - EXIT
test_results_dir="$TEST_DIRECTORY/test-results"
mkdir -p "$test_results_dir"
test_results_path="$test_results_dir/${0%-*}-$$"
test="trash directory.$(basename "$0" .sh)"
test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
rm -fr "$test" || {
- trap - exit
+ trap - EXIT
echo >&5 "FATAL: Cannot prepare test area"
exit 1
}
diff --git a/test-ctype.c b/test-ctype.c
--- /dev/null
+++ b/test-ctype.c
@@ -0,0 +1,78 @@
+#include "cache.h"
+
+
+static int test_isdigit(int c)
+{
+ return isdigit(c);
+}
+
+static int test_isspace(int c)
+{
+ return isspace(c);
+}
+
+static int test_isalpha(int c)
+{
+ return isalpha(c);
+}
+
+static int test_isalnum(int c)
+{
+ return isalnum(c);
+}
+
+static int test_is_glob_special(int c)
+{
+ return is_glob_special(c);
+}
+
+static int test_is_regex_special(int c)
+{
+ return is_regex_special(c);
+}
+
+#define DIGIT "0123456789"
+#define LOWER "abcdefghijklmnopqrstuvwxyz"
+#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+static const struct ctype_class {
+ const char *name;
+ int (*test_fn)(int);
+ const char *members;
+} classes[] = {
+ { "isdigit", test_isdigit, DIGIT },
+ { "isspace", test_isspace, " \n\r\t" },
+ { "isalpha", test_isalpha, LOWER UPPER },
+ { "isalnum", test_isalnum, LOWER UPPER DIGIT },
+ { "is_glob_special", test_is_glob_special, "*?[\\" },
+ { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" },
+ { NULL }
+};
+
+static int test_class(const struct ctype_class *test)
+{
+ int i, rc = 0;
+
+ for (i = 0; i < 256; i++) {
+ int expected = i ? !!strchr(test->members, i) : 0;
+ int actual = test->test_fn(i);
+
+ if (actual != expected) {
+ rc = 1;
+ printf("%s classifies char %d (0x%02x) wrongly\n",
+ test->name, i, i);
+ }
+ }
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ const struct ctype_class *test;
+ int rc = 0;
+
+ for (test = classes; test->name; test++)
+ rc |= test_class(test);
+
+ return rc;
+}
diff --git a/test-path-utils.c b/test-path-utils.c
index 2c0f5a37e8b9051b1db80aa04e9ea763fb8d772b..d261398d6c50aeefa7450b99caae23ee5cdff0d0 100644 (file)
--- a/test-path-utils.c
+++ b/test-path-utils.c
int main(int argc, char **argv)
{
- if (argc == 3 && !strcmp(argv[1], "normalize_absolute_path")) {
+ if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
char *buf = xmalloc(PATH_MAX + 1);
- int rv = normalize_absolute_path(buf, argv[2]);
- assert(strlen(buf) == rv);
+ int rv = normalize_path_copy(buf, argv[2]);
+ if (rv)
+ buf = "++failed++";
puts(buf);
+ return 0;
}
if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) {
argc--;
argv++;
}
+ return 0;
}
if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
int len = longest_ancestor_length(argv[2], argv[3]);
printf("%d\n", len);
+ return 0;
}
- return 0;
+ if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
+ char *prefix = strip_path_suffix(argv[2], argv[3]);
+ printf("%s\n", prefix ? prefix : "(null)");
+ return 0;
+ }
+
+ fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+ argv[1] ? argv[1] : "(there was none)");
+ return 1;
}
diff --git a/test-sigchain.c b/test-sigchain.c
--- /dev/null
+++ b/test-sigchain.c
@@ -0,0 +1,22 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define X(f) \
+static void f(int sig) { \
+ puts(#f); \
+ fflush(stdout); \
+ sigchain_pop(sig); \
+ raise(sig); \
+}
+X(one)
+X(two)
+X(three)
+#undef X
+
+int main(int argc, char **argv) {
+ sigchain_push(SIGTERM, one);
+ sigchain_push(SIGTERM, two);
+ sigchain_push(SIGTERM, three);
+ raise(SIGTERM);
+ return 0;
+}
index 4713f9165c54405d51e81c3e90847120ee907e5d..4229ae1231d69aedd9f1aa8350989ddbe2bdb845 100644 (file)
--- a/trace.c
+++ b/trace.c
return fd;
}
- fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace);
+ fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace);
fprintf(stderr, "If you want to trace into a file, "
"then please set GIT_TRACE to an absolute pathname "
"(starting with /).\n");
diff --git a/transport.c b/transport.c
index 56831c57c5cf6500714c46c63581ca98662d58c3..9ad4a16c317b90770974aa5afbcf0986f4a5db91 100644 (file)
--- a/transport.c
+++ b/transport.c
memset (&list, 0, sizeof(list));
while ((de = readdir(dir))) {
- if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
- (de->d_name[1] == '.' &&
- de->d_name[2] == '\0')))
+ if (is_dot_or_dotdot(de->d_name))
continue;
ALLOC_GROW(list.entries, list.nr + 1, list.alloc);
list.entries[list.nr++] = xstrdup(de->d_name);
index 03e782a9cabc0a12ed5baec0ef59c99f19dbc843..25d2e29fa8b1dfb964b97a10898c77d8fe86ef78 100644 (file)
--- a/tree.c
+++ b/tree.c
case 0:
continue;
case READ_TREE_RECURSIVE:
- break;;
+ break;
default:
return -1;
}
if (retval)
return -1;
continue;
+ } else if (S_ISGITLINK(entry.mode)) {
+ int retval;
+ struct strbuf path;
+ unsigned int entrylen;
+ struct commit *commit;
+
+ entrylen = tree_entry_len(entry.path, entry.sha1);
+ strbuf_init(&path, baselen + entrylen + 1);
+ strbuf_add(&path, base, baselen);
+ strbuf_add(&path, entry.path, entrylen);
+ strbuf_addch(&path, '/');
+
+ commit = lookup_commit(entry.sha1);
+ if (!commit)
+ die("Commit %s in submodule path %s not found",
+ sha1_to_hex(entry.sha1), path.buf);
+
+ if (parse_commit(commit))
+ die("Invalid commit %s in submodule path %s",
+ sha1_to_hex(entry.sha1), path.buf);
+
+ retval = read_tree_recursive(commit->tree,
+ path.buf, path.len,
+ stage, match, fn, context);
+ strbuf_release(&path);
+ if (retval)
+ return -1;
+ continue;
}
}
return 0;
diff --git a/unpack-file.c b/unpack-file.c
index bcdc8bbb3b44a43aa43db6035a31478158e070af..75cd2f1a6adf3ea773a6acc3f1e7f122e30676e5 100644 (file)
--- a/unpack-file.c
+++ b/unpack-file.c
#include "cache.h"
#include "blob.h"
+#include "exec_cmd.h"
static char *create_temp_file(unsigned char *sha1)
{
{
unsigned char sha1[20];
+ git_extract_argv0_path(argv[0]);
+
if (argc != 2)
- usage("git-unpack-file <sha1>");
+ usage("git unpack-file <sha1>");
if (get_sha1(argv[1], sha1))
die("Not a valid object name %s", argv[1]);
diff --git a/unpack-trees.c b/unpack-trees.c
index 3a4e181af43add517a7a86511c17e4198552289c..e547282ed5c0027b35cbbd8e4f07bf968c6f2171 100644 (file)
--- a/unpack-trees.c
+++ b/unpack-trees.c
char *cp, *prev;
char *name = ce->name;
- if (has_symlink_leading_path(ce_namelen(ce), ce->name))
+ if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name))
return;
if (unlink(name))
return;
if (o->index_only || o->reset || !o->update)
return 0;
- if (has_symlink_leading_path(ce_namelen(ce), ce->name))
+ if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name))
return 0;
if (!lstat(ce->name, &st)) {
diff --git a/update-server-info.c b/update-server-info.c
index 7e8209ea4b43995737b36bc58db47e7dd6eadb19..7b38fd867bba6f8b8dcf9f2094769314b7f23e02 100644 (file)
--- a/update-server-info.c
+++ b/update-server-info.c
#include "cache.h"
+#include "exec_cmd.h"
static const char update_server_info_usage[] =
"git update-server-info [--force]";
if (i != ac)
usage(update_server_info_usage);
+ git_extract_argv0_path(av[0]);
+
setup_git_directory();
return !!update_server_info(force);
diff --git a/upload-pack.c b/upload-pack.c
index e5adbc011e0ab71eeb06a42c4bd40cbea0bf3fa2..19c24db643c0bceb6e1b960d411d657d2feb0f32 100644 (file)
--- a/upload-pack.c
+++ b/upload-pack.c
#include "list-objects.h"
#include "run-command.h"
-static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
+static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
/* bits #0..7 in revision.h, #8..10 in commit.c */
#define THEY_HAVE (1u << 11)
int i;
int strict = 0;
+ git_extract_argv0_path(argv[0]);
+
for (i = 1; i < argc; i++) {
char *arg = argv[i];
diff --git a/userdiff.c b/userdiff.c
index 3681062ebfef85af08d71ed6e1ff734804906d6a..d556da975197a718979575864e37c2b9b9f3327a 100644 (file)
--- a/userdiff.c
+++ b/userdiff.c
static int ndrivers;
static int drivers_alloc;
-#define FUNCNAME(name, pattern) \
- { name, NULL, -1, { pattern, REG_EXTENDED } }
+#define PATTERNS(name, pattern, word_regex) \
+ { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex }
static struct userdiff_driver builtin_drivers[] = {
-FUNCNAME("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$"),
-FUNCNAME("java",
+PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
+ "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("java",
"!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
- "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$"),
-FUNCNAME("objc",
+ "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]="
+ "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"
+ "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("objc",
/* Negate C statements that can look like functions */
"!^[ \t]*(do|for|if|else|return|switch|while)\n"
/* Objective-C methods */
/* C functions */
"^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$\n"
/* Objective-C class/protocol definitions */
- "^(@(implementation|interface|protocol)[ \t].*)$"),
-FUNCNAME("pascal",
+ "^(@(implementation|interface|protocol)[ \t].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
+ "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("pascal",
"^((procedure|function|constructor|destructor|interface|"
"implementation|initialization|finalization)[ \t]*.*)$"
"\n"
- "^(.*=[ \t]*(class|record).*)$"),
-FUNCNAME("php", "^[\t ]*((function|class).*)"),
-FUNCNAME("python", "^[ \t]*((class|def)[ \t].*)$"),
-FUNCNAME("ruby", "^[ \t]*((class|module|def)[ \t].*)$"),
-FUNCNAME("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$"),
-FUNCNAME("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$"),
+ "^(.*=[ \t]*(class|record).*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
+ "|<>|<=|>=|:=|\\.\\."
+ "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("php", "^[\t ]*((function|class).*)",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
+ "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"
+ "|[^[:space:]]|[\x80-\xff]+"),
+PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"
+ "|[^[:space:]|[\x80-\xff]+"),
+ /* -- */
+PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
+ /* -- */
+ "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
+ "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"
+ "|[^[:space:]|[\x80-\xff]+"),
+PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
+ "[={}\"]|[^={}\" \t]+"),
+PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
+ "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+|[^[:space:]]"),
+PATTERNS("cpp",
+ /* Jump targets or access declarations */
+ "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
+ /* C/++ functions/methods at top level */
+ "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
+ /* compound type at top level */
+ "^((struct|class|enum)[^;]*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"
+ "|[^[:space:]]|[\x80-\xff]+"),
{ "default", NULL, -1, { NULL, 0 } },
};
-#undef FUNCNAME
+#undef PATTERNS
static struct userdiff_driver driver_true = {
"diff=true",
return parse_string(&drv->external, k, v);
if ((drv = parse_driver(k, v, "textconv")))
return parse_string(&drv->textconv, k, v);
+ if ((drv = parse_driver(k, v, "wordregex")))
+ return parse_string(&drv->word_regex, k, v);
return 0;
}
diff --git a/userdiff.h b/userdiff.h
index ba2945770b379f51aa8da45d112a2ef896ec4c10..c3151594f5c0643fead757accc27bf1093cf4a68 100644 (file)
--- a/userdiff.h
+++ b/userdiff.h
const char *external;
int binary;
struct userdiff_funcname funcname;
+ const char *word_regex;
const char *textconv;
};
index dc3735364f85273c2a119b994ddb405c09dc395c..ddfdc5e2b88d346886f64e39a93ced85cc10a78e 100644 (file)
--- a/utf8.c
+++ b/utf8.c
return git_wcwidth(ch);
}
+/*
+ * Returns the total number of columns required by a null-terminated
+ * string, assuming that the string is utf8. Returns strlen() instead
+ * if the string does not look like a valid utf8 string.
+ */
+int utf8_strwidth(const char *string)
+{
+ int width = 0;
+ const char *orig = string;
+
+ while (1) {
+ if (!string)
+ return strlen(orig);
+ if (!*string)
+ return width;
+ width += utf8_width(&string, NULL);
+ }
+}
+
int is_utf8(const char *text)
{
while (*text) {
index 98cce1b038a908bec51ccd2f7e1c1f648cb429a1..2f1b14ff49ef3c73bee6f298ba396b96120c34b7 100644 (file)
--- a/utf8.h
+++ b/utf8.h
ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p);
int utf8_width(const char **start, size_t *remainder_p);
+int utf8_strwidth(const char *string);
int is_utf8(const char *text);
int is_encoding_utf8(const char *name);
index f1eb314e899c518b8dea54b4ff8f3923da7b12cf..7362ed87354a4bea98c28f9a8c315b7209c67fa2 100644 (file)
--- a/var.c
+++ b/var.c
* Copyright (C) Eric Biederman, 2005
*/
#include "cache.h"
+#include "exec_cmd.h"
static const char var_usage[] = "git var [-l | <variable>]";
usage(var_usage);
}
+ git_extract_argv0_path(argv[0]);
+
setup_git_directory_gently(&nongit);
val = NULL;
diff --git a/walker.c b/walker.c
index 679adab6a0fccef460acaf90b116b8c6cb9bd460..e57630e983488e5c0222dc47a5e32d6efb184762 100644 (file)
--- a/walker.c
+++ b/walker.c
static void report_missing(const struct object *obj)
{
char missing_hex[41];
- strcpy(missing_hex, sha1_to_hex(obj->sha1));;
+ strcpy(missing_hex, sha1_to_hex(obj->sha1));
fprintf(stderr, "Cannot obtain needed %s %s\n",
obj->type ? typename(obj->type): "object", missing_hex);
if (!is_null_sha1(current_commit_sha1))
diff --git a/wrapper.c b/wrapper.c
index c85ca52ec63a679a2da7bd8980ad4e2df4e38794..b07cdf299af66d4c503d5a788858b9cc56683143 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message");
return ret;
}
+
+int odb_mkstemp(char *template, size_t limit, const char *pattern)
+{
+ int fd;
+
+ snprintf(template, limit, "%s/%s",
+ get_object_directory(), pattern);
+ fd = mkstemp(template);
+ if (0 <= fd)
+ return fd;
+
+ /* slow path */
+ safe_create_leading_directories(template);
+ snprintf(template, limit, "%s/%s",
+ get_object_directory(), pattern);
+ return xmkstemp(template);
+}
+
+int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
+{
+ int fd;
+
+ snprintf(name, namesz, "%s/pack/pack-%s.keep",
+ get_object_directory(), sha1_to_hex(sha1));
+ fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ if (0 <= fd)
+ return fd;
+
+ /* slow path */
+ safe_create_leading_directories(name);
+ return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+}
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index 84fff583e2a6e3411da4260b9af98043fade1e49..4da052a3fff6768fca05b2f3399c5d87ec1daa2e 100644 (file)
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
#define XDF_IGNORE_WHITESPACE (1 << 2)
#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
+#define XDF_PATIENCE_DIFF (1 << 5)
#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
#define XDL_PATCH_NORMAL '-'
typedef struct s_xdemitconf {
long ctxlen;
+ long interhunkctxlen;
unsigned long flags;
find_func_t find_func;
void *find_func_priv;
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index 9d0324a38c2a1974648e67161fa0ed1b0f811233..3e97462bdd2ed72b4ec60a1eb3869e516609867b 100644 (file)
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
xdalgoenv_t xenv;
diffdata_t dd1, dd2;
+ if (xpp->flags & XDF_PATIENCE_DIFF)
+ return xdl_do_patience_diff(mf1, mf2, xpp, xe);
+
if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
return -1;
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
index 3e099dc445d6130f6a0ce2c6270a3b06d6ee119f..ad033a8e6a79600b6a3ba0cc16244ede0e9437ea 100644 (file)
--- a/xdiff/xdiffi.h
+++ b/xdiff/xdiffi.h
void xdl_free_script(xdchange_t *xscr);
int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg);
+int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *env);
#endif /* #if !defined(XDIFFI_H) */
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 4625c1b4215231dc343478b2f4f7b4bfccf2c766..05bfa41f102801a5182e7fc642976f91e9ba7db6 100644 (file)
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
*/
xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
xdchange_t *xch, *xchp;
+ long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
- if (xch->i1 - (xchp->i1 + xchp->chg1) > 2 * xecfg->ctxlen)
+ if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common)
break;
return xchp;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c
--- /dev/null
+++ b/xdiff/xpatience.c
@@ -0,0 +1,381 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+/*
+ * The basic idea of patience diff is to find lines that are unique in
+ * both files. These are intuitively the ones that we want to see as
+ * common lines.
+ *
+ * The maximal ordered sequence of such line pairs (where ordered means
+ * that the order in the sequence agrees with the order of the lines in
+ * both files) naturally defines an initial set of common lines.
+ *
+ * Now, the algorithm tries to extend the set of common lines by growing
+ * the line ranges where the files have identical lines.
+ *
+ * Between those common lines, the patience diff algorithm is applied
+ * recursively, until no unique line pairs can be found; these line ranges
+ * are handled by the well-known Myers algorithm.
+ */
+
+#define NON_UNIQUE ULONG_MAX
+
+/*
+ * This is a hash mapping from line hash to line numbers in the first and
+ * second file.
+ */
+struct hashmap {
+ int nr, alloc;
+ struct entry {
+ unsigned long hash;
+ /*
+ * 0 = unused entry, 1 = first line, 2 = second, etc.
+ * line2 is NON_UNIQUE if the line is not unique
+ * in either the first or the second file.
+ */
+ unsigned long line1, line2;
+ /*
+ * "next" & "previous" are used for the longest common
+ * sequence;
+ * initially, "next" reflects only the order in file1.
+ */
+ struct entry *next, *previous;
+ } *entries, *first, *last;
+ /* were common records found? */
+ unsigned long has_matches;
+ mmfile_t *file1, *file2;
+ xdfenv_t *env;
+ xpparam_t const *xpp;
+};
+
+/* The argument "pass" is 1 for the first file, 2 for the second. */
+static void insert_record(int line, struct hashmap *map, int pass)
+{
+ xrecord_t **records = pass == 1 ?
+ map->env->xdf1.recs : map->env->xdf2.recs;
+ xrecord_t *record = records[line - 1], *other;
+ /*
+ * After xdl_prepare_env() (or more precisely, due to
+ * xdl_classify_record()), the "ha" member of the records (AKA lines)
+ * is _not_ the hash anymore, but a linearized version of it. In
+ * other words, the "ha" member is guaranteed to start with 0 and
+ * the second record's ha can only be 0 or 1, etc.
+ *
+ * So we multiply ha by 2 in the hope that the hashing was
+ * "unique enough".
+ */
+ int index = (int)((record->ha << 1) % map->alloc);
+
+ while (map->entries[index].line1) {
+ other = map->env->xdf1.recs[map->entries[index].line1 - 1];
+ if (map->entries[index].hash != record->ha ||
+ !xdl_recmatch(record->ptr, record->size,
+ other->ptr, other->size,
+ map->xpp->flags)) {
+ if (++index >= map->alloc)
+ index = 0;
+ continue;
+ }
+ if (pass == 2)
+ map->has_matches = 1;
+ if (pass == 1 || map->entries[index].line2)
+ map->entries[index].line2 = NON_UNIQUE;
+ else
+ map->entries[index].line2 = line;
+ return;
+ }
+ if (pass == 2)
+ return;
+ map->entries[index].line1 = line;
+ map->entries[index].hash = record->ha;
+ if (!map->first)
+ map->first = map->entries + index;
+ if (map->last) {
+ map->last->next = map->entries + index;
+ map->entries[index].previous = map->last;
+ }
+ map->last = map->entries + index;
+ map->nr++;
+}
+
+/*
+ * This function has to be called for each recursion into the inter-hunk
+ * parts, as previously non-unique lines can become unique when being
+ * restricted to a smaller part of the files.
+ *
+ * It is assumed that env has been prepared using xdl_prepare().
+ */
+static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ struct hashmap *result,
+ int line1, int count1, int line2, int count2)
+{
+ result->file1 = file1;
+ result->file2 = file2;
+ result->xpp = xpp;
+ result->env = env;
+
+ /* We know exactly how large we want the hash map */
+ result->alloc = count1 * 2;
+ result->entries = (struct entry *)
+ xdl_malloc(result->alloc * sizeof(struct entry));
+ if (!result->entries)
+ return -1;
+ memset(result->entries, 0, result->alloc * sizeof(struct entry));
+
+ /* First, fill with entries from the first file */
+ while (count1--)
+ insert_record(line1++, result, 1);
+
+ /* Then search for matches in the second file */
+ while (count2--)
+ insert_record(line2++, result, 2);
+
+ return 0;
+}
+
+/*
+ * Find the longest sequence with a smaller last element (meaning a smaller
+ * line2, as we construct the sequence with entries ordered by line1).
+ */
+static int binary_search(struct entry **sequence, int longest,
+ struct entry *entry)
+{
+ int left = -1, right = longest;
+
+ while (left + 1 < right) {
+ int middle = (left + right) / 2;
+ /* by construction, no two entries can be equal */
+ if (sequence[middle]->line2 > entry->line2)
+ right = middle;
+ else
+ left = middle;
+ }
+ /* return the index in "sequence", _not_ the sequence length */
+ return left;
+}
+
+/*
+ * The idea is to start with the list of common unique lines sorted by
+ * the order in file1. For each of these pairs, the longest (partial)
+ * sequence whose last element's line2 is smaller is determined.
+ *
+ * For efficiency, the sequences are kept in a list containing exactly one
+ * item per sequence length: the sequence with the smallest last
+ * element (in terms of line2).
+ */
+static struct entry *find_longest_common_sequence(struct hashmap *map)
+{
+ struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
+ int longest = 0, i;
+ struct entry *entry;
+
+ for (entry = map->first; entry; entry = entry->next) {
+ if (!entry->line2 || entry->line2 == NON_UNIQUE)
+ continue;
+ i = binary_search(sequence, longest, entry);
+ entry->previous = i < 0 ? NULL : sequence[i];
+ sequence[++i] = entry;
+ if (i == longest)
+ longest++;
+ }
+
+ /* No common unique lines were found */
+ if (!longest) {
+ xdl_free(sequence);
+ return NULL;
+ }
+
+ /* Iterate starting at the last element, adjusting the "next" members */
+ entry = sequence[longest - 1];
+ entry->next = NULL;
+ while (entry->previous) {
+ entry->previous->next = entry;
+ entry = entry->previous;
+ }
+ xdl_free(sequence);
+ return entry;
+}
+
+static int match(struct hashmap *map, int line1, int line2)
+{
+ xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
+ xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
+ return xdl_recmatch(record1->ptr, record1->size,
+ record2->ptr, record2->size, map->xpp->flags);
+}
+
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2);
+
+static int walk_common_sequence(struct hashmap *map, struct entry *first,
+ int line1, int count1, int line2, int count2)
+{
+ int end1 = line1 + count1, end2 = line2 + count2;
+ int next1, next2;
+
+ for (;;) {
+ /* Try to grow the line ranges of common lines */
+ if (first) {
+ next1 = first->line1;
+ next2 = first->line2;
+ while (next1 > line1 && next2 > line2 &&
+ match(map, next1 - 1, next2 - 1)) {
+ next1--;
+ next2--;
+ }
+ } else {
+ next1 = end1;
+ next2 = end2;
+ }
+ while (line1 < next1 && line2 < next2 &&
+ match(map, line1, line2)) {
+ line1++;
+ line2++;
+ }
+
+ /* Recurse */
+ if (next1 > line1 || next2 > line2) {
+ struct hashmap submap;
+
+ memset(&submap, 0, sizeof(submap));
+ if (patience_diff(map->file1, map->file2,
+ map->xpp, map->env,
+ line1, next1 - line1,
+ line2, next2 - line2))
+ return -1;
+ }
+
+ if (!first)
+ return 0;
+
+ while (first->next &&
+ first->next->line1 == first->line1 + 1 &&
+ first->next->line2 == first->line2 + 1)
+ first = first->next;
+
+ line1 = first->line1 + 1;
+ line2 = first->line2 + 1;
+
+ first = first->next;
+ }
+}
+
+static int fall_back_to_classic_diff(struct hashmap *map,
+ int line1, int count1, int line2, int count2)
+{
+ /*
+ * This probably does not work outside Git, since
+ * we have a very simple mmfile structure.
+ *
+ * Note: ideally, we would reuse the prepared environment, but
+ * the libxdiff interface does not (yet) allow for diffing only
+ * ranges of lines instead of the whole files.
+ */
+ mmfile_t subfile1, subfile2;
+ xpparam_t xpp;
+ xdfenv_t env;
+
+ subfile1.ptr = (char *)map->env->xdf1.recs[line1 - 1]->ptr;
+ subfile1.size = map->env->xdf1.recs[line1 + count1 - 2]->ptr +
+ map->env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+ subfile2.ptr = (char *)map->env->xdf2.recs[line2 - 1]->ptr;
+ subfile2.size = map->env->xdf2.recs[line2 + count2 - 2]->ptr +
+ map->env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+ xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF;
+ if (xdl_do_diff(&subfile1, &subfile2, &xpp, &env) < 0)
+ return -1;
+
+ memcpy(map->env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
+ memcpy(map->env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+
+ xdl_free_env(&env);
+
+ return 0;
+}
+
+/*
+ * Recursively find the longest common sequence of unique lines,
+ * and if none was found, ask xdl_do_diff() to do the job.
+ *
+ * This function assumes that env was prepared with xdl_prepare_env().
+ */
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2)
+{
+ struct hashmap map;
+ struct entry *first;
+ int result = 0;
+
+ /* trivial case: one side is empty */
+ if (!count1) {
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ return 0;
+ } else if (!count2) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ return 0;
+ }
+
+ memset(&map, 0, sizeof(map));
+ if (fill_hashmap(file1, file2, xpp, env, &map,
+ line1, count1, line2, count2))
+ return -1;
+
+ /* are there any matching lines at all? */
+ if (!map.has_matches) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ xdl_free(map.entries);
+ return 0;
+ }
+
+ first = find_longest_common_sequence(&map);
+ if (first)
+ result = walk_common_sequence(&map, first,
+ line1, count1, line2, count2);
+ else
+ result = fall_back_to_classic_diff(&map,
+ line1, count1, line2, count2);
+
+ xdl_free(map.entries);
+ return result;
+}
+
+int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env)
+{
+ if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+ return -1;
+
+ /* environment is cleaned up in xdl_diff() */
+ return patience_diff(file1, file2, xpp, env,
+ 1, env->xdf1.nrec, 1, env->xdf2.nrec);
+}
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
index a43aa72cd088af07b13a6bc8de304ecc178047e5..16890852350cb62bb9f9aec5e52eea8ba46f1192 100644 (file)
--- a/xdiff/xprepare.c
+++ b/xdiff/xprepare.c
xdl_free_classifier(&cf);
- if (xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
+ if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
+ xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
xdl_free_ctx(&xe->xdf2);
xdl_free_ctx(&xe->xdf1);