Merge branch 'master' of git://repo.or.cz/alt-git

This commit is contained in:
Johannes Sixt
2010-01-11 08:56:35 +01:00
107 changed files with 3200 additions and 467 deletions

1
.gitattributes vendored
View File

@@ -1,2 +1,3 @@
* whitespace=!indent,trail,space
*.[ch] whitespace=indent,trail,space
*.sh whitespace=indent,trail,space

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
/GIT-CFLAGS
/GIT-GUI-VARS
/GIT-VERSION-FILE
/bin-wrappers/
/git
/git-add
/git-add--interactive

View File

@@ -0,0 +1,15 @@
Git v1.6.6.1 Release Notes
==========================
Fixes since v1.6.6
------------------
* http-backend was not listed in the command list in the documentation.
Other minor documentation updates are included.
--
exec >/var/tmp/1
O=v1.6.6-4-gd828fdb
echo O=$(git describe maint)
git shortlog --no-merges $O..maint

View File

@@ -0,0 +1,70 @@
Git v1.7.0 Release Notes
========================
Notes on behaviour change
-------------------------
* "git push" into a branch that is currently checked out (i.e. pointed by
HEAD in a repository that is not bare) is refused by default.
Similarly, "git push $there :$killed" to delete the branch $killed
in a remote repository $there, when $killed branch is the current
branch pointed at by its HEAD, will be refused by default.
Setting the configuration variables receive.denyCurrentBranch and
receive.denyDeleteCurrent to 'ignore' in the receiving repository
can be used to override these safety features.
* "git send-email" does not make deep threads by default when sending a
patch series with more than two messages. All messages will be sent
as a reply to the first message, i.e. cover letter.
It has been possible to configure send-email to send "shallow thread"
by setting sendemail.chainreplyto configuration variable to false. The
only thing this release does is to change the default when you haven't
configured that variable.
* "git status" is not "git commit --dry-run" anymore. This change does
not affect you if you run the command without pathspec.
* "git diff" traditionally treated various "ignore whitespace" options
only as a way to filter the patch output. "git diff --exit-code -b"
exited with non-zero status even if all changes were about changing the
ammount of whitespace and nothing else. and "git diff -b" showed the
"diff --git" header line for such a change without patch text.
In this release, the "ignore whitespaces" options affect the semantics
of the diff operation. A change that does not affect anything but
whitespaces is reported with zero exit status when run with
--exit-code, and there is no "diff --git" header for such a change.
Updates since v1.6.6
--------------------
(subsystems)
(portability)
(performance)
(usability, bells and whistles)
* "git commit --date='<date>'" can be used to override the author date
just like "git commit --author='<name> <email>'" can be used to
override the author identity.
* "git status" learned "-s(hort)" output format.
Fixes since v1.6.6
------------------
All of the fixes in v1.6.6.X maintenance series are included in this
release, unless otherwise noted.
--
exec >/var/tmp/1
O=v1.6.6-101-gf012d27
echo O=$(git describe master)
git shortlog --no-merges $O..master ^maint

View File

@@ -279,6 +279,20 @@ from the list and queue it to 'pu', in order to make it easier for
people play with it without having to pick up and apply the patch to
their trees themselves.
------------------------------------------------
Know the status of your patch after submission
* You can use Git itself to find out when your patch is merged in
master. 'git pull --rebase' will automatically skip already-applied
patches, and will let you know. This works only if you rebase on top
of the branch in which your patch has been merged (i.e. it will not
tell you if your patch is merged in pu if you rebase on top of
master).
* Read the git mailing list, the maintainer regularly posts messages
entitled "What's cooking in git.git" and "What's in git.git" giving
the status of various proposed changes.
------------------------------------------------
MUA specific hints

View File

@@ -297,17 +297,24 @@ false), while all other repositories are assumed to be bare (bare
= true).
core.worktree::
Set the path to the working tree. The value will not be
used in combination with repositories found automatically in
a .git directory (i.e. $GIT_DIR is not set).
Set the path to the root of the work tree.
This can be overridden by the GIT_WORK_TREE environment
variable and the '--work-tree' command line option. It can be
a absolute path or relative path to the directory specified by
--git-dir or GIT_DIR.
Note: If --git-dir or GIT_DIR are specified but none of
an absolute path or a relative path to the .git directory,
either specified by --git-dir or GIT_DIR, or automatically
discovered.
If --git-dir or GIT_DIR are specified but none of
--work-tree, GIT_WORK_TREE and core.worktree is specified,
the current working directory is regarded as the top directory
of your working tree.
the current working directory is regarded as the root of the
work tree.
+
Note that this variable is honored even when set in a configuration
file in a ".git" subdirectory of a directory, and its value differs
from the latter directory (e.g. "/path/to/.git/config" has
core.worktree set to "/different/path"), which is most likely a
misconfiguration. Running git commands in "/path/to" directory will
still use "/different/path" as the root of the work tree and can cause
great confusion to the users.
core.logAllRefUpdates::
Enable the reflog. Updates to a ref <ref> is logged to the file
@@ -530,7 +537,7 @@ apply.whitespace::
as the '--whitespace' option. See linkgit:git-apply[1].
branch.autosetupmerge::
Tells 'git-branch' and 'git-checkout' to setup new branches
Tells 'git-branch' and 'git-checkout' to set up new branches
so that linkgit:git-pull[1] will appropriately merge from the
starting point branch. Note that even if this option is not set,
this behavior can be chosen per-branch using the `--track`
@@ -718,7 +725,7 @@ diff.autorefreshindex::
contents in the work tree match the contents in the
index. This option defaults to true. Note that this
affects only 'git-diff' Porcelain, and not lower level
'diff' commands, such as 'git-diff-files'.
'diff' commands such as 'git-diff-files'.
diff.external::
If this config variable is set, diff generation is not
@@ -834,8 +841,8 @@ format.pretty::
format.thread::
The default threading style for 'git-format-patch'. Can be
either a boolean value, `shallow` or `deep`. `shallow`
threading makes every mail a reply to the head of the series,
a boolean value, or `shallow` or `deep`. `shallow` threading
makes every mail a reply to the head of the series,
where the head is chosen from the cover letter, the
`\--in-reply-to`, and the first patch mail, in this order.
`deep` threading makes every mail a reply to the previous one.
@@ -868,15 +875,12 @@ gc.autopacklimit::
default value is 50. Setting this to 0 disables it.
gc.packrefs::
'git-gc' does not run `git pack-refs` in a bare repository by
default so that older dumb-transport clients can still fetch
from the repository. Setting this to `true` lets 'git-gc'
to run `git pack-refs`. Setting this to `false` tells
'git-gc' never to run `git pack-refs`. The default setting is
`notbare`. Enable it only when you know you do not have to
support such clients. The default setting will change to `true`
at some stage, and setting this to `false` will continue to
prevent `git pack-refs` from being run from 'git-gc'.
Running `git pack-refs` in a repository renders it
unclonable by Git versions prior to 1.5.1.2 over dumb
transports such as HTTP. This variable determines whether
'git gc' runs `git pack-refs`. This can be set to "nobare"
to enable it within all non-bare repos or it can be set to a
boolean value. The default is `true`.
gc.pruneexpire::
When 'git-gc' is run, it will call 'prune --expire 2.weeks.ago'.
@@ -1132,6 +1136,12 @@ http.maxRequests::
How many HTTP requests to launch in parallel. Can be overridden
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
http.minSessions::
The number of curl sessions (counted across slots) to be kept across
requests. They will not be ended with curl_easy_cleanup() until
http_cleanup() is invoked. If USE_CURL_MULTI is not defined, this
value will be capped at 1. Defaults to 1.
http.postBuffer::
Maximum size in bytes of the buffer used by smart HTTP
transports when POSTing data to the remote system.
@@ -1461,6 +1471,10 @@ remote.<name>.tagopt::
Setting this value to \--no-tags disables automatic tag following when
fetching from remote <name>
remote.<name>.vcs::
Setting this to a value <vcs> will cause git to interact with
the remote with the git-remote-<vcs> helper.
remotes.<group>::
The list of remotes which are fetched by "git remote update
<group>". See linkgit:git-remote[1].

View File

@@ -0,0 +1,26 @@
DATE FORMATS
------------
The GIT_AUTHOR_DATE, GIT_COMMITTER_DATE environment variables
ifdef::git-commit[]
and the `--date` option
endif::git-commit[]
support the following date formats:
Git internal format::
It is `<unix timestamp> <timezone offset>`, where `<unix
timestamp>` is the number of seconds since the UNIX epoch.
`<timezone offset>` is a positive or negative offset from UTC.
For example CET (which is 2 hours ahead UTC) is `+0200`.
RFC 2822::
The standard email format as described by RFC 2822, for example
`Thu, 07 Apr 2005 22:13:13 +0200`.
ISO 8601::
Time and date specified by the ISO 8601 standard, for example
`2005-04-07T22:13:13`. The parser accepts a space instead of the
`T` character as well.
+
NOTE: In addition, the date part is accepted in the following formats:
`YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`.

View File

@@ -14,28 +14,32 @@ SYNOPSIS
DESCRIPTION
-----------
This command adds the current content of new or modified files to the
index, thus staging that content for inclusion in the next commit.
This command updates the index using the current content found in
the working tree, to prepare the content staged for the next commit.
It typically adds the current content of existing paths as a whole,
but with some options it can also be used to add content with
only part of the changes made to the working tree files applied, or
remove paths that do not exist in the working tree anymore.
The "index" holds a snapshot of the content of the working tree, and it
is this snapshot that is taken as the contents of the next commit. Thus
after making any changes to the working directory, and before running
the commit command, you must use the 'add' command to add any new or
the commit command, you must use the `add` command to add any new or
modified files to the index.
This command can be performed multiple times before a commit. It only
adds the content of the specified file(s) at the time the add command is
run; if you want subsequent changes included in the next commit, then
you must run 'git add' again to add the new content to the index.
you must run `git add` again to add the new content to the index.
The 'git status' command can be used to obtain a summary of which
The `git status` command can be used to obtain a summary of which
files have changes that are staged for the next commit.
The 'git add' command will not add ignored files by default. If any
ignored files were explicitly specified on the command line, 'git add'
The `git add` command will not add ignored files by default. If any
ignored files were explicitly specified on the command line, `git add`
will fail with a list of ignored files. Ignored files reached by
directory recursion or filename globbing performed by Git (quote your
globs before the shell) will be silently ignored. The 'add' command can
globs before the shell) will be silently ignored. The `add` command can
be used to add ignored files with the `-f` (force) option.
Please see linkgit:git-commit[1] for alternative ways to add content to a
@@ -92,28 +96,31 @@ apply.
-u::
--update::
Update only files that git already knows about, staging modified
content for commit and marking deleted files for removal. This
is similar
to what "git commit -a" does in preparation for making a commit,
except that the update is limited to paths specified on the
command line. If no paths are specified, all tracked files in the
current directory and its subdirectories are updated.
Only match <filepattern> against already tracked files in
the index rather than the working tree. That means that it
will never stage new files, but that it will stage modified
new contents of tracked files and that it will remove files
from the index if the corresponding files in the working tree
have been removed.
+
If no <filepattern> is given, default to "."; in other words,
update all tracked files in the current directory and its
subdirectories.
-A::
--all::
Update files that git already knows about (same as '\--update')
and add all untracked files that are not ignored by '.gitignore'
mechanism.
Like `-u`, but match <filepattern> against files in the
working tree in addition to the index. That means that it
will find new files as well as staging modified content and
removing files that are no longer in the working tree.
-N::
--intent-to-add::
Record only the fact that the path will be added later. An entry
for the path is placed in the index with no content. This is
useful for, among other things, showing the unstaged content of
such files with 'git diff' and committing them with 'git commit
-a'.
such files with `git diff` and committing them with `git commit
-a`.
--refresh::
Don't add the file(s), but only refresh their stat()
@@ -133,7 +140,7 @@ apply.
Configuration
-------------
The optional configuration variable 'core.excludesfile' indicates a path to a
The optional configuration variable `core.excludesfile` indicates a path to a
file containing patterns of file names to exclude from git-add, similar to
$GIT_DIR/info/exclude. Patterns in the exclude file are used in addition to
those in info/exclude. See linkgit:gitrepository-layout[5].
@@ -181,7 +188,7 @@ and type return, like this:
What now> 1
------------
You also could say "s" or "sta" or "status" above as long as the
You also could say `s` or `sta` or `status` above as long as the
choice is unique.
The main command loop has 6 subcommands (plus help and quit).
@@ -189,9 +196,9 @@ The main command loop has 6 subcommands (plus help and quit).
status::
This shows the change between HEAD and index (i.e. what will be
committed if you say "git commit"), and between index and
committed if you say `git commit`), and between index and
working tree files (i.e. what you could stage further before
"git commit" using "git-add") for each path. A sample output
`git commit` using `git add`) for each path. A sample output
looks like this:
+
------------

View File

@@ -73,6 +73,7 @@ A commit comment is read from stdin. If a changelog
entry is not provided via "<" redirection, 'git-commit-tree' will just wait
for one to be entered and terminated with ^D.
include::date-formats.txt[]
Diagnostics
-----------

View File

@@ -11,7 +11,7 @@ SYNOPSIS
'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
[(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
[--allow-empty] [--no-verify] [-e] [--author=<author>]
[--cleanup=<mode>] [--] [[-i | -o ]<file>...]
[--date=<date>] [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
DESCRIPTION
-----------
@@ -74,6 +74,20 @@ OPTIONS
authorship of the resulting commit now belongs of the committer.
This also renews the author timestamp.
--short::
When doing a dry-run, give the output in the short-format. See
linkgit:git-status[1] for details. Implies `--dry-run`.
--porcelain::
When doing a dry-run, give the output in a porcelain-ready
format. See linkgit:git-status[1] for details. Implies
`--dry-run`.
-z::
When showing `short` or `porcelain` status output, terminate
entries in the status output with NUL, instead of LF. If no
format is given, implies the `--porcelain` output format.
-F <file>::
--file=<file>::
Take the commit message from the given file. Use '-' to
@@ -85,6 +99,9 @@ OPTIONS
an existing commit that matches the given string and its author
name is used.
--date=<date>::
Override the author date used in the commit.
-m <msg>::
--message=<msg>::
Use the given <msg> as the commit message.
@@ -217,6 +234,8 @@ specified.
these files are also staged for the next commit on top
of what have been staged before.
:git-commit: 1
include::date-formats.txt[]
EXAMPLES
--------

View File

@@ -37,11 +37,12 @@ existing values that match the regexp are updated or unset. If
you want to handle the lines that do *not* match the regex, just
prepend a single exclamation mark in front (see also <<EXAMPLES>>).
The type specifier can be either '--int' or '--bool', which will make
The type specifier can be either '--int' or '--bool', to make
'git-config' ensure that the variable(s) are of the given type and
convert the value to the canonical form (simple decimal number for int,
a "true" or "false" string for bool). If no type specifier is passed,
no checks or transformations are performed on the value.
a "true" or "false" string for bool), or '--path', which does some
path expansion (see '--path' below). If no type specifier is passed, no
checks or transformations are performed on the value.
The file-option can be one of '--system', '--global' or '--file'
which specify where the values will be read from or written to.
@@ -136,6 +137,13 @@ See also <<FILES>>.
'git-config' will ensure that the output matches the format of
either --bool or --int, as described above.
--path::
'git-config' will expand leading '{tilde}' to the value of
'$HOME', and '{tilde}user' to the home directory for the
specified user. This option has no effect when setting the
value (but you can use 'git config bla {tilde}/' from the
command line to let your shell do the expansion).
-z::
--null::
For all options that output values and/or keys, always

View File

@@ -277,6 +277,21 @@ In `dbdriver` and `dbuser` you can use the following variables:
If no name can be determined, the
numeric uid is used.
ENVIRONMENT
-----------
These variables obviate the need for command-line options in some
circumstances, allowing easier restricted usage through git-shell.
GIT_CVSSERVER_BASE_PATH takes the place of the argument to --base-path.
GIT_CVSSERVER_ROOT specifies a single-directory whitelist. The
repository must still be configured to allow access through
git-cvsserver, as described above.
When these environment variables are set, the corresponding
command-line arguments may not be used.
Eclipse CVS Client Notes
------------------------

View File

@@ -311,8 +311,8 @@ change to the project.
....
'commit' SP <ref> LF
mark?
('author' SP <name> SP LT <email> GT SP <when> LF)?
'committer' SP <name> SP LT <email> GT SP <when> LF
('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
'committer' (SP <name>)? SP LT <email> GT SP <when> LF
data
('from' SP <committish> LF)?
('merge' SP <committish> LF)?
@@ -657,7 +657,7 @@ lightweight (non-annotated) tags see the `reset` command below.
....
'tag' SP <name> LF
'from' SP <committish> LF
'tagger' SP <name> SP LT <email> GT SP <when> LF
'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
data
....

View File

@@ -91,6 +91,10 @@ nor in any Push line of the corresponding remotes file---see below).
will be tab-separated and sent to stdout instead of stderr. The full
symbolic names of the refs will be given.
--delete::
All listed refs are deleted from the remote repository. This is
the same as prefixing all refs with a colon.
--tags::
All refs under `$GIT_DIR/refs/tags` are pushed, in
addition to refspecs explicitly listed on the command

View File

@@ -79,6 +79,17 @@ style string if it contains an LF.
+
Supported if the helper has the "push" capability.
'import' <name>::
Produces a fast-import stream which imports the current value
of the named ref. It may additionally import other refs as
needed to construct the history efficiently. The script writes
to a helper-specific private namespace. The value of the named
ref should be written to a location in this namespace derived
by applying the refspecs from the "refspec" capability to the
name of the ref.
+
Supported if the helper has the "import" capability.
If a fatal error occurs, the program writes the error message to
stderr and exits. The caller should expect that a suitable error
message has been printed if the child closes the connection without
@@ -99,6 +110,19 @@ CAPABILITIES
'push'::
This helper supports the 'push' command.
'import'::
This helper supports the 'import' command.
'refspec' 'spec'::
When using the import command, expect the source ref to have
been written to the destination ref. The earliest applicable
refspec takes precedence. For example
"refs/heads/*:refs/svn/origin/branches/*" means that, after an
"import refs/heads/name", the script has written to
refs/svn/origin/branches/name. If this capability is used at
all, it must cover all refs reported by the list command; if
it is not used, it is effectively "*:*"
REF LIST ATTRIBUTES
-------------------
@@ -107,6 +131,10 @@ REF LIST ATTRIBUTES
commands. A helper might chose to acquire the ref list by
opening a different type of connection to the destination.
'unchanged'::
This ref is unchanged since the last import or fetch, although
the helper cannot necessarily determine what value that produced.
OPTIONS
-------
'option verbosity' <N>::

View File

@@ -62,6 +62,7 @@ This means that `git reset -p` is the opposite of `git add -p` (see
linkgit:git-add[1]).
-q::
--quiet::
Be quiet, only report errors.
<commit>::

View File

@@ -12,13 +12,13 @@ SYNOPSIS
DESCRIPTION
-----------
Remove files from the index, or from the working tree and the index.
'git-rm' will not remove a file from just your working directory.
(There is no option to remove a file only from the work tree
`git rm` will not remove a file from just your working directory.
(There is no option to remove a file only from the working tree
and yet keep it in the index; use `/bin/rm` if you want to do that.)
The files being removed have to be identical to the tip of the branch,
and no updates to their contents can be staged in the index,
though that default behavior can be overridden with the `-f` option.
When '--cached' is given, the staged content has to
When `--cached` is given, the staged content has to
match either the tip of the branch or the file on disk,
allowing the file to be removed from just the index.
@@ -64,7 +64,7 @@ OPTIONS
-q::
--quiet::
'git-rm' normally outputs one line (in the form of an "rm" command)
`git rm` normally outputs one line (in the form of an `rm` command)
for each file removed. This option suppresses that output.
@@ -81,6 +81,58 @@ two directories `d` and `d2`, there is a difference between
using `git rm \'d\*\'` and `git rm \'d/\*\'`, as the former will
also remove all of directory `d2`.
REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
--------------------------------------------------------
There is no option for `git rm` to remove from the index only
the paths that have disappeared from the filesystem. However,
depending on the use case, there are several ways that can be
done.
Using "git commit -a"
~~~~~~~~~~~~~~~~~~~~~
If you intend that your next commit should record all modifications
of tracked files in the working tree and record all removals of
files that have been removed from the working tree with `rm`
(as opposed to `git rm`), use `git commit -a`, as it will
automatically notice and record all removals. You can also have a
similar effect without committing by using `git add -u`.
Using "git add -A"
~~~~~~~~~~~~~~~~~~
When accepting a new code drop for a vendor branch, you probably
want to record both the removal of paths and additions of new paths
as well as modifications of existing paths.
Typically you would first remove all tracked files from the working
tree using this command:
----------------
git ls-files -z | xargs -0 rm -f
----------------
and then "untar" the new code in the working tree. Alternately
you could "rsync" the changes into the working tree.
After that, the easiest way to record all removals, additions, and
modifications in the working tree is:
----------------
git add -A
----------------
See linkgit:git-add[1].
Other ways
~~~~~~~~~~
If all you really want to do is to remove from the index the files
that are no longer present in the working tree (perhaps because
your working tree is dirty so that you cannot use `git commit -a`),
use the following command:
----------------
git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
----------------
EXAMPLES
--------
git rm Documentation/\\*.txt::

View File

@@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
--in-reply-to=<identifier>::
Specify the contents of the first In-Reply-To header.
Subsequent emails will refer to the previous email
instead of this if --chain-reply-to is set (the default)
instead of this if --chain-reply-to is set.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
@@ -172,8 +172,8 @@ Automating
email sent. If disabled with "--no-chain-reply-to", all emails after
the first will be sent as replies to the first email sent. When using
this, it is recommended that the first file given be an overview of the
entire patch series. Default is the value of the 'sendemail.chainreplyto'
configuration value; if that is unspecified, default to --chain-reply-to.
entire patch series. Disabled by default, but the 'sendemail.chainreplyto'
configuration variable can be used to enable it.
--identity=<identity>::
A configuration identity. When given, causes values in the

View File

@@ -8,7 +8,7 @@ git-status - Show the working tree status
SYNOPSIS
--------
'git status' <options>...
'git status' [<options>...] [--] [<pathspec>...]
DESCRIPTION
-----------
@@ -20,25 +20,90 @@ are what you _would_ commit by running `git commit`; the second and
third are what you _could_ commit by running 'git-add' before running
`git commit`.
The command takes the same set of options as 'git-commit'; it
shows what would be committed if the same options are given to
'git-commit'.
OPTIONS
-------
If there is no path that is different between the index file and
the current HEAD commit (i.e., there is nothing to commit by running
`git commit`), the command exits with non-zero status.
-s::
--short::
Give the output in the short-format.
--porcelain::
Give the output in a stable, easy-to-parse format for scripts.
Currently this is identical to --short output, but is guaranteed
not to change in the future, making it safe for scripts.
-u[<mode>]::
--untracked-files[=<mode>]::
Show untracked files (Default: 'all').
+
The mode parameter is optional, and is used to specify
the handling of untracked files. The possible options are:
+
--
- 'no' - Show no untracked files
- 'normal' - Shows untracked files and directories
- 'all' - Also shows individual files in untracked directories.
--
+
See linkgit:git-config[1] for configuration variable
used to change the default for when the option is not
specified.
-z::
Terminate entries with NUL, instead of LF. This implies
the `--porcelain` output format if no other format is given.
OUTPUT
------
The output from this command is designed to be used as a commit
template comment, and all the output lines are prefixed with '#'.
The default, long format, is designed to be human readable,
verbose and descriptive. They are subject to change in any time.
The paths mentioned in the output, unlike many other git commands, are
made relative to the current directory if you are working in a
subdirectory (this is on purpose, to help cutting and pasting). See
the status.relativePaths config option below.
In short-format, the status of each path is shown as
XY PATH1 -> PATH2
where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is
shown only when `PATH1` corresponds to a different path in the
index/worktree (i.e. renamed).
For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y`
shows the status of stage #3 (i.e. theirs).
For entries that do not have conflicts, `X` shows the status of the index,
and `Y` shows the status of the work tree. For untracked paths, `XY` are
`??`.
X Y Meaning
-------------------------------------------------
[MD] not updated
M [ MD] updated in index
A [ MD] added to index
D [ MD] deleted from index
R [ MD] renamed in index
C [ MD] copied in index
[MARC] index and work tree matches
[ MARC] M work tree changed since index
[ MARC] D deleted in work tree
-------------------------------------------------
D D unmerged, both deleted
A U unmerged, added by us
U D unmerged, deleted by them
U A unmerged, added by them
D U unmerged, deleted by us
A A unmerged, both added
U U unmerged, both modified
-------------------------------------------------
? ? untracked
-------------------------------------------------
CONFIGURATION
-------------
@@ -53,9 +118,9 @@ paths shown are relative to the repository root, not to the current
directory.
If `status.submodulesummary` is set to a non zero number or true (identical
to -1 or an unlimited number), the submodule summary will be enabled and a
summary of commits for modified submodules will be shown (see --summary-limit
option of linkgit:git-submodule[1]).
to -1 or an unlimited number), the submodule summary will be enabled for
the long format and a summary of commits for modified submodules will be
shown (see --summary-limit option of linkgit:git-submodule[1]).
SEE ALSO
--------
@@ -63,8 +128,7 @@ linkgit:gitignore[5]
Author
------
Written by Linus Torvalds <torvalds@osdl.org> and
Junio C Hamano <gitster@pobox.com>.
Written by Junio C Hamano <gitster@pobox.com>.
Documentation
--------------

View File

@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
DEF_VER=v1.6.6
DEF_VER=v1.6.6.GIT
LF='
'

18
INSTALL
View File

@@ -38,13 +38,17 @@ Issues of note:
Interactive Tools package still can install "git", but you can build it
with --disable-transition option to avoid this.
- You can use git after building but without installing if you
wanted to. Various git commands need to find other git
commands and scripts to do their work, so you would need to
arrange a few environment variables to tell them that their
friends will be found in your built source area instead of at
their standard installation area. Something like this works
for me:
- You can use git after building but without installing if you want
to test drive it. Simply run git found in bin-wrappers directory
in the build directory, or prepend that directory to your $PATH.
This however is less efficient than running an installed git, as
you always need an extra fork+exec to run any git subcommand.
It is still possible to use git without installing by setting a few
environment variables, which was the way this was done
traditionally. But using git found in bin-wrappers directory in
the build directory is far simpler. As a historical reference, the
old way went like this:
GIT_EXEC_PATH=`pwd`
PATH=`pwd`:$PATH

105
Makefile
View File

@@ -168,6 +168,8 @@ all::
#
# Define NO_PERL if you do not want Perl scripts or libraries at all.
#
# Define NO_PYTHON if you do not want Python scripts or libraries at all.
#
# Define NO_TCLTK if you do not want Tcl/Tk GUI.
#
# The TCL_PATH variable governs the location of the Tcl interpreter
@@ -341,6 +343,7 @@ LIB_H =
LIB_OBJS =
PROGRAMS =
SCRIPT_PERL =
SCRIPT_PYTHON =
SCRIPT_SH =
TEST_PROGRAMS =
@@ -379,6 +382,7 @@ SCRIPT_PERL += git-svn.perl
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
git-instaweb
# Empty...
@@ -427,6 +431,15 @@ ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X
# what test wrappers are needed and 'install' will install, in bindir
BINDIR_PROGRAMS_NEED_X += git
BINDIR_PROGRAMS_NEED_X += git-upload-pack
BINDIR_PROGRAMS_NEED_X += git-receive-pack
BINDIR_PROGRAMS_NEED_X += git-upload-archive
BINDIR_PROGRAMS_NEED_X += git-shell
BINDIR_PROGRAMS_NO_X += git-cvsserver
# Set paths to tools early so that they can be used for version tests.
ifndef SHELL_PATH
SHELL_PATH = /bin/sh
@@ -434,8 +447,12 @@ endif
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
ifndef PYTHON_PATH
PYTHON_PATH = /usr/bin/python
endif
export PERL_PATH
export PYTHON_PATH
LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
@@ -828,6 +845,7 @@ ifeq ($(uname_O),Cygwin)
endif
ifeq ($(uname_S),FreeBSD)
NEEDS_LIBICONV = YesPlease
OLD_ICONV = YesPlease
NO_MEMMEM = YesPlease
BASIC_CFLAGS += -I/usr/local/include
BASIC_LDFLAGS += -L/usr/local/lib
@@ -1346,6 +1364,10 @@ ifeq ($(PERL_PATH),)
NO_PERL=NoThanks
endif
ifeq ($(PYTHON_PATH),)
NO_PYTHON=NoThanks
endif
QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
QUIET_SUBDIR1 =
@@ -1393,6 +1415,7 @@ prefix_SQ = $(subst ','\'',$(prefix))
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1439,6 +1462,9 @@ ifndef NO_TCLTK
endif
ifndef NO_PERL
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
endif
ifndef NO_PYTHON
$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
endif
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
@@ -1566,11 +1592,41 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
mv $@+ $@
endif # NO_PERL
ifdef JSMIN
gitweb/gitweb.min.js: gitweb/gitweb.js
$(QUIET_GEN)$(JSMIN) <$< >$@
endif # JSMIN
ifndef NO_PYTHON
$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS
$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
$(QUIET_GEN)$(RM) $@ $@+ && \
INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
--no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
instlibdir` && \
sed -e '1{' \
-e ' s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
-e '}' \
-e 's|^import sys.*|&; \\\
import os; \\\
sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\
os.environ["GITPYTHONLIB"] or \\\
"@@INSTLIBDIR@@"|' \
-e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
$@.py >$@+ && \
chmod +x $@+ && \
mv $@+ $@
else # NO_PYTHON
$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
unimplemented.sh >$@+ && \
chmod +x $@+ && \
mv $@+ $@
endif # NO_PYTHON
configure: configure.ac
$(QUIET_GEN)$(RM) $@ $<+ && \
sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1588,7 +1644,7 @@ git.o git.spec \
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
%.s: %.c GIT-CFLAGS
$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
%.o: %.S
%.o: %.S GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
exec_cmd.o: exec_cmd.c GIT-CFLAGS
@@ -1697,6 +1753,7 @@ GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
### Detect Tck/Tk interpreter path changes
ifndef NO_TCLTK
@@ -1714,19 +1771,30 @@ endif
### Testing rules
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
TEST_PROGRAMS_NEED_X += test-chmtime
TEST_PROGRAMS_NEED_X += test-ctype
TEST_PROGRAMS_NEED_X += test-date
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-match-trees
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain
all:: $(TEST_PROGRAMS)
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
all:: $(TEST_PROGRAMS) $(test_bindir_programs)
bin-wrappers/%: wrap-for-bin.sh
@mkdir -p bin-wrappers
$(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@@BUILD_DIR@@|$(shell pwd)|' \
-e 's|@@PROG@@|$(@F)|' < $< > $@ && \
chmod +x $@
# GNU make supports exporting all variables by "export" without parameters.
# However, the environment gets quite big, and some programs have problems
@@ -1787,15 +1855,20 @@ endif
gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir))
export gitexec_instdir
install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
install: all
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
ifndef NO_PERL
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
endif
ifndef NO_PYTHON
$(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
endif
ifndef NO_TCLTK
$(MAKE) -C gitk-git install
$(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
@@ -1902,6 +1975,7 @@ clean:
$(LIB_FILE) $(XDIFF_LIB)
$(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
$(RM) $(TEST_PROGRAMS)
$(RM) -r bin-wrappers
$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope*
$(RM) -r autom4te.cache
$(RM) config.log config.mak.autogen config.mak.append config.status config.cache
@@ -1912,6 +1986,9 @@ clean:
ifndef NO_PERL
$(RM) gitweb/gitweb.cgi
$(MAKE) -C perl clean
endif
ifndef NO_PYTHON
$(MAKE) -C git_remote_helpers clean
endif
$(MAKE) -C templates/ clean
$(MAKE) -C t/ clean

View File

@@ -1 +1 @@
Documentation/RelNotes-1.6.6.txt
Documentation/RelNotes-1.7.0.txt

View File

@@ -57,14 +57,8 @@ int decode_85(char *dst, const char *buffer, int len)
de = de85[ch];
if (--de < 0)
return error("invalid base85 alphabet %c", ch);
/*
* Detect overflow. The largest
* 5-letter possible is "|NsC0" to
* encode 0xffffffff, and "|NsC" gives
* 0x03030303 at this point (i.e.
* 0xffffffff = 0x03030303 * 85).
*/
if (0x03030303 < acc ||
/* Detect overflow. */
if (0xffffffff / 85 < acc ||
0xffffffff - de < (acc *= 85))
return error("invalid base85 sequence %.5s", buffer-5);
acc += de;
@@ -84,8 +78,6 @@ int decode_85(char *dst, const char *buffer, int len)
void encode_85(char *buf, const unsigned char *data, int bytes)
{
prep_base85();
say("encode 85");
while (bytes) {
unsigned acc = 0;
@@ -118,7 +110,7 @@ int main(int ac, char **av)
int len = strlen(av[2]);
encode_85(buf, av[2], len);
if (len <= 26) len = len + 'A' - 1;
else len = len + 'a' - 26 + 1;
else len = len + 'a' - 26 - 1;
printf("encoded: %c%s\n", len, buf);
return 0;
}

View File

@@ -813,11 +813,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
char *bad_hex = sha1_to_hex(current_bad_sha1);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
fprintf(stderr, "Warning: the merge base between %s and [%s] "
warning("the merge base between %s and [%s] "
"must be skipped.\n"
"So we cannot be sure the first bad commit is "
"between %s and %s.\n"
"We continue anyway.\n",
"We continue anyway.",
bad_hex, good_hex, mb_hex, bad_hex);
free(good_hex);
}

View File

@@ -638,10 +638,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
rename_branch(head, argv[0], rename > 1);
else if (rename && (argc == 2))
rename_branch(argv[0], argv[1], rename > 1);
else if (argc <= 2)
else if (argc <= 2) {
if (kinds != REF_LOCAL_BRANCH)
die("-a and -r options to 'git branch' do not make sense with a branch name");
create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
force_create, reflog, track);
else
} else
usage_with_options(builtin_branch_usage, options);
return 0;

View File

@@ -397,7 +397,7 @@ static int merge_working_tree(struct checkout_opts *opts,
topts.initial_checkout = is_cache_unborn();
topts.update = 1;
topts.merge = 1;
topts.gently = opts->merge;
topts.gently = opts->merge && old->commit;
topts.verbose_update = !opts->quiet;
topts.fn = twoway_merge;
topts.dir = xcalloc(1, sizeof(*topts.dir));
@@ -422,7 +422,13 @@ static int merge_working_tree(struct checkout_opts *opts,
struct merge_options o;
if (!opts->merge)
return 1;
parse_commit(old->commit);
/*
* Without old->commit, the below is the same as
* the two-tree unpack we already tried and failed.
*/
if (!old->commit)
return 1;
/* Do more real merge */

View File

@@ -362,9 +362,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
const char *repo_name, *repo, *work_tree, *git_dir;
char *path, *dir;
int dest_exists;
const struct ref *refs, *remote_head, *mapped_refs;
const struct ref *refs, *remote_head;
const struct ref *remote_head_points_at;
const struct ref *our_head_points_at;
struct ref *mapped_refs;
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;

View File

@@ -24,6 +24,7 @@
#include "string-list.h"
#include "rerere.h"
#include "unpack-trees.h"
#include "quote.h"
static const char * const builtin_commit_usage[] = {
"git commit [options] [--] <filepattern>...",
@@ -35,7 +36,7 @@ static const char * const builtin_status_usage[] = {
NULL
};
static unsigned char head_sha1[20], merge_head_sha1[20];
static unsigned char head_sha1[20];
static char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */
@@ -52,7 +53,7 @@ static char *edit_message, *use_message;
static char *author_name, *author_email, *author_date;
static int all, edit_flag, also, interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static char *untracked_files_arg;
static char *untracked_files_arg, *force_date;
/*
* The default commit message cleanup mode will remove the lines
* beginning with # (shell comments) and leading and trailing
@@ -71,6 +72,13 @@ static int use_editor = 1, initial_commit, in_merge;
static const char *only_include_assumed;
static struct strbuf message;
static int null_termination;
static enum {
STATUS_FORMAT_LONG,
STATUS_FORMAT_SHORT,
STATUS_FORMAT_PORCELAIN,
} status_format = STATUS_FORMAT_LONG;
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
struct strbuf *buf = opt->value;
@@ -86,10 +94,11 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
static struct option builtin_commit_options[] = {
OPT__QUIET(&quiet),
OPT__VERBOSE(&verbose),
OPT_GROUP("Commit message options"),
OPT_GROUP("Commit message options"),
OPT_FILENAME('F', "file", &logfile, "read log from file"),
OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
@@ -97,6 +106,8 @@ static struct option builtin_commit_options[] = {
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_FILENAME('t', "template", &template_file, "use specified template file"),
OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
/* end commit message options */
OPT_GROUP("Commit contents options"),
OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
@@ -105,10 +116,16 @@ static struct option builtin_commit_options[] = {
OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
OPT_SET_INT(0, "short", &status_format, "show status concisely",
STATUS_FORMAT_SHORT),
OPT_SET_INT(0, "porcelain", &status_format,
"show porcelain output format", STATUS_FORMAT_PORCELAIN),
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
/* end commit contents options */
OPT_END()
};
@@ -306,7 +323,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
*/
commit_style = COMMIT_PARTIAL;
if (file_exists(git_path("MERGE_HEAD")))
if (in_merge)
die("cannot do a partial commit during a merge.");
memset(&partial, 0, sizeof(partial));
@@ -347,6 +364,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
struct wt_status *s)
{
unsigned char sha1[20];
if (s->relative_paths)
s->prefix = prefix;
@@ -358,8 +377,21 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
s->index_file = index_file;
s->fp = fp;
s->nowarn = nowarn;
s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
wt_status_print(s);
wt_status_collect(s);
switch (status_format) {
case STATUS_FORMAT_SHORT:
wt_shortstatus_print(s, null_termination);
break;
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(s, null_termination);
break;
case STATUS_FORMAT_LONG:
wt_status_print(s);
break;
}
return s->commitable;
}
@@ -410,6 +442,9 @@ static void determine_author_info(void)
email = xstrndup(lb + 2, rb - (lb + 2));
}
if (force_date)
date = force_date;
author_name = name;
author_email = email;
author_date = date;
@@ -735,6 +770,21 @@ static const char *find_author_by_nickname(const char *name)
die("No existing author found with '%s'", name);
}
static void handle_untracked_files_arg(struct wt_status *s)
{
if (!untracked_files_arg)
; /* default already initialized */
else if (!strcmp(untracked_files_arg, "no"))
s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
else if (!strcmp(untracked_files_arg, "normal"))
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
else if (!strcmp(untracked_files_arg, "all"))
s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else
die("Invalid untracked files mode '%s'", untracked_files_arg);
}
static int parse_and_validate_options(int argc, const char *argv[],
const char * const usage[],
const char *prefix,
@@ -761,9 +811,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (get_sha1("HEAD", head_sha1))
initial_commit = 1;
if (!get_sha1("MERGE_HEAD", merge_head_sha1))
in_merge = 1;
/* Sanity check options */
if (amend && initial_commit)
die("You have nothing to amend.");
@@ -843,22 +890,18 @@ static int parse_and_validate_options(int argc, const char *argv[],
else
die("Invalid cleanup mode %s", cleanup_arg);
if (!untracked_files_arg)
; /* default already initialized */
else if (!strcmp(untracked_files_arg, "no"))
s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
else if (!strcmp(untracked_files_arg, "normal"))
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
else if (!strcmp(untracked_files_arg, "all"))
s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
else
die("Invalid untracked files mode '%s'", untracked_files_arg);
handle_untracked_files_arg(s);
if (all && argc > 0)
die("Paths with -a does not make sense.");
else if (interactive && argc > 0)
die("Paths with --interactive does not make sense.");
if (null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
if (status_format != STATUS_FORMAT_LONG)
dry_run = 1;
return argc;
}
@@ -940,17 +983,63 @@ static int git_status_config(const char *k, const char *v, void *cb)
int cmd_status(int argc, const char **argv, const char *prefix)
{
struct wt_status s;
unsigned char sha1[20];
static struct option builtin_status_options[] = {
OPT__VERBOSE(&verbose),
OPT_SET_INT('s', "short", &status_format,
"show status concisely", STATUS_FORMAT_SHORT),
OPT_SET_INT(0, "porcelain", &status_format,
"show porcelain output format",
STATUS_FORMAT_PORCELAIN),
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
"mode",
"show untracked files, optional modes: all, normal, no. (Default: all)",
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_END(),
};
if (null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
wt_status_prepare(&s);
git_config(git_status_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD"));
argc = parse_options(argc, argv, prefix,
builtin_status_options,
builtin_status_usage, 0);
handle_untracked_files_arg(&s);
if (*argv)
s.pathspec = get_pathspec(prefix, argv);
read_cache();
refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
s.in_merge = in_merge;
wt_status_collect(&s);
if (s.relative_paths)
s.prefix = prefix;
if (s.use_color == -1)
s.use_color = git_use_color_default;
if (diff_use_color_default == -1)
diff_use_color_default = git_use_color_default;
argc = parse_and_validate_options(argc, argv, builtin_status_usage,
prefix, &s);
return dry_run_commit(argc, argv, prefix, &s);
switch (status_format) {
case STATUS_FORMAT_SHORT:
wt_shortstatus_print(&s, null_termination);
break;
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(&s, null_termination);
break;
case STATUS_FORMAT_LONG:
s.verbose = verbose;
wt_status_print(&s);
break;
}
return 0;
}
static void print_summary(const char *prefix, const unsigned char *sha1)
@@ -1026,10 +1115,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s);
git_config(git_commit_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD"));
s.in_merge = in_merge;
if (s.use_color == -1)
s.use_color = git_use_color_default;
argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
prefix, &s);
if (dry_run) {

View File

@@ -45,6 +45,7 @@ static int end_null;
#define TYPE_BOOL (1<<0)
#define TYPE_INT (1<<1)
#define TYPE_BOOL_OR_INT (1<<2)
#define TYPE_PATH (1<<3)
static struct option builtin_config_options[] = {
OPT_GROUP("Config file location"),
@@ -69,6 +70,7 @@ static struct option builtin_config_options[] = {
OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
OPT_GROUP("Other"),
OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
OPT_END(),
@@ -94,6 +96,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
{
char value[256];
const char *vptr = value;
int must_free_vptr = 0;
int dup_error = 0;
if (!use_key_regexp && strcmp(key_, key))
@@ -123,6 +126,9 @@ static int show_config(const char *key_, const char *value_, void *cb)
vptr = v ? "true" : "false";
else
sprintf(value, "%d", v);
} else if (types == TYPE_PATH) {
git_config_pathname(&vptr, key_, value_);
must_free_vptr = 1;
}
else
vptr = value_?value_:"";
@@ -133,6 +139,12 @@ static int show_config(const char *key_, const char *value_, void *cb)
}
else
printf("%s%c", vptr, term);
if (must_free_vptr)
/* If vptr must be freed, it's a pointer to a
* dynamically allocated buffer, it's safe to cast to
* const.
*/
free((char *)vptr);
return 0;
}
@@ -215,7 +227,13 @@ static char *normalize_value(const char *key, const char *value)
if (!value)
return NULL;
if (types == 0)
if (types == 0 || types == TYPE_PATH)
/*
* We don't do normalization for TYPE_PATH here: If
* the path is like ~/foobar/, we prefer to store
* "~/foobar/" in the config file, and to expand the ~
* when retrieving the value.
*/
normalized = xstrdup(value);
else {
normalized = xmalloc(64);

View File

@@ -11,7 +11,7 @@
static void count_objects(DIR *d, char *path, int len, int verbose,
unsigned long *loose,
unsigned long *loose_size,
off_t *loose_size,
unsigned long *packed_loose,
unsigned long *garbage)
{
@@ -77,7 +77,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
int len = strlen(objdir);
char *path = xmalloc(len + 50);
unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
unsigned long loose_size = 0;
off_t loose_size = 0;
struct option opts[] = {
OPT__VERBOSE(&verbose),
OPT_END(),
@@ -103,7 +103,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
if (verbose) {
struct packed_git *p;
unsigned long num_pack = 0;
unsigned long size_pack = 0;
off_t size_pack = 0;
if (!packed_git)
prepare_packed_git();
for (p = packed_git; p; p = p->next) {
@@ -116,15 +116,15 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
num_pack++;
}
printf("count: %lu\n", loose);
printf("size: %lu\n", loose_size / 1024);
printf("size: %lu\n", (unsigned long) (loose_size / 1024));
printf("in-pack: %lu\n", packed);
printf("packs: %lu\n", num_pack);
printf("size-pack: %lu\n", size_pack / 1024);
printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
printf("prune-packable: %lu\n", packed_loose);
printf("garbage: %lu\n", garbage);
}
else
printf("%lu objects, %lu kilobytes\n",
loose, loose_size / 1024);
loose, (unsigned long) (loose_size / 1024));
return 0;
}

View File

@@ -322,7 +322,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
if (!fp)
return error("cannot open %s: %s\n", filename, strerror(errno));
url = transport_anonymize_url(raw_url);
if (raw_url)
url = transport_anonymize_url(raw_url);
else
url = xstrdup("foreign");
for (rm = ref_map; rm; rm = rm->next) {
struct ref *ref = NULL;
@@ -819,7 +822,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
if (!remote)
die("Where do you want to fetch from today?");
transport = transport_get(remote, remote->url[0]);
transport = transport_get(remote, NULL);
if (verbosity >= 2)
transport->verbose = verbosity <= 3 ? verbosity : 3;
if (verbosity < 0)

View File

@@ -180,12 +180,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
char buf[80];
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet),
{ 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"),
OPT_END()
};

View File

@@ -23,13 +23,14 @@ static struct man_viewer_info_list {
} *man_viewer_info_list;
enum help_format {
HELP_FORMAT_NONE,
HELP_FORMAT_MAN,
HELP_FORMAT_INFO,
HELP_FORMAT_WEB,
};
static int show_all = 0;
static enum help_format help_format = HELP_FORMAT_MAN;
static enum help_format help_format = HELP_FORMAT_NONE;
static struct option builtin_help_options[] = {
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
@@ -415,10 +416,12 @@ int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
const char *alias;
enum help_format parsed_help_format;
load_command_list("git-", &main_cmds, &other_cmds);
argc = parse_options(argc, argv, prefix, builtin_help_options,
builtin_help_usage, 0);
parsed_help_format = help_format;
if (show_all) {
printf("usage: %s\n\n", git_usage_string);
@@ -437,6 +440,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)
setup_git_directory_gently(&nongit);
git_config(git_help_config, NULL);
if (parsed_help_format != HELP_FORMAT_NONE)
help_format = parsed_help_format;
alias = alias_lookup(argv[0]);
if (alias && !is_git_command(argv[0])) {
printf("`git %s' is aliased to `%s'\n", argv[0], alias);
@@ -444,6 +450,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
}
switch (help_format) {
case HELP_FORMAT_NONE:
case HELP_FORMAT_MAN:
show_man_page(argv[0]);
break;

View File

@@ -567,7 +567,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev)
get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
if (!DIFF_OPT_TST(&rev->diffopt, QUIET))
if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
if (freopen(filename.buf, "w", stdout) == NULL)

View File

@@ -89,7 +89,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
remote = remote_get(dest);
if (!remote->url_nr)
die("remote %s has no configured URL", dest);
transport = transport_get(remote, remote->url[0]);
transport = transport_get(remote, NULL);
if (uploadpack != NULL)
transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);

View File

@@ -169,9 +169,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
* check both source and destination
*/
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
fprintf(stderr, "Warning: %s;"
" will overwrite!\n",
bad);
warning("%s; will overwrite!", bad);
bad = NULL;
} else
bad = "Cannot overwrite";

View File

@@ -15,6 +15,7 @@ static const char * const push_usage[] = {
};
static int thin;
static int deleterefs;
static const char *receivepack;
static const char **refspec;
@@ -39,11 +40,24 @@ static void set_refspecs(const char **refs, int nr)
if (nr <= ++i)
die("tag shorthand without <tag>");
len = strlen(refs[i]) + 11;
tag = xmalloc(len);
strcpy(tag, "refs/tags/");
if (deleterefs) {
tag = xmalloc(len+1);
strcpy(tag, ":refs/tags/");
} else {
tag = xmalloc(len);
strcpy(tag, "refs/tags/");
}
strcat(tag, refs[i]);
ref = tag;
}
} else if (deleterefs && !strchr(ref, ':')) {
char *delref;
int len = strlen(ref)+1;
delref = xmalloc(len);
strcpy(delref, ":");
strcat(delref, ref);
ref = delref;
} else if (deleterefs)
die("--delete only accepts plain target ref names");
add_refspec(ref);
}
}
@@ -87,6 +101,37 @@ static void setup_default_push_refspecs(void)
}
}
static int push_with_options(struct transport *transport, int flags)
{
int err;
int nonfastforward;
if (receivepack)
transport_set_option(transport,
TRANS_OPT_RECEIVEPACK, receivepack);
if (thin)
transport_set_option(transport, TRANS_OPT_THIN, "yes");
if (flags & TRANSPORT_PUSH_VERBOSE)
fprintf(stderr, "Pushing to %s\n", transport->url);
err = transport_push(transport, refspec_nr, refspec, flags,
&nonfastforward);
if (err != 0)
error("failed to push some refs to '%s'", transport->url);
err |= transport_disconnect(transport);
if (!err)
return 0;
if (nonfastforward && advice_push_nonfastforward) {
printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
"Merge the remote changes before pushing again. See the 'non-fast-forward'\n"
"section of 'git push --help' for details.\n");
}
return 1;
}
static int do_push(const char *repo, int flags)
{
int i, errs;
@@ -135,33 +180,19 @@ static int do_push(const char *repo, int flags)
url = remote->url;
url_nr = remote->url_nr;
}
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
int err;
int nonfastforward;
if (receivepack)
transport_set_option(transport,
TRANS_OPT_RECEIVEPACK, receivepack);
if (thin)
transport_set_option(transport, TRANS_OPT_THIN, "yes");
if (flags & TRANSPORT_PUSH_VERBOSE)
fprintf(stderr, "Pushing to %s\n", url[i]);
err = transport_push(transport, refspec_nr, refspec, flags,
&nonfastforward);
err |= transport_disconnect(transport);
if (!err)
continue;
error("failed to push some refs to '%s'", url[i]);
if (nonfastforward && advice_push_nonfastforward) {
printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
"Merge the remote changes before pushing again. See the 'non-fast-forward'\n"
"section of 'git push --help' for details.\n");
if (url_nr) {
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
if (push_with_options(transport, flags))
errs++;
}
errs++;
} else {
struct transport *transport =
transport_get(remote, NULL);
if (push_with_options(transport, flags))
errs++;
}
return !!errs;
}
@@ -179,6 +210,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
(TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
@@ -192,6 +224,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
die("--delete is incompatible with --all, --mirror and --tags");
if (deleterefs && argc < 2)
die("--delete doesn't make sense without any refs");
if (tags)
add_refspec("refs/tags/*");

View File

@@ -204,59 +204,47 @@ static int is_ref_checked_out(const char *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.",
static char *refuse_unconfigured_deny_msg[] = {
"By default, updating the current branch in a non-bare repository",
"is denied, because it will make the index and work tree inconsistent",
"with what you pushed, and will require 'git reset --hard' to match",
"the work tree to HEAD.",
"",
"You can set 'receive.denyCurrentBranch' configuration variable to",
"'refuse' in the remote repository to forbid pushing into its",
"current branch."
"'ignore' or 'warn' in the remote repository to allow pushing into",
"its current branch; however, this is not recommended unless you",
"arranged to update its work tree to match what you pushed in some",
"other way.",
"",
"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'."
"To squelch this message and still keep the default behaviour, set",
"'receive.denyCurrentBranch' configuration variable to 'refuse'."
};
static void warn_unconfigured_deny(void)
static void refuse_unconfigured_deny(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
warning("%s", warn_unconfigured_deny_msg[i]);
for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
error("%s", refuse_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.",
static char *refuse_unconfigured_deny_delete_current_msg[] = {
"By default, deleting the current branch is denied, because the next",
"'git clone' won't result in any file checked out, causing confusion.",
"",
"You can set 'receive.denyDeleteCurrent' configuration variable to",
"'refuse' in the remote repository to disallow deleting the current",
"branch.",
"'warn' or 'ignore' in the remote repository to allow deleting the",
"current branch, with or without a warning message.",
"",
"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'."
"To squelch this message, you can set it to 'refuse'."
};
static void warn_unconfigured_deny_delete_current(void)
static void refuse_unconfigured_deny_delete_current(void)
{
int i;
for (i = 0;
i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
i++)
warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
}
static const char *update(struct command *cmd)
@@ -276,14 +264,14 @@ static const char *update(struct command *cmd)
switch (deny_current_branch) {
case DENY_IGNORE:
break;
case DENY_UNCONFIGURED:
case DENY_WARN:
warning("updating the current branch");
if (deny_current_branch == DENY_UNCONFIGURED)
warn_unconfigured_deny();
break;
case DENY_REFUSE:
case DENY_UNCONFIGURED:
error("refusing to update checked out branch: %s", name);
if (deny_current_branch == DENY_UNCONFIGURED)
refuse_unconfigured_deny();
return "branch is currently checked out";
}
}
@@ -305,12 +293,12 @@ static const char *update(struct command *cmd)
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:
case DENY_UNCONFIGURED:
if (deny_delete_current == DENY_UNCONFIGURED)
refuse_unconfigured_deny_delete_current();
error("refusing to delete the current branch: %s", name);
return "deletion of the current branch prohibited";
}

View File

@@ -1238,13 +1238,11 @@ static int update(int argc, const char **argv)
fetch_argv[fetch_argc++] = "--prune";
if (verbose)
fetch_argv[fetch_argc++] = "-v";
if (argc < 2) {
fetch_argv[fetch_argc++] = "--multiple";
if (argc < 2)
fetch_argv[fetch_argc++] = "default";
} else {
fetch_argv[fetch_argc++] = "--multiple";
for (i = 1; i < argc; i++)
fetch_argv[fetch_argc++] = argv[i];
}
for (i = 1; i < argc; i++)
fetch_argv[fetch_argc++] = argv[i];
if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
git_config(get_remote_default, &default_defined);

View File

@@ -202,6 +202,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
struct commit *commit;
char *reflog_action, msg[1024];
const struct option options[] = {
OPT__QUIET(&quiet),
OPT_SET_INT(0, "mixed", &reset_type,
"reset HEAD and index", MIXED),
OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
@@ -209,8 +210,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
"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_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
OPT_END()
};
@@ -286,10 +285,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (reset_type == NONE)
reset_type = MIXED; /* by default */
if ((reset_type == HARD || reset_type == MERGE)
&& !is_inside_work_tree())
die("%s reset requires a work tree",
reset_type_names[reset_type]);
if (reset_type == HARD || reset_type == MERGE)
setup_work_tree();
/* Soft reset does not touch the index file nor the working tree
* at all, but requires them in a good order. Other resets reset

View File

@@ -322,7 +322,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (revs.bisect)
bisect_list = 1;
quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];

View File

@@ -140,7 +140,7 @@ static int delete_tag(const char *name, const char *ref,
{
if (delete_ref(ref, sha1, 0))
return 1;
printf("Deleted tag '%s'\n", name);
printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
return 0;
}
@@ -479,6 +479,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
die("%s: cannot lock the ref", ref);
if (write_ref_sha1(lock, object, NULL) < 0)
die("%s: cannot update the ref", ref);
if (force && hashcmp(prev, object))
printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
strbuf_release(&buf);
return 0;

View File

@@ -702,7 +702,11 @@ static inline unsigned int hexval(unsigned char c)
#define DEFAULT_ABBREV 7
extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int gently, const char *prefix);
static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
{
return get_sha1_with_mode_1(str, sha1, mode, 1, NULL);
}
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);

View File

@@ -49,6 +49,7 @@ git-grep mainporcelain common
git-gui mainporcelain
git-hash-object plumbingmanipulators
git-help ancillaryinterrogators
git-http-backend synchingrepositories
git-http-fetch synchelpers
git-http-push synchelpers
git-imap-send foreignscminterface

View File

@@ -276,6 +276,9 @@ GIT_ARG_SET_PATH(shell)
# Define PERL_PATH to provide path to Perl.
GIT_ARG_SET_PATH(perl)
#
# Define PYTHON_PATH to provide path to Python.
GIT_ARG_SET_PATH(python)
#
# Define ZLIB_PATH to provide path to zlib.
GIT_ARG_SET_PATH(zlib)
#

View File

@@ -142,11 +142,9 @@ __git_ps1 ()
elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
git diff --no-ext-diff --ignore-submodules \
--quiet --exit-code || w="*"
git diff --no-ext-diff --quiet --exit-code || w="*"
if git rev-parse --quiet --verify HEAD >/dev/null; then
git diff-index --cached --quiet \
--ignore-submodules HEAD -- || i="+"
git diff-index --cached --quiet HEAD -- || i="+"
else
i="#"
fi
@@ -163,11 +161,8 @@ __git_ps1 ()
fi
fi
if [ -n "${1-}" ]; then
printf "$1" "$c${b##refs/heads/}$w$i$s$u$r"
else
printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r"
fi
local f="$w$i$s$u"
printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
fi
}
@@ -2022,7 +2017,7 @@ _git_svn ()
init fetch clone rebase dcommit log find-rev
set-tree commit-diff info create-ignore propget
proplist show-ignore show-externals branch tag blame
migrate
migrate mkdirs reset gc
"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
@@ -2069,7 +2064,7 @@ _git_svn ()
__gitcomp "--stdin $cmt_opts $fc_opts"
;;
create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
show-externals,--*)
show-externals,--*|mkdirs,--*)
__gitcomp "--revision="
;;
log,--*)
@@ -2106,6 +2101,9 @@ _git_svn ()
--no-auth-cache --username=
"
;;
reset,--*)
__gitcomp "--revision= --parent"
;;
*)
COMPREPLY=()
;;

View File

@@ -562,6 +562,24 @@ static int execute(struct sockaddr *addr)
return -1;
}
static int addrcmp(const struct sockaddr_storage *s1,
const struct sockaddr_storage *s2)
{
if (s1->ss_family != s2->ss_family)
return s1->ss_family - s2->ss_family;
if (s1->ss_family == AF_INET)
return memcmp(&((struct sockaddr_in *)s1)->sin_addr,
&((struct sockaddr_in *)s2)->sin_addr,
sizeof(struct in_addr));
#ifndef NO_IPV6
if (s1->ss_family == AF_INET6)
return memcmp(&((struct sockaddr_in6 *)s1)->sin6_addr,
&((struct sockaddr_in6 *)s2)->sin6_addr,
sizeof(struct in6_addr));
#endif
return 0;
}
static int max_connections = 32;
static unsigned int live_children;
@@ -576,17 +594,12 @@ static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
{
struct child *newborn, **cradle;
/*
* This must be xcalloc() -- we'll compare the whole sockaddr_storage
* but individual address may be shorter.
*/
newborn = xcalloc(1, sizeof(*newborn));
live_children++;
newborn->pid = pid;
memcpy(&newborn->address, addr, addrlen);
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
if (!memcmp(&(*cradle)->address, &newborn->address,
sizeof(newborn->address)))
if (!addrcmp(&(*cradle)->address, &newborn->address))
break;
newborn->next = *cradle;
*cradle = newborn;
@@ -619,8 +632,7 @@ static void kill_some_child(void)
return;
for (; (next = blanket->next); blanket = next)
if (!memcmp(&blanket->address, &next->address,
sizeof(next->address))) {
if (!addrcmp(&blanket->address, &next->address)) {
kill(blanket->pid, SIGTERM);
break;
}

View File

@@ -73,7 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
struct cache_entry *ce = active_cache[i];
int changed;
if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
break;
@@ -507,7 +507,7 @@ int index_differs_from(const char *def, int diff_flags)
init_revisions(&rev, NULL);
setup_revisions(0, NULL, &rev, def);
DIFF_OPT_SET(&rev.diffopt, QUIET);
DIFF_OPT_SET(&rev.diffopt, QUICK);
DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
rev.diffopt.flags |= diff_flags;
run_diff_index(&rev, 1);

75
diff.c
View File

@@ -194,6 +194,7 @@ struct emit_callback {
struct diff_words_data *diff_words;
int *found_changesp;
FILE *file;
struct strbuf *header;
};
static int count_lines(const char *data, int size)
@@ -797,6 +798,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
if (ecbdata->header) {
fprintf(ecbdata->file, "%s", ecbdata->header->buf);
strbuf_reset(ecbdata->header);
ecbdata->header = NULL;
}
*(ecbdata->found_changesp) = 1;
if (ecbdata->label_path[0]) {
@@ -1601,6 +1607,7 @@ static void builtin_diff(const char *name_a,
const char *reset = diff_get_color_opt(o, DIFF_RESET);
const char *a_prefix, *b_prefix;
const char *textconv_one = NULL, *textconv_two = NULL;
struct strbuf header = STRBUF_INIT;
if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
(!one->mode || S_ISGITLINK(one->mode)) &&
@@ -1635,25 +1642,26 @@ static void builtin_diff(const char *name_a,
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
if (lbl[0][0] == '/') {
/* /dev/null */
fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
if (xfrm_msg && xfrm_msg[0])
fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
}
else if (lbl[1][0] == '/') {
fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
if (xfrm_msg && xfrm_msg[0])
fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
}
else {
if (one->mode != two->mode) {
fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
}
if (xfrm_msg && xfrm_msg[0])
fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
/*
* we do not run diff between different kind
* of objects.
@@ -1663,6 +1671,8 @@ static void builtin_diff(const char *name_a,
if (complete_rewrite &&
(textconv_one || !diff_filespec_is_binary(one)) &&
(textconv_two || !diff_filespec_is_binary(two))) {
fprintf(o->file, "%s", header.buf);
strbuf_reset(&header);
emit_rewrite_diff(name_a, name_b, one, two,
textconv_one, textconv_two, o);
o->found_changes = 1;
@@ -1680,6 +1690,8 @@ static void builtin_diff(const char *name_a,
if (mf1.size == mf2.size &&
!memcmp(mf1.ptr, mf2.ptr, mf1.size))
goto free_ab_and_return;
fprintf(o->file, "%s", header.buf);
strbuf_reset(&header);
if (DIFF_OPT_TST(o, BINARY))
emit_binary_diff(o->file, &mf1, &mf2);
else
@@ -1696,6 +1708,11 @@ static void builtin_diff(const char *name_a,
struct emit_callback ecbdata;
const struct userdiff_funcname *pe;
if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
fprintf(o->file, "%s", header.buf);
strbuf_reset(&header);
}
if (textconv_one) {
size_t size;
mf1.ptr = run_textconv(textconv_one, one, &size);
@@ -1725,6 +1742,7 @@ static void builtin_diff(const char *name_a,
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
check_blank_at_eof(&mf1, &mf2, &ecbdata);
ecbdata.file = o->file;
ecbdata.header = header.len ? &header : NULL;
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.interhunkctxlen = o->interhunkcontext;
@@ -1769,6 +1787,7 @@ static void builtin_diff(const char *name_a,
}
free_ab_and_return:
strbuf_release(&header);
diff_free_filespec_data(one);
diff_free_filespec_data(two);
free(a_one);
@@ -2551,6 +2570,20 @@ int diff_setup_done(struct diff_options *options)
if (count > 1)
die("--name-only, --name-status, --check and -s are mutually exclusive");
/*
* Most of the time we can say "there are changes"
* only by checking if there are changed paths, but
* --ignore-whitespace* options force us to look
* inside contents.
*/
if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
else
DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
options->detect_rename = DIFF_DETECT_COPY;
@@ -2611,7 +2644,7 @@ int diff_setup_done(struct diff_options *options)
* to have found. It does not make sense not to return with
* exit code in such a case either.
*/
if (DIFF_OPT_TST(options, QUIET)) {
if (DIFF_OPT_TST(options, QUICK)) {
options->output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
}
@@ -2802,7 +2835,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--exit-code"))
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
else if (!strcmp(arg, "--quiet"))
DIFF_OPT_SET(options, QUIET);
DIFF_OPT_SET(options, QUICK);
else if (!strcmp(arg, "--ext-diff"))
DIFF_OPT_SET(options, ALLOW_EXTERNAL);
else if (!strcmp(arg, "--no-ext-diff"))
@@ -3509,6 +3542,18 @@ free_queue:
q->nr = q->alloc = 0;
if (options->close_file)
fclose(options->file);
/*
* Report the content-level differences with HAS_CHANGES;
* diff_addremove/diff_change does not set the bit when
* DIFF_FROM_CONTENTS is in effect (e.g. with -w).
*/
if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
if (options->found_changes)
DIFF_OPT_SET(options, HAS_CHANGES);
else
DIFF_OPT_CLR(options, HAS_CHANGES);
}
}
static void diffcore_apply_filter(const char *filter)
@@ -3645,7 +3690,7 @@ void diffcore_std(struct diff_options *options)
diff_resolve_rename_copy();
diffcore_apply_filter(options->filter);
if (diff_queued_diff.nr)
if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
DIFF_OPT_SET(options, HAS_CHANGES);
else
DIFF_OPT_CLR(options, HAS_CHANGES);
@@ -3705,7 +3750,8 @@ void diff_addremove(struct diff_options *options,
fill_filespec(two, sha1, mode);
diff_queue(&diff_queued_diff, one, two);
DIFF_OPT_SET(options, HAS_CHANGES);
if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
DIFF_OPT_SET(options, HAS_CHANGES);
}
void diff_change(struct diff_options *options,
@@ -3737,7 +3783,8 @@ void diff_change(struct diff_options *options,
fill_filespec(two, new_sha1, new_mode);
diff_queue(&diff_queued_diff, one, two);
DIFF_OPT_SET(options, HAS_CHANGES);
if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
DIFF_OPT_SET(options, HAS_CHANGES);
}
void diff_unmerge(struct diff_options *options,
@@ -3776,11 +3823,13 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
if (start_command(&child) != 0 ||
strbuf_read(&buf, child.out, 0) < 0 ||
finish_command(&child) != 0) {
close(child.out);
strbuf_release(&buf);
remove_tempfile();
error("error running textconv command '%s'", pgm);
return NULL;
}
close(child.out);
remove_tempfile();
return strbuf_detach(&buf, outsize);

4
diff.h
View File

@@ -55,7 +55,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_OPT_COLOR_DIFF (1 << 8)
#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9)
#define DIFF_OPT_HAS_CHANGES (1 << 10)
#define DIFF_OPT_QUIET (1 << 11)
#define DIFF_OPT_QUICK (1 << 11)
#define DIFF_OPT_NO_INDEX (1 << 12)
#define DIFF_OPT_ALLOW_EXTERNAL (1 << 13)
#define DIFF_OPT_EXIT_WITH_STATUS (1 << 14)
@@ -66,7 +66,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19)
#define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20)
#define DIFF_OPT_ALLOW_TEXTCONV (1 << 21)
#define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22)
#define DIFF_OPT_SUBMODULE_LOG (1 << 23)
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)

View File

@@ -19,8 +19,8 @@ Format of STDIN stream:
new_commit ::= 'commit' sp ref_str lf
mark?
('author' sp name sp '<' email '>' sp when lf)?
'committer' sp name sp '<' email '>' sp when lf
('author' (sp name)? sp '<' email '>' sp when lf)?
'committer' (sp name)? sp '<' email '>' sp when lf
commit_msg
('from' sp committish lf)?
('merge' sp committish lf)*
@@ -47,7 +47,7 @@ Format of STDIN stream:
new_tag ::= 'tag' sp tag_str lf
'from' sp committish lf
('tagger' sp name sp '<' email '>' sp when lf)?
('tagger' (sp name)? sp '<' email '>' sp when lf)?
tag_msg;
tag_msg ::= data;

View File

@@ -104,6 +104,7 @@ $log->info("--------------- STARTING -----------------");
my $usage =
"Usage: git cvsserver [options] [pserver|server] [<directory> ...]\n".
" --base-path <path> : Prepend to requested CVSROOT\n".
" Can be read from GIT_CVSSERVER_BASE_PATH\n".
" --strict-paths : Don't allow recursing into subdirectories\n".
" --export-all : Don't check for gitcvs.enabled in config\n".
" --version, -V : Print version information and exit\n".
@@ -111,7 +112,8 @@ my $usage =
"\n".
"<directory> ... is a list of allowed directories. If no directories\n".
"are given, all are allowed. This is an additional restriction, gitcvs\n".
"access still needs to be enabled by the gitcvs.enabled config option.\n";
"access still needs to be enabled by the gitcvs.enabled config option.\n".
"Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
my @opts = ( 'help|h|H', 'version|V',
'base-path=s', 'strict-paths', 'export-all' );
@@ -148,6 +150,24 @@ if ($state->{'export-all'} && !@{$state->{allowed_roots}}) {
die "--export-all can only be used together with an explicit whitelist\n";
}
# Environment handling for running under git-shell
if (exists $ENV{GIT_CVSSERVER_BASE_PATH}) {
if ($state->{'base-path'}) {
die "Cannot specify base path both ways.\n";
}
my $base_path = $ENV{GIT_CVSSERVER_BASE_PATH};
$state->{'base-path'} = $base_path;
$log->debug("Picked up base path '$base_path' from environment.\n");
}
if (exists $ENV{GIT_CVSSERVER_ROOT}) {
if (@{$state->{allowed_roots}}) {
die "Cannot specify roots both ways: @ARGV\n";
}
my $allowed_root = $ENV{GIT_CVSSERVER_ROOT};
$state->{allowed_roots} = [ $allowed_root ];
$log->debug("Picked up allowed root '$allowed_root' from environment.\n");
}
# if we are called with a pserver argument,
# deal with the authentication cat before entering the
# main loop
@@ -981,6 +1001,8 @@ sub req_update
#$log->debug("update state : " . Dumper($state));
my $last_dirname = "///";
# foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
@@ -988,6 +1010,20 @@ sub req_update
$log->debug("Processing file $filename");
unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
{
my $cur_dirname = dirname($filename);
if ( $cur_dirname ne $last_dirname )
{
$last_dirname = $cur_dirname;
if ( $cur_dirname eq "" )
{
$cur_dirname = ".";
}
print "E cvs update: Updating $cur_dirname\n";
}
}
# if we have a -C we should pretend we never saw modified stuff
if ( exists ( $state->{opt}{C} ) )
{

View File

@@ -259,7 +259,6 @@ test -s "$tempdir"/heads ||
GIT_INDEX_FILE="$(pwd)/../index"
export GIT_INDEX_FILE
git read-tree || die "Could not seed the index"
# map old->new commit ids for rewriting parents
mkdir ../map || die "Could not create map/ directory"

View File

@@ -44,9 +44,8 @@ esac
# MRC is the current "merge reference commit"
# MRT is the current "merge result tree"
MRC=$head MSG= PARENT="-p $head"
MRC=$(git rev-parse --verify -q $head)
MRT=$(git write-tree)
CNT=1 ;# counting our head
NON_FF_MERGE=0
OCTOPUS_FAILURE=0
for SHA1 in $remotes
@@ -61,19 +60,17 @@ do
exit 2
esac
eval pretty_name=\${GITHEAD_$SHA1:-$SHA1}
common=$(git merge-base --all $SHA1 $MRC) ||
die "Unable to find common commit with $SHA1"
die "Unable to find common commit with $pretty_name"
case "$LF$common$LF" in
*"$LF$SHA1$LF"*)
echo "Already up-to-date with $SHA1"
echo "Already up-to-date with $pretty_name"
continue
;;
esac
CNT=`expr $CNT + 1`
PARENT="$PARENT -p $SHA1"
if test "$common,$NON_FF_MERGE" = "$MRC,0"
then
# The first head being merged was a fast-forward.
@@ -81,7 +78,7 @@ do
# tree as the intermediate result of the merge.
# We still need to count this as part of the parent set.
echo "Fast-forwarding to: $SHA1"
echo "Fast-forwarding to: $pretty_name"
git read-tree -u -m $head $SHA1 || exit
MRC=$SHA1 MRT=$(git write-tree)
continue
@@ -89,7 +86,7 @@ do
NON_FF_MERGE=1
echo "Trying simple merge with $SHA1"
echo "Trying simple merge with $pretty_name"
git read-tree -u -m --aggressive $common $MRT $SHA1 || exit 2
next=$(git write-tree 2>/dev/null)
if test $? -ne 0

View File

@@ -71,7 +71,7 @@ git send-email [options] <file | directory | rev-list options >
--suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all.
--[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.
--[no-]suppress-from * Send to self. Default off.
--[no-]chain-reply-to * Chain In-Reply-To: fields. Default on.
--[no-]chain-reply-to * Chain In-Reply-To: fields. Default off.
--[no-]thread * Use In-Reply-To: field. Default on.
Administering:
@@ -221,10 +221,10 @@ sub chain_reply_to {
if (defined $chain_reply_to &&
$chain_reply_to eq $not_set_by_user) {
print STDERR
"In git 1.7.0, the default will be changed to --no-chain-reply-to\n" .
"In git 1.7.0, the default has changed to --no-chain-reply-to\n" .
"Set sendemail.chainreplyto configuration variable to true if\n" .
"you want to keep --chain-reply-to as your default.\n";
$chain_reply_to = 1;
$chain_reply_to = 0;
}
return $chain_reply_to;
}

View File

@@ -7,7 +7,7 @@ USAGE="list [<options>]
or: $dashless drop [-q|--quiet] [<stash>]
or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
or: $dashless branch <branchname> [<stash>]
or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]]
or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
or: $dashless clear"
SUBDIRECTORY_OK=Yes

2
git_remote_helpers/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/build
/dist

View File

@@ -0,0 +1,35 @@
#
# Makefile for the git_remote_helpers python support modules
#
pysetupfile:=setup.py
# Shell quote (do not use $(call) to accommodate ancient setups);
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
ifndef PYTHON_PATH
PYTHON_PATH = /usr/bin/python
endif
ifndef prefix
prefix = $(HOME)
endif
ifndef V
QUIET = @
QUIETSETUP = --quiet
endif
PYLIBDIR=$(shell $(PYTHON_PATH) -c \
"import sys; \
print 'lib/python%i.%i/site-packages' % sys.version_info[:2]")
all: $(pysetupfile)
$(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build
install: $(pysetupfile)
$(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix)
instlibdir: $(pysetupfile)
@echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)"
clean:
$(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a
$(RM) *.pyo *.pyc

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
"""Support library package for git remote helpers.
Git remote helpers are helper commands that interfaces with a non-git
repository to provide automatic import of non-git history into a Git
repository.
This package provides the support library needed by these helpers..
The following modules are included:
- git.git - Interaction with Git repositories
- util - General utility functionality use by the other modules in
this package, and also used directly by the helpers.
"""

View File

View File

@@ -0,0 +1,678 @@
#!/usr/bin/env python
"""Functionality for interacting with Git repositories.
This module provides classes for interfacing with a Git repository.
"""
import os
import re
import time
from binascii import hexlify
from cStringIO import StringIO
import unittest
from git_remote_helpers.util import debug, error, die, start_command, run_command
def get_git_dir ():
"""Return the path to the GIT_DIR for this repo."""
args = ("git", "rev-parse", "--git-dir")
exit_code, output, errors = run_command(args)
if exit_code:
die("Failed to retrieve git dir")
assert not errors
return output.strip()
def parse_git_config ():
"""Return a dict containing the parsed version of 'git config -l'."""
exit_code, output, errors = run_command(("git", "config", "-z", "-l"))
if exit_code:
die("Failed to retrieve git configuration")
assert not errors
return dict([e.split('\n', 1) for e in output.split("\0") if e])
def git_config_bool (value):
"""Convert the given git config string value to True or False.
Raise ValueError if the given string was not recognized as a
boolean value.
"""
norm_value = str(value).strip().lower()
if norm_value in ("true", "1", "yes", "on", ""):
return True
if norm_value in ("false", "0", "no", "off", "none"):
return False
raise ValueError("Failed to parse '%s' into a boolean value" % (value))
def valid_git_ref (ref_name):
"""Return True iff the given ref name is a valid git ref name."""
# The following is a reimplementation of the git check-ref-format
# command. The rules were derived from the git check-ref-format(1)
# manual page. This code should be replaced by a call to
# check_ref_format() in the git library, when such is available.
if ref_name.endswith('/') or \
ref_name.startswith('.') or \
ref_name.count('/.') or \
ref_name.count('..') or \
ref_name.endswith('.lock'):
return False
for c in ref_name:
if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[":
return False
return True
class GitObjectFetcher(object):
"""Provide parsed access to 'git cat-file --batch'.
This provides a read-only interface to the Git object database.
"""
def __init__ (self):
"""Initiate a 'git cat-file --batch' session."""
self.queue = [] # List of object names to be submitted
self.in_transit = None # Object name currently in transit
# 'git cat-file --batch' produces binary output which is likely
# to be corrupted by the default "rU"-mode pipe opened by
# start_command. (Mode == "rU" does universal new-line
# conversion, which mangles carriage returns.) Therefore, we
# open an explicitly binary-safe pipe for transferring the
# output from 'git cat-file --batch'.
pipe_r_fd, pipe_w_fd = os.pipe()
pipe_r = os.fdopen(pipe_r_fd, "rb")
pipe_w = os.fdopen(pipe_w_fd, "wb")
self.proc = start_command(("git", "cat-file", "--batch"),
stdout = pipe_w)
self.f = pipe_r
def __del__ (self):
"""Verify completed communication with 'git cat-file --batch'."""
assert not self.queue
assert self.in_transit is None
self.proc.stdin.close()
assert self.proc.wait() == 0 # Zero exit code
assert self.f.read() == "" # No remaining output
def _submit_next_object (self):
"""Submit queue items to the 'git cat-file --batch' process.
If there are items in the queue, and there is currently no item
currently in 'transit', then pop the first item off the queue,
and submit it.
"""
if self.queue and self.in_transit is None:
self.in_transit = self.queue.pop(0)
print >> self.proc.stdin, self.in_transit[0]
def push (self, obj, callback):
"""Push the given object name onto the queue.
The given callback function will at some point in the future
be called exactly once with the following arguments:
- self - this GitObjectFetcher instance
- obj - the object name provided to push()
- sha1 - the SHA1 of the object, if 'None' obj is missing
- t - the type of the object (tag/commit/tree/blob)
- size - the size of the object in bytes
- data - the object contents
"""
self.queue.append((obj, callback))
self._submit_next_object() # (Re)start queue processing
def process_next_entry (self):
"""Read the next entry off the queue and invoke callback."""
obj, cb = self.in_transit
self.in_transit = None
header = self.f.readline()
if header == "%s missing\n" % (obj):
cb(self, obj, None, None, None, None)
return
sha1, t, size = header.split(" ")
assert len(sha1) == 40
assert t in ("tag", "commit", "tree", "blob")
assert size.endswith("\n")
size = int(size.strip())
data = self.f.read(size)
assert self.f.read(1) == "\n"
cb(self, obj, sha1, t, size, data)
self._submit_next_object()
def process (self):
"""Process the current queue until empty."""
while self.in_transit is not None:
self.process_next_entry()
# High-level convenience methods:
def get_sha1 (self, objspec):
"""Return the SHA1 of the object specified by 'objspec'.
Return None if 'objspec' does not specify an existing object.
"""
class _ObjHandler(object):
"""Helper class for getting the returned SHA1."""
def __init__ (self, parser):
self.parser = parser
self.sha1 = None
def __call__ (self, parser, obj, sha1, t, size, data):
# FIXME: Many unused arguments. Could this be cheaper?
assert parser == self.parser
self.sha1 = sha1
handler = _ObjHandler(self)
self.push(objspec, handler)
self.process()
return handler.sha1
def open_obj (self, objspec):
"""Return a file object wrapping the contents of a named object.
The caller is responsible for calling .close() on the returned
file object.
Raise KeyError if 'objspec' does not exist in the repo.
"""
class _ObjHandler(object):
"""Helper class for parsing the returned git object."""
def __init__ (self, parser):
"""Set up helper."""
self.parser = parser
self.contents = StringIO()
self.err = None
def __call__ (self, parser, obj, sha1, t, size, data):
"""Git object callback (see GitObjectFetcher documentation)."""
assert parser == self.parser
if not sha1: # Missing object
self.err = "Missing object '%s'" % obj
else:
assert size == len(data)
self.contents.write(data)
handler = _ObjHandler(self)
self.push(objspec, handler)
self.process()
if handler.err:
raise KeyError(handler.err)
handler.contents.seek(0)
return handler.contents
def walk_tree (self, tree_objspec, callback, prefix = ""):
"""Recursively walk the given Git tree object.
Recursively walk all subtrees of the given tree object, and
invoke the given callback passing three arguments:
(path, mode, data) with the path, permission bits, and contents
of all the blobs found in the entire tree structure.
"""
class _ObjHandler(object):
"""Helper class for walking a git tree structure."""
def __init__ (self, parser, cb, path, mode = None):
"""Set up helper."""
self.parser = parser
self.cb = cb
self.path = path
self.mode = mode
self.err = None
def parse_tree (self, treedata):
"""Parse tree object data, yield tree entries.
Each tree entry is a 3-tuple (mode, sha1, path)
self.path is prepended to all paths yielded
from this method.
"""
while treedata:
mode = int(treedata[:6], 10)
# Turn 100xxx into xxx
if mode > 100000:
mode -= 100000
assert treedata[6] == " "
i = treedata.find("\0", 7)
assert i > 0
path = treedata[7:i]
sha1 = hexlify(treedata[i + 1: i + 21])
yield (mode, sha1, self.path + path)
treedata = treedata[i + 21:]
def __call__ (self, parser, obj, sha1, t, size, data):
"""Git object callback (see GitObjectFetcher documentation)."""
assert parser == self.parser
if not sha1: # Missing object
self.err = "Missing object '%s'" % (obj)
return
assert size == len(data)
if t == "tree":
if self.path:
self.path += "/"
# Recurse into all blobs and subtrees
for m, s, p in self.parse_tree(data):
parser.push(s,
self.__class__(self.parser, self.cb, p, m))
elif t == "blob":
self.cb(self.path, self.mode, data)
else:
raise ValueError("Unknown object type '%s'" % (t))
self.push(tree_objspec, _ObjHandler(self, callback, prefix))
self.process()
class GitRefMap(object):
"""Map Git ref names to the Git object names they currently point to.
Behaves like a dictionary of Git ref names -> Git object names.
"""
def __init__ (self, obj_fetcher):
"""Create a new Git ref -> object map."""
self.obj_fetcher = obj_fetcher
self._cache = {} # dict: refname -> objname
def _load (self, ref):
"""Retrieve the object currently bound to the given ref.
The name of the object pointed to by the given ref is stored
into this mapping, and also returned.
"""
if ref not in self._cache:
self._cache[ref] = self.obj_fetcher.get_sha1(ref)
return self._cache[ref]
def __contains__ (self, refname):
"""Return True if the given refname is present in this cache."""
return bool(self._load(refname))
def __getitem__ (self, refname):
"""Return the git object name pointed to by the given refname."""
commit = self._load(refname)
if commit is None:
raise KeyError("Unknown ref '%s'" % (refname))
return commit
def get (self, refname, default = None):
"""Return the git object name pointed to by the given refname."""
commit = self._load(refname)
if commit is None:
return default
return commit
class GitFICommit(object):
"""Encapsulate the data in a Git fast-import commit command."""
SHA1RE = re.compile(r'^[0-9a-f]{40}$')
@classmethod
def parse_mode (cls, mode):
"""Verify the given git file mode, and return it as a string."""
assert mode in (644, 755, 100644, 100755, 120000)
return "%i" % (mode)
@classmethod
def parse_objname (cls, objname):
"""Return the given object name (or mark number) as a string."""
if isinstance(objname, int): # Object name is a mark number
assert objname > 0
return ":%i" % (objname)
# No existence check is done, only checks for valid format
assert cls.SHA1RE.match(objname) # Object name is valid SHA1
return objname
@classmethod
def quote_path (cls, path):
"""Return a quoted version of the given path."""
path = path.replace("\\", "\\\\")
path = path.replace("\n", "\\n")
path = path.replace('"', '\\"')
return '"%s"' % (path)
@classmethod
def parse_path (cls, path):
"""Verify that the given path is valid, and quote it, if needed."""
assert not isinstance(path, int) # Cannot be a mark number
# These checks verify the rules on the fast-import man page
assert not path.count("//")
assert not path.endswith("/")
assert not path.startswith("/")
assert not path.count("/./")
assert not path.count("/../")
assert not path.endswith("/.")
assert not path.endswith("/..")
assert not path.startswith("./")
assert not path.startswith("../")
if path.count('"') + path.count('\n') + path.count('\\'):
return cls.quote_path(path)
return path
def __init__ (self, name, email, timestamp, timezone, message):
"""Create a new Git fast-import commit, with the given metadata."""
self.name = name
self.email = email
self.timestamp = timestamp
self.timezone = timezone
self.message = message
self.pathops = [] # List of path operations in this commit
def modify (self, mode, blobname, path):
"""Add a file modification to this Git fast-import commit."""
self.pathops.append(("M",
self.parse_mode(mode),
self.parse_objname(blobname),
self.parse_path(path)))
def delete (self, path):
"""Add a file deletion to this Git fast-import commit."""
self.pathops.append(("D", self.parse_path(path)))
def copy (self, path, newpath):
"""Add a file copy to this Git fast-import commit."""
self.pathops.append(("C",
self.parse_path(path),
self.parse_path(newpath)))
def rename (self, path, newpath):
"""Add a file rename to this Git fast-import commit."""
self.pathops.append(("R",
self.parse_path(path),
self.parse_path(newpath)))
def note (self, blobname, commit):
"""Add a note object to this Git fast-import commit."""
self.pathops.append(("N",
self.parse_objname(blobname),
self.parse_objname(commit)))
def deleteall (self):
"""Delete all files in this Git fast-import commit."""
self.pathops.append("deleteall")
class TestGitFICommit(unittest.TestCase):
"""GitFICommit selftests."""
def test_basic (self):
"""GitFICommit basic selftests."""
def expect_fail (method, data):
"""Verify that the method(data) raises an AssertionError."""
try:
method(data)
except AssertionError:
return
raise AssertionError("Failed test for invalid data '%s(%s)'" %
(method.__name__, repr(data)))
def test_parse_mode (self):
"""GitFICommit.parse_mode() selftests."""
self.assertEqual(GitFICommit.parse_mode(644), "644")
self.assertEqual(GitFICommit.parse_mode(755), "755")
self.assertEqual(GitFICommit.parse_mode(100644), "100644")
self.assertEqual(GitFICommit.parse_mode(100755), "100755")
self.assertEqual(GitFICommit.parse_mode(120000), "120000")
self.assertRaises(AssertionError, GitFICommit.parse_mode, 0)
self.assertRaises(AssertionError, GitFICommit.parse_mode, 123)
self.assertRaises(AssertionError, GitFICommit.parse_mode, 600)
self.assertRaises(AssertionError, GitFICommit.parse_mode, "644")
self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc")
def test_parse_objname (self):
"""GitFICommit.parse_objname() selftests."""
self.assertEqual(GitFICommit.parse_objname(1), ":1")
self.assertRaises(AssertionError, GitFICommit.parse_objname, 0)
self.assertRaises(AssertionError, GitFICommit.parse_objname, -1)
self.assertEqual(GitFICommit.parse_objname("0123456789" * 4),
"0123456789" * 4)
self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4),
"2468abcdef" * 4)
self.assertRaises(AssertionError, GitFICommit.parse_objname,
"abcdefghij" * 4)
def test_parse_path (self):
"""GitFICommit.parse_path() selftests."""
self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar")
self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"),
'"path/with\\n and \\" in it"')
self.assertRaises(AssertionError, GitFICommit.parse_path, 1)
self.assertRaises(AssertionError, GitFICommit.parse_path, 0)
self.assertRaises(AssertionError, GitFICommit.parse_path, -1)
self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar")
self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/")
self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar")
self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar")
self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar")
self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.")
self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..")
self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar")
self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar")
class GitFastImport(object):
"""Encapsulate communication with git fast-import."""
def __init__ (self, f, obj_fetcher, last_mark = 0):
"""Set up self to communicate with a fast-import process through f."""
self.f = f # File object where fast-import stream is written
self.obj_fetcher = obj_fetcher # GitObjectFetcher instance
self.next_mark = last_mark + 1 # Next mark number
self.refs = set() # Keep track of the refnames we've seen
def comment (self, s):
"""Write the given comment in the fast-import stream."""
assert "\n" not in s, "Malformed comment: '%s'" % (s)
self.f.write("# %s\n" % (s))
def commit (self, ref, commitdata):
"""Make a commit on the given ref, with the given GitFICommit.
Return the mark number identifying this commit.
"""
self.f.write("""\
commit %(ref)s
mark :%(mark)i
committer %(name)s <%(email)s> %(timestamp)i %(timezone)s
data %(msgLength)i
%(msg)s
""" % {
'ref': ref,
'mark': self.next_mark,
'name': commitdata.name,
'email': commitdata.email,
'timestamp': commitdata.timestamp,
'timezone': commitdata.timezone,
'msgLength': len(commitdata.message),
'msg': commitdata.message,
})
if ref not in self.refs:
self.refs.add(ref)
parent = ref + "^0"
if self.obj_fetcher.get_sha1(parent):
self.f.write("from %s\n" % (parent))
for op in commitdata.pathops:
self.f.write(" ".join(op))
self.f.write("\n")
self.f.write("\n")
retval = self.next_mark
self.next_mark += 1
return retval
def blob (self, data):
"""Import the given blob.
Return the mark number identifying this blob.
"""
self.f.write("blob\nmark :%i\ndata %i\n%s\n" %
(self.next_mark, len(data), data))
retval = self.next_mark
self.next_mark += 1
return retval
def reset (self, ref, objname):
"""Reset the given ref to point at the given Git object."""
self.f.write("reset %s\nfrom %s\n\n" %
(ref, GitFICommit.parse_objname(objname)))
if ref not in self.refs:
self.refs.add(ref)
class GitNotes(object):
"""Encapsulate access to Git notes.
Simulates a dictionary of object name (SHA1) -> Git note mappings.
"""
def __init__ (self, notes_ref, obj_fetcher):
"""Create a new Git notes interface, bound to the given notes ref."""
self.notes_ref = notes_ref
self.obj_fetcher = obj_fetcher # Used to get objects from repo
self.imports = [] # list: (objname, note data blob name) tuples
def __del__ (self):
"""Verify that self.commit_notes() was called before destruction."""
if self.imports:
error("Missing call to self.commit_notes().")
error("%i notes are not committed!", len(self.imports))
def _load (self, objname):
"""Return the note data associated with the given git object.
The note data is returned in string form. If no note is found
for the given object, None is returned.
"""
try:
f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname))
ret = f.read()
f.close()
except KeyError:
ret = None
return ret
def __getitem__ (self, objname):
"""Return the note contents associated with the given object.
Raise KeyError if given object has no associated note.
"""
blobdata = self._load(objname)
if blobdata is None:
raise KeyError("Object '%s' has no note" % (objname))
return blobdata
def get (self, objname, default = None):
"""Return the note contents associated with the given object.
Return given default if given object has no associated note.
"""
blobdata = self._load(objname)
if blobdata is None:
return default
return blobdata
def import_note (self, objname, data, gfi):
"""Tell git fast-import to store data as a note for objname.
This method uses the given GitFastImport object to create a
blob containing the given note data. Also an entry mapping the
given object name to the created blob is stored until
commit_notes() is called.
Note that this method only works if it is later followed by a
call to self.commit_notes() (which produces the note commit
that refers to the blob produced here).
"""
if not data.endswith("\n"):
data += "\n"
gfi.comment("Importing note for object %s" % (objname))
mark = gfi.blob(data)
self.imports.append((objname, mark))
def commit_notes (self, gfi, author, message):
"""Produce a git fast-import note commit for the imported notes.
This method uses the given GitFastImport object to create a
commit on the notes ref, introducing the notes previously
submitted to import_note().
"""
if not self.imports:
return
commitdata = GitFICommit(author[0], author[1],
time.time(), "0000", message)
for objname, blobname in self.imports:
assert isinstance(objname, int) and objname > 0
assert isinstance(blobname, int) and blobname > 0
commitdata.note(blobname, objname)
gfi.commit(self.notes_ref, commitdata)
self.imports = []
class GitCachedNotes(GitNotes):
"""Encapsulate access to Git notes (cached version).
Only use this class if no caching is done at a higher level.
Simulates a dictionary of object name (SHA1) -> Git note mappings.
"""
def __init__ (self, notes_ref, obj_fetcher):
"""Set up a caching wrapper around GitNotes."""
GitNotes.__init__(self, notes_ref, obj_fetcher)
self._cache = {} # Cache: object name -> note data
def __del__ (self):
"""Verify that GitNotes' destructor is called."""
GitNotes.__del__(self)
def _load (self, objname):
"""Extend GitNotes._load() with a local objname -> note cache."""
if objname not in self._cache:
self._cache[objname] = GitNotes._load(self, objname)
return self._cache[objname]
def import_note (self, objname, data, gfi):
"""Extend GitNotes.import_note() with a local objname -> note cache."""
if not data.endswith("\n"):
data += "\n"
assert objname not in self._cache
self._cache[objname] = data
GitNotes.import_note(self, objname, data, gfi)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python
"""Distutils build/install script for the git_remote_helpers package."""
from distutils.core import setup
setup(
name = 'git_remote_helpers',
version = '0.1.0',
description = 'Git remote helper program for non-git repositories',
license = 'GPLv2',
author = 'The Git Community',
author_email = 'git@vger.kernel.org',
url = 'http://www.git-scm.com/',
package_dir = {'git_remote_helpers': ''},
packages = ['git_remote_helpers', 'git_remote_helpers.git'],
)

194
git_remote_helpers/util.py Normal file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/env python
"""Misc. useful functionality used by the rest of this package.
This module provides common functionality used by the other modules in
this package.
"""
import sys
import os
import subprocess
# Whether or not to show debug messages
DEBUG = False
def notify(msg, *args):
"""Print a message to stderr."""
print >> sys.stderr, msg % args
def debug (msg, *args):
"""Print a debug message to stderr when DEBUG is enabled."""
if DEBUG:
print >> sys.stderr, msg % args
def error (msg, *args):
"""Print an error message to stderr."""
print >> sys.stderr, "ERROR:", msg % args
def warn(msg, *args):
"""Print a warning message to stderr."""
print >> sys.stderr, "warning:", msg % args
def die (msg, *args):
"""Print as error message to stderr and exit the program."""
error(msg, *args)
sys.exit(1)
class ProgressIndicator(object):
"""Simple progress indicator.
Displayed as a spinning character by default, but can be customized
by passing custom messages that overrides the spinning character.
"""
States = ("|", "/", "-", "\\")
def __init__ (self, prefix = "", f = sys.stdout):
"""Create a new ProgressIndicator, bound to the given file object."""
self.n = 0 # Simple progress counter
self.f = f # Progress is written to this file object
self.prev_len = 0 # Length of previous msg (to be overwritten)
self.prefix = prefix # Prefix prepended to each progress message
self.prefix_lens = [] # Stack of prefix string lengths
def pushprefix (self, prefix):
"""Append the given prefix onto the prefix stack."""
self.prefix_lens.append(len(self.prefix))
self.prefix += prefix
def popprefix (self):
"""Remove the last prefix from the prefix stack."""
prev_len = self.prefix_lens.pop()
self.prefix = self.prefix[:prev_len]
def __call__ (self, msg = None, lf = False):
"""Indicate progress, possibly with a custom message."""
if msg is None:
msg = self.States[self.n % len(self.States)]
msg = self.prefix + msg
print >> self.f, "\r%-*s" % (self.prev_len, msg),
self.prev_len = len(msg.expandtabs())
if lf:
print >> self.f
self.prev_len = 0
self.n += 1
def finish (self, msg = "done", noprefix = False):
"""Finalize progress indication with the given message."""
if noprefix:
self.prefix = ""
self(msg, True)
def start_command (args, cwd = None, shell = False, add_env = None,
stdin = subprocess.PIPE, stdout = subprocess.PIPE,
stderr = subprocess.PIPE):
"""Start the given command, and return a subprocess object.
This provides a simpler interface to the subprocess module.
"""
env = None
if add_env is not None:
env = os.environ.copy()
env.update(add_env)
return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout,
stderr = stderr, cwd = cwd, shell = shell,
env = env, universal_newlines = True)
def run_command (args, cwd = None, shell = False, add_env = None,
flag_error = True):
"""Run the given command to completion, and return its results.
This provides a simpler interface to the subprocess module.
The results are formatted as a 3-tuple: (exit_code, output, errors)
If flag_error is enabled, Error messages will be produced if the
subprocess terminated with a non-zero exit code and/or stderr
output.
The other arguments are passed on to start_command().
"""
process = start_command(args, cwd, shell, add_env)
(output, errors) = process.communicate()
exit_code = process.returncode
if flag_error and errors:
error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
if flag_error and exit_code:
error("'%s' returned exit code %i", " ".join(args), exit_code)
return (exit_code, output, errors)
def file_reader_method (missing_ok = False):
"""Decorator for simplifying reading of files.
If missing_ok is True, a failure to open a file for reading will
not raise the usual IOError, but instead the wrapped method will be
called with f == None. The method must in this case properly
handle f == None.
"""
def _wrap (method):
"""Teach given method to handle both filenames and file objects.
The given method must take a file object as its second argument
(the first argument being 'self', of course). This decorator
will take a filename given as the second argument and promote
it to a file object.
"""
def _wrapped_method (self, filename, *args, **kwargs):
if isinstance(filename, file):
f = filename
else:
try:
f = open(filename, 'r')
except IOError:
if missing_ok:
f = None
else:
raise
try:
return method(self, f, *args, **kwargs)
finally:
if not isinstance(filename, file) and f:
f.close()
return _wrapped_method
return _wrap
def file_writer_method (method):
"""Decorator for simplifying writing of files.
Enables the given method to handle both filenames and file objects.
The given method must take a file object as its second argument
(the first argument being 'self', of course). This decorator will
take a filename given as the second argument and promote it to a
file object.
"""
def _new_method (self, filename, *args, **kwargs):
if isinstance(filename, file):
f = filename
else:
# Make sure the containing directory exists
parent_dir = os.path.dirname(filename)
if not os.path.isdir(parent_dir):
os.makedirs(parent_dir)
f = open(filename, 'w')
try:
return method(self, f, *args, **kwargs)
finally:
if not isinstance(filename, file):
f.close()
return _new_method

28
http.c
View File

@@ -7,6 +7,12 @@ int active_requests;
int http_is_verbose;
size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
#if LIBCURL_VERSION_NUM >= 0x070a06
#define LIBCURL_CAN_HANDLE_AUTH_ANY
#endif
static int min_curl_sessions = 1;
static int curl_session_count;
#ifdef USE_CURL_MULTI
static int max_requests = -1;
static CURLM *curlm;
@@ -152,6 +158,14 @@ static int http_options(const char *var, const char *value, void *cb)
ssl_cert_password_required = 1;
return 0;
}
if (!strcmp("http.minsessions", var)) {
min_curl_sessions = git_config_int(var, value);
#ifndef USE_CURL_MULTI
if (min_curl_sessions > 1)
min_curl_sessions = 1;
#endif
return 0;
}
#ifdef USE_CURL_MULTI
if (!strcmp("http.maxrequests", var)) {
max_requests = git_config_int(var, value);
@@ -230,6 +244,9 @@ static CURL *get_curl_handle(void)
#if LIBCURL_VERSION_NUM >= 0x070907
curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
#endif
#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
#endif
init_curl_http_auth(result);
@@ -372,6 +389,7 @@ void http_init(struct remote *remote)
if (curl_ssl_verify == -1)
curl_ssl_verify = 1;
curl_session_count = 0;
#ifdef USE_CURL_MULTI
if (max_requests < 1)
max_requests = DEFAULT_MAX_REQUESTS;
@@ -480,6 +498,7 @@ struct active_request_slot *get_active_slot(void)
#else
slot->curl = curl_easy_duphandle(curl_default);
#endif
curl_session_count++;
}
active_requests++;
@@ -558,9 +577,11 @@ void fill_active_slots(void)
}
while (slot != NULL) {
if (!slot->in_use && slot->curl != NULL) {
if (!slot->in_use && slot->curl != NULL
&& curl_session_count > min_curl_sessions) {
curl_easy_cleanup(slot->curl);
slot->curl = NULL;
curl_session_count--;
}
slot = slot->next;
}
@@ -633,12 +654,13 @@ static void closedown_active_slot(struct active_request_slot *slot)
void release_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
if (slot->curl) {
if (slot->curl && curl_session_count > min_curl_sessions) {
#ifdef USE_CURL_MULTI
curl_multi_remove_handle(curlm, slot->curl);
#endif
curl_easy_cleanup(slot->curl);
slot->curl = NULL;
curl_session_count--;
}
#ifdef USE_CURL_MULTI
fill_active_slots();
@@ -1244,7 +1266,7 @@ int finish_http_object_request(struct http_object_request *freq)
process_http_object_request(freq);
if (freq->http_code == 416) {
fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
warning("requested range invalid; we may already have all the data.");
} else if (freq->curl_result != CURLE_OK) {
if (stat(freq->tmpfile, &st) == 0)
if (st.st_size == 0)

View File

@@ -1322,7 +1322,7 @@ int read_index_from(struct index_state *istate, const char *path)
* extension name (4-byte) and section length
* in 4-byte network byte order.
*/
unsigned long extsize;
uint32_t extsize;
memcpy(&extsize, (char *)mmap + src_offset + 4, 4);
extsize = ntohl(extsize);
if (read_index_extension(istate,

View File

@@ -290,6 +290,7 @@ struct rpc_state {
int out;
struct strbuf result;
unsigned gzip_request : 1;
unsigned initial_buffer : 1;
};
static size_t rpc_out(void *ptr, size_t eltsize,
@@ -300,6 +301,7 @@ static size_t rpc_out(void *ptr, size_t eltsize,
size_t avail = rpc->len - rpc->pos;
if (!avail) {
rpc->initial_buffer = 0;
avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
if (!avail)
return 0;
@@ -314,6 +316,29 @@ static size_t rpc_out(void *ptr, size_t eltsize,
return avail;
}
#ifndef NO_CURL_IOCTL
curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
{
struct rpc_state *rpc = clientp;
switch (cmd) {
case CURLIOCMD_NOP:
return CURLIOE_OK;
case CURLIOCMD_RESTARTREAD:
if (rpc->initial_buffer) {
rpc->pos = 0;
return CURLIOE_OK;
}
fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n");
return CURLIOE_FAILRESTART;
default:
return CURLIOE_UNKNOWNCMD;
}
}
#endif
static size_t rpc_in(const void *ptr, size_t eltsize,
size_t nmemb, void *buffer_)
{
@@ -370,8 +395,13 @@ static int post_rpc(struct rpc_state *rpc)
*/
headers = curl_slist_append(headers, "Expect: 100-continue");
headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
rpc->initial_buffer = 1;
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
#ifndef NO_CURL_IOCTL
curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
#endif
if (options.verbosity > 1) {
fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
fflush(stderr);

View File

@@ -53,6 +53,11 @@ static struct rewrites rewrites_push;
#define BUF_SIZE (2048)
static char buffer[BUF_SIZE];
static int valid_remote(const struct remote *remote)
{
return (!!remote->url) || (!!remote->foreign_vcs);
}
static const char *alias_url(const char *url, struct rewrites *r)
{
int i, j;
@@ -441,6 +446,8 @@ static int handle_config(const char *key, const char *value, void *cb)
} else if (!strcmp(subkey, ".proxy")) {
return git_config_string((const char **)&remote->http_proxy,
key, value);
} else if (!strcmp(subkey, ".vcs")) {
return git_config_string(&remote->foreign_vcs, key, value);
}
return 0;
}
@@ -668,6 +675,16 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
return parse_refspec_internal(nr_refspec, refspec, 0, 0);
}
void free_refspec(int nr_refspec, struct refspec *refspec)
{
int i;
for (i = 0; i < nr_refspec; i++) {
free(refspec[i].src);
free(refspec[i].dst);
}
free(refspec);
}
static int valid_remote_nick(const char *name)
{
if (!name[0] || is_dot_or_dotdot(name))
@@ -690,14 +707,14 @@ struct remote *remote_get(const char *name)
ret = make_remote(name, 0);
if (valid_remote_nick(name)) {
if (!ret->url)
if (!valid_remote(ret))
read_remotes_file(ret);
if (!ret->url)
if (!valid_remote(ret))
read_branches_file(ret);
}
if (name_given && !ret->url)
if (name_given && !valid_remote(ret))
add_url_alias(ret, name);
if (!ret->url)
if (!valid_remote(ret))
return NULL;
ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
@@ -810,6 +827,23 @@ static int match_name_with_pattern(const char *key, const char *name,
return ret;
}
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
const char *name)
{
int i;
char *ret = NULL;
for (i = 0; i < nr_refspec; i++) {
struct refspec *refspec = refspecs + i;
if (refspec->pattern) {
if (match_name_with_pattern(refspec->src, name,
refspec->dst, &ret))
return ret;
} else if (!strcmp(refspec->src, name))
return strdup(refspec->dst);
}
return NULL;
}
int remote_find_tracking(struct remote *remote, struct refspec *refspec)
{
int find_src = refspec->src == NULL;

View File

@@ -11,6 +11,8 @@ struct remote {
const char *name;
int origin;
const char *foreign_vcs;
const char **url;
int url_nr;
int url_alloc;
@@ -89,6 +91,11 @@ void ref_remove_duplicates(struct ref *ref_map);
int valid_fetch_refspec(const char *refspec);
struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
void free_refspec(int nr_refspec, struct refspec *refspec);
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
const char *name);
int match_refs(struct ref *src, struct ref **dst,
int nr_refspec, const char **refspec, int all);

View File

@@ -791,7 +791,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->ignore_merges = 1;
revs->simplify_history = 1;
DIFF_OPT_SET(&revs->pruning, RECURSIVE);
DIFF_OPT_SET(&revs->pruning, QUIET);
DIFF_OPT_SET(&revs->pruning, QUICK);
revs->pruning.add_remove = file_add_remove;
revs->pruning.change = file_change;
revs->lifo = 1;

15
setup.c
View File

@@ -77,6 +77,18 @@ int check_filename(const char *prefix, const char *arg)
die_errno("failed to stat '%s'", arg);
}
static void NORETURN die_verify_filename(const char *prefix, const char *arg)
{
unsigned char sha1[20];
unsigned mode;
/* try a detailed diagnostic ... */
get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
/* ... or fall back the most general message. */
die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
"Use '--' to separate paths from revisions", arg);
}
/*
* Verify a filename that we got as an argument for a pathspec
* entry. Note that a filename that begins with "-" never verifies
@@ -90,8 +102,7 @@ void verify_filename(const char *prefix, const char *arg)
die("bad flag '%s' used after filename", arg);
if (check_filename(prefix, arg))
return;
die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
"Use '--' to separate paths from revisions", arg);
die_verify_filename(prefix, arg);
}
/*

View File

@@ -804,7 +804,96 @@ int get_sha1(const char *name, unsigned char *sha1)
return get_sha1_with_mode(name, sha1, &unused);
}
int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
/* Must be called only when object_name:filename doesn't exist. */
static void diagnose_invalid_sha1_path(const char *prefix,
const char *filename,
const unsigned char *tree_sha1,
const char *object_name)
{
struct stat st;
unsigned char sha1[20];
unsigned mode;
if (!prefix)
prefix = "";
if (!lstat(filename, &st))
die("Path '%s' exists on disk, but not in '%s'.",
filename, object_name);
if (errno == ENOENT || errno == ENOTDIR) {
char *fullname = xmalloc(strlen(filename)
+ strlen(prefix) + 1);
strcpy(fullname, prefix);
strcat(fullname, filename);
if (!get_tree_entry(tree_sha1, fullname,
sha1, &mode)) {
die("Path '%s' exists, but not '%s'.\n"
"Did you mean '%s:%s'?",
fullname,
filename,
object_name,
fullname);
}
die("Path '%s' does not exist in '%s'",
filename, object_name);
}
}
/* Must be called only when :stage:filename doesn't exist. */
static void diagnose_invalid_index_path(int stage,
const char *prefix,
const char *filename)
{
struct stat st;
struct cache_entry *ce;
int pos;
unsigned namelen = strlen(filename);
unsigned fullnamelen;
char *fullname;
if (!prefix)
prefix = "";
/* Wrong stage number? */
pos = cache_name_pos(filename, namelen);
if (pos < 0)
pos = -pos - 1;
ce = active_cache[pos];
if (ce_namelen(ce) == namelen &&
!memcmp(ce->name, filename, namelen))
die("Path '%s' is in the index, but not at stage %d.\n"
"Did you mean ':%d:%s'?",
filename, stage,
ce_stage(ce), filename);
/* Confusion between relative and absolute filenames? */
fullnamelen = namelen + strlen(prefix);
fullname = xmalloc(fullnamelen + 1);
strcpy(fullname, prefix);
strcat(fullname, filename);
pos = cache_name_pos(fullname, fullnamelen);
if (pos < 0)
pos = -pos - 1;
ce = active_cache[pos];
if (ce_namelen(ce) == fullnamelen &&
!memcmp(ce->name, fullname, fullnamelen))
die("Path '%s' is in the index, but not '%s'.\n"
"Did you mean ':%d:%s'?",
fullname, filename,
ce_stage(ce), fullname);
if (!lstat(filename, &st))
die("Path '%s' exists on disk, but not in the index.", filename);
if (errno == ENOENT || errno == ENOTDIR)
die("Path '%s' does not exist (neither on disk nor in the index).",
filename);
free(fullname);
}
int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
{
int ret, bracket_depth;
int namelen = strlen(name);
@@ -850,6 +939,8 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
}
pos++;
}
if (!gently)
diagnose_invalid_index_path(stage, prefix, cp);
return -1;
}
for (cp = name, bracket_depth = 0; *cp; cp++) {
@@ -862,9 +953,25 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
}
if (*cp == ':') {
unsigned char tree_sha1[20];
if (!get_sha1_1(name, cp-name, tree_sha1))
return get_tree_entry(tree_sha1, cp+1, sha1,
mode);
char *object_name = NULL;
if (!gently) {
object_name = xmalloc(cp-name+1);
strncpy(object_name, name, cp-name);
object_name[cp-name] = '\0';
}
if (!get_sha1_1(name, cp-name, tree_sha1)) {
const char *filename = cp+1;
ret = get_tree_entry(tree_sha1, filename, sha1, mode);
if (!gently) {
diagnose_invalid_sha1_path(prefix, filename,
tree_sha1, object_name);
free(object_name);
}
return ret;
} else {
if (!gently)
die("Invalid object name '%s'.", object_name);
}
}
return ret;
}

View File

@@ -1,5 +1,5 @@
#ifndef PATH_LIST_H
#define PATH_LIST_H
#ifndef STRING_LIST_H
#define STRING_LIST_H
struct string_list_item {
char *string;
@@ -39,4 +39,4 @@ struct string_list_item *string_list_append(const char *string, struct string_li
void sort_string_list(struct string_list *list);
int unsorted_string_list_has_string(struct string_list *list, const char *string);
#endif /* PATH_LIST_H */
#endif /* STRING_LIST_H */

View File

@@ -75,6 +75,15 @@ appropriately before running "make".
As the names depend on the tests' file names, it is safe to
run the tests with this option in parallel.
--with-dashes::
By default tests are run without dashed forms of
commands (like git-commit) in the PATH (it only uses
wrappers from ../bin-wrappers). Use this option to include
the build directory (..) in the PATH, which contains all
the dashed forms of commands. This option is currently
implied by other options like --valgrind and
GIT_TEST_INSTALLED.
You can also set the GIT_TEST_INSTALLED environment variable to
the bindir of an existing git installation to test that installation.
You still need to have built this git sandbox, from which various

View File

@@ -259,7 +259,7 @@ test_expect_success 'git repack' 'git repack'
test_expect_success 'git prune-packed' 'git prune-packed'
test_expect_success '-> only packed objects' '
git prune && # Remove conflict marked blobs
! find .git/objects/[0-9a-f][0-9a-f] -type f
test $(find .git/objects/[0-9a-f][0-9a-f] -type f -print 2>/dev/null | wc -l) = 0
'
test_done

View File

@@ -683,6 +683,34 @@ test_expect_success 'set --bool-or-int' '
rm .git/config
cat >expect <<\EOF
[path]
home = ~/
normal = /dev/null
trailingtilde = foo~
EOF
test_expect_success 'set --path' '
git config --path path.home "~/" &&
git config --path path.normal "/dev/null" &&
git config --path path.trailingtilde "foo~" &&
test_cmp expect .git/config'
cat >expect <<EOF
$HOME/
/dev/null
foo~
EOF
test_expect_success 'get --path' '
git config --get --path path.home > result &&
git config --get --path path.normal >> result &&
git config --get --path path.trailingtilde >> result &&
test_cmp expect result
'
rm .git/config
git config quote.leading " test"
git config quote.ending "test "
git config quote.semicolon "test;test"

69
t/t1506-rev-parse-diagnosis.sh Executable file
View File

@@ -0,0 +1,69 @@
#!/bin/sh
test_description='test git rev-parse diagnosis for invalid argument'
exec </dev/null
. ./test-lib.sh
HASH_file=
test_expect_success 'set up basic repo' '
echo one > file.txt &&
mkdir subdir &&
echo two > subdir/file.txt &&
echo three > subdir/file2.txt &&
git add . &&
git commit -m init &&
echo four > index-only.txt &&
git add index-only.txt &&
echo five > disk-only.txt
'
test_expect_success 'correct file objects' '
HASH_file=$(git rev-parse HEAD:file.txt) &&
git rev-parse HEAD:subdir/file.txt &&
git rev-parse :index-only.txt &&
(cd subdir &&
git rev-parse HEAD:subdir/file2.txt &&
test $HASH_file = $(git rev-parse HEAD:file.txt) &&
test $HASH_file = $(git rev-parse :file.txt) &&
test $HASH_file = $(git rev-parse :0:file.txt) )
'
test_expect_success 'incorrect revision id' '
test_must_fail git rev-parse foobar:file.txt 2>error &&
grep "Invalid object name '"'"'foobar'"'"'." error &&
test_must_fail git rev-parse foobar 2> error &&
grep "unknown revision or path not in the working tree." error
'
test_expect_success 'incorrect file in sha1:path' '
test_must_fail git rev-parse HEAD:nothing.txt 2> error &&
grep "fatal: Path '"'"'nothing.txt'"'"' does not exist in '"'"'HEAD'"'"'" error &&
test_must_fail git rev-parse HEAD:index-only.txt 2> error &&
grep "fatal: Path '"'"'index-only.txt'"'"' exists on disk, but not in '"'"'HEAD'"'"'." error &&
(cd subdir &&
test_must_fail git rev-parse HEAD:file2.txt 2> error &&
grep "Did you mean '"'"'HEAD:subdir/file2.txt'"'"'?" error )
'
test_expect_success 'incorrect file in :path and :N:path' '
test_must_fail git rev-parse :nothing.txt 2> error &&
grep "fatal: Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
test_must_fail git rev-parse :1:nothing.txt 2> error &&
grep "Path '"'"'nothing.txt'"'"' does not exist (neither on disk nor in the index)." error &&
test_must_fail git rev-parse :1:file.txt 2> error &&
grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
(cd subdir &&
test_must_fail git rev-parse :1:file.txt 2> error &&
grep "Did you mean '"'"':0:file.txt'"'"'?" error &&
test_must_fail git rev-parse :file2.txt 2> error &&
grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error &&
test_must_fail git rev-parse :2:file2.txt 2> error &&
grep "Did you mean '"'"':0:subdir/file2.txt'"'"'?" error) &&
test_must_fail git rev-parse :disk-only.txt 2> error &&
grep "fatal: Path '"'"'disk-only.txt'"'"' exists on disk, but not in the index." error
'
test_done

View File

@@ -93,8 +93,6 @@ git diff > out
test_expect_success 'another test, without options' 'test_cmp expect out'
cat << EOF > expect
diff --git a/x b/x
index d99af23..8b32fb5 100644
EOF
git diff -w > out
test_expect_success 'another test, with -w' 'test_cmp expect out'
@@ -386,6 +384,18 @@ test_expect_success 'checkdiff allows new blank lines' '
git diff --check
'
cat <<EOF >expect
EOF
test_expect_success 'whitespace-only changes not reported' '
git reset --hard &&
echo >x "hello world" &&
git add x &&
git commit -m "hello 1" &&
echo >x "hello world" &&
git diff -b >actual &&
test_cmp expect actual
'
test_expect_success 'combined diff with autocrlf conversion' '
git reset --hard &&

View File

@@ -20,11 +20,27 @@ test_expect_success setup '
blue_grep='7;34m' ;# ESC [ 7 ; 3 4 m
printf "\033[%s" "$blue_grep" >check-grep
if (grep "$blue_grep" <check-grep | grep "$blue_grep") >/dev/null 2>&1
then
grep_a=grep
elif (grep -a "$blue_grep" <check-grep | grep -a "$blue_grep") >/dev/null 2>&1
then
grep_a='grep -a'
else
grep_a=grep ;# expected to fail...
fi
rm -f check-grep
prepare_output () {
git diff --color >output
$grep_a "$blue_grep" output >error
$grep_a -v "$blue_grep" output >normal
}
test_expect_success default '
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -37,9 +53,7 @@ test_expect_success default '
test_expect_success 'without -trail' '
git config core.whitespace -trail
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -53,9 +67,7 @@ test_expect_success 'without -trail (attribute)' '
git config --unset core.whitespace
echo "F whitespace=-trail" >.gitattributes
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -69,9 +81,7 @@ test_expect_success 'without -space' '
rm -f .gitattributes
git config core.whitespace -space
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
@@ -85,9 +95,7 @@ test_expect_success 'without -space (attribute)' '
git config --unset core.whitespace
echo "F whitespace=-space" >.gitattributes
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
@@ -101,9 +109,7 @@ test_expect_success 'with indent-non-tab only' '
rm -f .gitattributes
git config core.whitespace indent,-trailing,-space
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
@@ -117,9 +123,7 @@ test_expect_success 'with indent-non-tab only (attribute)' '
git config --unset core.whitespace
echo "F whitespace=indent,-trailing,-space" >.gitattributes
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
@@ -133,9 +137,7 @@ test_expect_success 'with cr-at-eol' '
rm -f .gitattributes
git config core.whitespace cr-at-eol
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -149,9 +151,7 @@ test_expect_success 'with cr-at-eol (attribute)' '
git config --unset core.whitespace
echo "F whitespace=trailing,cr-at-eol" >.gitattributes
git diff --color >output
grep "$blue_grep" output >error
grep -v "$blue_grep" output >normal
prepare_output
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
@@ -195,7 +195,7 @@ test_expect_success 'color new trailing blank lines' '
git add x &&
{ echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x &&
git diff --color x >output &&
cnt=$(grep "${blue_grep}" output | wc -l) &&
cnt=$($grep_a "${blue_grep}" output | wc -l) &&
test $cnt = 2
'

View File

@@ -12,19 +12,9 @@ test_expect_success setup '
'
decrypt_color () {
sed \
-e 's/.\[1m/<WHITE>/g' \
-e 's/.\[31m/<RED>/g' \
-e 's/.\[32m/<GREEN>/g' \
-e 's/.\[35m/<MAGENTA>/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_decode_color <output >output.decrypted &&
test_cmp expect output.decrypted
}
@@ -49,7 +39,7 @@ cat > expect <<\EOF
<WHITE>index 330b04f..5ed8eff 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
<CYAN>@@ -1,3 +1,7 @@<RESET>
<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
a = b + c<RESET>
@@ -70,9 +60,9 @@ cat > expect <<\EOF
<WHITE>index 330b04f..5ed8eff 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1 +1 @@<RESET>
<CYAN>@@ -1 +1 @@<RESET>
<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
<BROWN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
<GREEN>aa = a<RESET>
@@ -90,7 +80,7 @@ cat > expect <<\EOF
<WHITE>index 330b04f..5ed8eff 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
<CYAN>@@ -1,3 +1,7 @@<RESET>
h(4),<GREEN>hh<RESET>[44]
a = b + c<RESET>
@@ -126,7 +116,7 @@ cat > expect <<\EOF
<WHITE>index 330b04f..5ed8eff 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
<CYAN>@@ -1,3 +1,7 @@<RESET>
h(4)<GREEN>,hh[44]<RESET>
a = b + c<RESET>
@@ -168,7 +158,7 @@ cat > expect <<\EOF
<WHITE>index 330b04f..5ed8eff 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
<CYAN>@@ -1,3 +1,7 @@<RESET>
h(4),<GREEN>hh[44<RESET>]
a = b + c<RESET>
@@ -190,7 +180,7 @@ cat > expect <<\EOF
<WHITE>index c29453b..be22f37 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1 +1 @@<RESET>
<CYAN>@@ -1 +1 @@<RESET>
aaa (aaa) <GREEN>aaa<RESET>
EOF
@@ -209,7 +199,7 @@ cat > expect <<\EOF
<WHITE>index 289cb9d..2d06f37 100644<RESET>
<WHITE>--- a/pre<RESET>
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1 +1 @@<RESET>
<CYAN>@@ -1 +1 @@<RESET>
(<RED>:<RESET>
EOF

63
t/t4040-whitespace-status.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/sh
test_description='diff --exit-code with whitespace'
. ./test-lib.sh
test_expect_success setup '
mkdir a b &&
echo >c &&
echo >a/d &&
echo >b/e &&
git add . &&
test_tick &&
git commit -m initial &&
echo " " >a/d &&
test_tick &&
git commit -a -m second &&
echo " " >a/d &&
echo " " >b/e &&
git add a/d
'
test_expect_success 'diff-tree --exit-code' '
test_must_fail git diff --exit-code HEAD^ HEAD &&
test_must_fail git diff-tree --exit-code HEAD^ HEAD
'
test_expect_success 'diff-tree -b --exit-code' '
git diff -b --exit-code HEAD^ HEAD &&
git diff-tree -b -p --exit-code HEAD^ HEAD &&
git diff-tree -b --exit-code HEAD^ HEAD
'
test_expect_success 'diff-index --cached --exit-code' '
test_must_fail git diff --cached --exit-code HEAD &&
test_must_fail git diff-index --cached --exit-code HEAD
'
test_expect_success 'diff-index -b -p --cached --exit-code' '
git diff -b --cached --exit-code HEAD &&
git diff-index -b -p --cached --exit-code HEAD
'
test_expect_success 'diff-index --exit-code' '
test_must_fail git diff --exit-code HEAD &&
test_must_fail git diff-index --exit-code HEAD
'
test_expect_success 'diff-index -b -p --exit-code' '
git diff -b --exit-code HEAD &&
git diff-index -b -p --exit-code HEAD
'
test_expect_success 'diff-files --exit-code' '
test_must_fail git diff --exit-code &&
test_must_fail git diff-files --exit-code
'
test_expect_success 'diff-files -b -p --exit-code' '
git diff -b --exit-code &&
git diff-files -b -p --exit-code
'
test_done

View File

@@ -32,7 +32,7 @@ test_expect_success setup '
done &&
git update-ref HEAD "$commit" &&
git clone ./. victim &&
( cd victim && git log ) &&
( cd victim && git config receive.denyCurrentBranch warn && git log ) &&
git update-ref HEAD "$zero" &&
parent=$zero &&
i=0 &&
@@ -129,6 +129,7 @@ rewound_push_setup() {
cd parent &&
git init &&
echo one >file && git add file && git commit -m one &&
git config receive.denyCurrentBranch warn &&
echo two >file && git commit -a -m two
) &&
git clone parent child &&
@@ -190,16 +191,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
test "$parent_head" = "$child_head"
'
test_expect_success 'warn pushing to delete current branch' '
test_expect_success 'deny 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_must_fail git send-pack ../parent :refs/heads/master 2>errs
)
'

View File

@@ -18,6 +18,7 @@ test_expect_success setup '
git update-ref refs/heads/master $commit0 &&
git update-ref refs/heads/tofail $commit1 &&
git clone ./. victim &&
GIT_DIR=victim/.git git config receive.denyCurrentBranch warn &&
GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
git update-ref refs/heads/master $commit1 &&
git update-ref refs/heads/tofail $commit0

View File

@@ -7,19 +7,19 @@ test_description='Test the post-checkout hook.'
. ./test-lib.sh
test_expect_success setup '
echo Data for commit0. >a &&
echo Data for commit0. >b &&
git update-index --add a &&
git update-index --add b &&
tree0=$(git write-tree) &&
commit0=$(echo setup | git commit-tree $tree0) &&
git update-ref refs/heads/master $commit0 &&
git clone ./. clone1 &&
git clone ./. clone2 &&
GIT_DIR=clone2/.git git branch -a new2 &&
echo Data for commit1. >clone2/b &&
GIT_DIR=clone2/.git git add clone2/b &&
GIT_DIR=clone2/.git git commit -m new2
echo Data for commit0. >a &&
echo Data for commit0. >b &&
git update-index --add a &&
git update-index --add b &&
tree0=$(git write-tree) &&
commit0=$(echo setup | git commit-tree $tree0) &&
git update-ref refs/heads/master $commit0 &&
git clone ./. clone1 &&
git clone ./. clone2 &&
GIT_DIR=clone2/.git git branch new2 &&
echo Data for commit1. >clone2/b &&
GIT_DIR=clone2/.git git add clone2/b &&
GIT_DIR=clone2/.git git commit -m new2
'
for clone in 1 2; do

View File

@@ -8,6 +8,7 @@ test_expect_success setup '
>file1 && git add file1 && test_tick &&
git commit -m Initial &&
git config receive.denyCurrentBranch warn &&
mkdir another && (
cd another &&

View File

@@ -419,6 +419,20 @@ test_expect_success 'update default (overridden, with funny whitespace)' '
'
test_expect_success 'update (with remotes.default defined)' '
(cd one &&
for b in $(git branch -r)
do
git branch -r -d $b || break
done &&
git config remotes.default "drosophila" &&
git remote update &&
git branch -r > output &&
test_cmp expect output)
'
test_expect_success '"remote show" does not show symbolic refs' '
git clone one three &&

View File

@@ -12,6 +12,7 @@ mk_empty () {
(
cd testrepo &&
git init &&
git config receive.denyCurrentBranch warn &&
mv .git/hooks .git/hooks-disabled
)
}
@@ -546,6 +547,32 @@ test_expect_success 'allow deleting an invalid remote ref' '
'
test_expect_success 'allow deleting a ref using --delete' '
mk_test heads/master &&
(cd testrepo && git config receive.denyDeleteCurrent warn) &&
git push testrepo --delete master &&
(cd testrepo && test_must_fail git rev-parse --verify refs/heads/master)
'
test_expect_success 'allow deleting a tag using --delete' '
mk_test heads/master &&
git tag -a -m dummy_message deltag heads/master &&
git push testrepo --tags &&
(cd testrepo && git rev-parse --verify -q refs/tags/deltag) &&
git push testrepo --delete tag deltag &&
(cd testrepo && test_must_fail git rev-parse --verify refs/tags/deltag)
'
test_expect_success 'push --delete without args aborts' '
mk_test heads/master &&
test_must_fail git push testrepo --delete
'
test_expect_success 'push --delete refuses src:dest refspecs' '
mk_test heads/master &&
test_must_fail git push testrepo --delete master:foo
'
test_expect_success 'warn on push to HEAD of non-bare repository' '
mk_test heads/master
(cd testrepo &&

View File

@@ -19,7 +19,8 @@ mk_repo_pair () {
mkdir mirror &&
(
cd mirror &&
git init
git init &&
git config receive.denyCurrentBranch warn
) &&
mkdir master &&
(

View File

@@ -20,13 +20,19 @@ fi
#
# The working directory is subdir-link.
mkdir subdir
echo file >subdir/file
git add subdir/file
git commit -q -m file
git clone -q . clone-repo
ln -s clone-repo/subdir/ subdir-link
test_expect_success setup '
mkdir subdir &&
echo file >subdir/file &&
git add subdir/file &&
git commit -q -m file &&
git clone -q . clone-repo &&
ln -s clone-repo/subdir/ subdir-link &&
(
cd clone-repo &&
git config receive.denyCurrentBranch warn
) &&
git config receive.denyCurrentBranch warn
'
# Demonstrate that things work if we just avoid the symlink
#

View File

@@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' '
test_expect_success 'clone empty repository' '
cd "$D" &&
mkdir empty &&
(cd empty && git init) &&
(cd empty &&
git init &&
git config receive.denyCurrentBranch warn) &&
git clone empty empty-clone &&
test_tick &&
(cd empty-clone

View File

@@ -423,7 +423,7 @@ test_expect_success 'skipped merge base when good and bad are siblings' '
grep "merge base must be tested" my_bisect_log.txt &&
grep $HASH4 my_bisect_log.txt &&
git bisect skip > my_bisect_log.txt 2>&1 &&
grep "Warning" my_bisect_log.txt &&
grep "warning" my_bisect_log.txt &&
grep $SIDE_HASH6 my_bisect_log.txt &&
git bisect reset
'

View File

@@ -69,7 +69,7 @@ test_expect_success 'status' '
cd test &&
git checkout b1 >/dev/null &&
# reports nothing to commit
test_must_fail git status
test_must_fail git commit --dry-run
) >actual &&
grep "have 1 and 1 different" actual
'

View File

@@ -31,8 +31,7 @@ test_expect_success 'Report new path with conflict' '
cat >expect <<EOF
# On branch side
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add <file>..." to mark resolution)
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# deleted by us: foo
#
@@ -50,9 +49,11 @@ test_expect_success 'M/D conflict does not segfault' '
git rm foo &&
git commit -m delete &&
test_must_fail git merge master &&
test_must_fail git status > ../actual
) &&
test_cmp expect actual
test_must_fail git commit --dry-run >../actual &&
test_cmp ../expect ../actual &&
git status >../actual &&
test_cmp ../expect ../actual
)
'
test_done

View File

@@ -139,19 +139,19 @@ test_expect_success \
test_expect_success \
'resetting to HEAD with no changes should succeed and do nothing' '
git reset --hard &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset --hard HEAD &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset --soft &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset --soft HEAD &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset --mixed &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset --mixed HEAD &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
git reset HEAD &&
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
'

View File

@@ -29,6 +29,12 @@ test_expect_success 'soft reset is ok' '
(cd .git && git reset --soft)
'
test_expect_success 'hard reset works with GIT_WORK_TREE' '
mkdir worktree &&
GIT_WORK_TREE=$PWD/worktree GIT_DIR=$PWD/.git git reset --hard &&
test_cmp file worktree/file
'
test_expect_success 'setup bare' '
git clone --bare . bare.git &&
cd bare.git

View File

@@ -211,6 +211,21 @@ test_expect_success 'amend commit to fix author' '
'
test_expect_success 'amend commit to fix date' '
test_tick &&
newtick=$GIT_AUTHOR_DATE &&
git reset --hard &&
git cat-file -p HEAD |
sed -e "s/author.*/author $author $newtick/" \
-e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
expected &&
git commit --amend --date="$newtick" &&
git cat-file -p HEAD > current &&
test_cmp expected current
'
test_expect_success 'sign off (1)' '
echo 1 >positive &&

View File

@@ -19,8 +19,8 @@ test_expect_success 'status clean' '
git status |
grep "nothing to commit"
'
test_expect_success 'status -a clean' '
git status -a |
test_expect_success 'commit --dry-run -a clean' '
git commit --dry-run -a |
grep "nothing to commit"
'
test_expect_success 'rm submodule contents' '
@@ -31,7 +31,7 @@ test_expect_success 'status clean (empty submodule dir)' '
grep "nothing to commit"
'
test_expect_success 'status -a clean (empty submodule dir)' '
git status -a |
git commit --dry-run -a |
grep "nothing to commit"
'

View File

@@ -12,26 +12,26 @@ esac
. ./test-lib.sh
test_expect_success 'setup' '
: > tracked &&
: > modified &&
: >tracked &&
: >modified &&
mkdir dir1 &&
: > dir1/tracked &&
: > dir1/modified &&
: >dir1/tracked &&
: >dir1/modified &&
mkdir dir2 &&
: > dir1/tracked &&
: > dir1/modified &&
: >dir1/tracked &&
: >dir1/modified &&
git add . &&
git status >output &&
test_tick &&
git commit -m initial &&
: > untracked &&
: > dir1/untracked &&
: > dir2/untracked &&
echo 1 > dir1/modified &&
echo 2 > dir2/modified &&
echo 3 > dir2/added &&
: >untracked &&
: >dir1/untracked &&
: >dir2/untracked &&
echo 1 >dir1/modified &&
echo 2 >dir2/modified &&
echo 3 >dir2/added &&
git add dir2/added
'
@@ -41,7 +41,7 @@ test_expect_success 'status (1)' '
'
cat > expect << \EOF
cat >expect <<\EOF
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
@@ -67,7 +67,25 @@ EOF
test_expect_success 'status (2)' '
git status > output &&
git status >output &&
test_cmp expect output
'
cat >expect <<\EOF
M dir1/modified
A dir2/added
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s (2)' '
git status -s >output &&
test_cmp expect output
'
@@ -89,8 +107,8 @@ cat >expect <<EOF
EOF
test_expect_success 'status -uno' '
mkdir dir3 &&
: > dir3/untracked1 &&
: > dir3/untracked2 &&
: >dir3/untracked1 &&
: >dir3/untracked2 &&
git status -uno >output &&
test_cmp expect output
'
@@ -101,6 +119,22 @@ test_expect_success 'status (status.showUntrackedFiles no)' '
test_cmp expect output
'
cat >expect << EOF
M dir1/modified
A dir2/added
EOF
test_expect_success 'status -s -uno' '
git config --unset status.showuntrackedfiles
git status -s -uno >output &&
test_cmp expect output
'
test_expect_success 'status -s (status.showUntrackedFiles no)' '
git config status.showuntrackedfiles no
git status -s >output &&
test_cmp expect output
'
cat >expect <<EOF
# On branch master
# Changes to be committed:
@@ -136,6 +170,29 @@ test_expect_success 'status (status.showUntrackedFiles normal)' '
test_cmp expect output
'
cat >expect <<EOF
M dir1/modified
A dir2/added
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? dir3/
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s -unormal' '
git config --unset status.showuntrackedfiles
git status -s -unormal >output &&
test_cmp expect output
'
test_expect_success 'status -s (status.showUntrackedFiles normal)' '
git config status.showuntrackedfiles normal
git status -s >output &&
test_cmp expect output
'
cat >expect <<EOF
# On branch master
# Changes to be committed:
@@ -173,7 +230,30 @@ test_expect_success 'status (status.showUntrackedFiles all)' '
test_cmp expect output
'
cat > expect << \EOF
cat >expect <<EOF
M dir1/modified
A dir2/added
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s -uall' '
git config --unset status.showuntrackedfiles
git status -s -uall >output &&
test_cmp expect output
'
test_expect_success 'status -s (status.showUntrackedFiles all)' '
git config status.showuntrackedfiles all
git status -s >output &&
rm -rf dir3 &&
git config --unset status.showuntrackedfiles &&
test_cmp expect output
'
cat >expect <<\EOF
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
@@ -199,12 +279,156 @@ EOF
test_expect_success 'status with relative paths' '
(cd dir1 && git status) > output &&
(cd dir1 && git status) >output &&
test_cmp expect output
'
cat > expect << \EOF
cat >expect <<\EOF
M modified
A ../dir2/added
?? untracked
?? ../dir2/modified
?? ../dir2/untracked
?? ../expect
?? ../output
?? ../untracked
EOF
test_expect_success 'status -s with relative paths' '
(cd dir1 && git status -s) >output &&
test_cmp expect output
'
cat >expect <<\EOF
M dir1/modified
A dir2/added
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status --porcelain ignores relative paths setting' '
(cd dir1 && git status --porcelain) >output &&
test_cmp expect output
'
test_expect_success 'setup unique colors' '
git config status.color.untracked blue
'
cat >expect <<\EOF
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# <GREEN>new file: dir2/added<RESET>
#
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# <RED>modified: dir1/modified<RESET>
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# <BLUE>dir1/untracked<RESET>
# <BLUE>dir2/modified<RESET>
# <BLUE>dir2/untracked<RESET>
# <BLUE>expect<RESET>
# <BLUE>output<RESET>
# <BLUE>untracked<RESET>
EOF
test_expect_success 'status with color.ui' '
git config color.ui always &&
git status | test_decode_color >output &&
test_cmp expect output
'
test_expect_success 'status with color.status' '
git config --unset color.ui &&
git config color.status always &&
git status | test_decode_color >output &&
test_cmp expect output
'
cat >expect <<\EOF
<RED>M<RESET> dir1/modified
<GREEN>A<RESET> dir2/added
<BLUE>??<RESET> dir1/untracked
<BLUE>??<RESET> dir2/modified
<BLUE>??<RESET> dir2/untracked
<BLUE>??<RESET> expect
<BLUE>??<RESET> output
<BLUE>??<RESET> untracked
EOF
test_expect_success 'status -s with color.ui' '
git config --unset color.status &&
git config color.ui always &&
git status -s | test_decode_color >output &&
test_cmp expect output
'
test_expect_success 'status -s with color.status' '
git config --unset color.ui &&
git config color.status always &&
git status -s | test_decode_color >output &&
test_cmp expect output
'
cat >expect <<\EOF
M dir1/modified
A dir2/added
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status --porcelain ignores color.ui' '
git config --unset color.status &&
git config color.ui always &&
git status --porcelain | test_decode_color >output &&
test_cmp expect output
'
test_expect_success 'status --porcelain ignores color.status' '
git config --unset color.ui &&
git config color.status always &&
git status --porcelain | test_decode_color >output &&
test_cmp expect output
'
# recover unconditionally from color tests
git config --unset color.status
git config --unset color.ui
cat >expect <<\EOF
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
@@ -228,10 +452,29 @@ cat > expect << \EOF
# untracked
EOF
test_expect_success 'status without relative paths' '
git config status.relativePaths false
(cd dir1 && git status) > output &&
(cd dir1 && git status) >output &&
test_cmp expect output
'
cat >expect <<\EOF
M dir1/modified
A dir2/added
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s without relative paths' '
(cd dir1 && git status -s) >output &&
test_cmp expect output
'
@@ -252,8 +495,8 @@ cat <<EOF >expect
# output
# untracked
EOF
test_expect_success 'status of partial commit excluding new file in index' '
git status dir1/modified >output &&
test_expect_success 'dry-run of partial commit excluding new file in index' '
git commit --dry-run dir1/modified >output &&
test_cmp expect output
'
@@ -302,6 +545,28 @@ test_expect_success 'status --untracked-files=all does not show submodule' '
test_cmp expect output
'
cat >expect <<EOF
M dir1/modified
A dir2/added
A sm
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s submodule summary is disabled by default' '
git status -s >output &&
test_cmp expect output
'
# we expect the same as the previous test
test_expect_success 'status -s --untracked-files=all does not show submodule' '
git status -s --untracked-files=all >output &&
test_cmp expect output
'
head=$(cd sm && git rev-parse --short=7 --verify HEAD)
cat >expect <<EOF
@@ -339,6 +604,21 @@ test_expect_success 'status submodule summary' '
test_cmp expect output
'
cat >expect <<EOF
M dir1/modified
A dir2/added
A sm
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s submodule summary' '
git status -s >output &&
test_cmp expect output
'
cat >expect <<EOF
# On branch master
@@ -362,7 +642,23 @@ EOF
test_expect_success 'status submodule summary (clean submodule)' '
git commit -m "commit submodule" &&
git config status.submodulesummary 10 &&
test_must_fail git status >output &&
test_must_fail git commit --dry-run >output &&
test_cmp expect output &&
git status >output &&
test_cmp expect output
'
cat >expect <<EOF
M dir1/modified
?? dir1/untracked
?? dir2/modified
?? dir2/untracked
?? expect
?? output
?? untracked
EOF
test_expect_success 'status -s submodule summary (clean submodule)' '
git status -s >output &&
test_cmp expect output
'
@@ -395,9 +691,9 @@ cat >expect <<EOF
# output
# untracked
EOF
test_expect_success 'status submodule summary (--amend)' '
test_expect_success 'commit --dry-run submodule summary (--amend)' '
git config status.submodulesummary 10 &&
git status --amend >output &&
git commit --dry-run --amend >output &&
test_cmp expect output
'

View File

@@ -49,4 +49,55 @@ test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
done
'
cat >expected <<\EOF
Trying simple merge with c2
Trying simple merge with c3
Trying simple merge with c4
Merge made by octopus.
c2.c | 1 +
c3.c | 1 +
c4.c | 1 +
3 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 c2.c
create mode 100644 c3.c
create mode 100644 c4.c
EOF
test_expect_success 'merge output uses pretty names' '
git reset --hard c1 &&
git merge c2 c3 c4 >actual &&
test_cmp actual expected
'
cat >expected <<\EOF
Already up-to-date with c4
Trying simple merge with c5
Merge made by octopus.
c5.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 c5.c
EOF
test_expect_success 'merge up-to-date output uses pretty names' '
git merge c4 c5 >actual &&
test_cmp actual expected
'
cat >expected <<\EOF
Fast-forwarding to: c1
Trying simple merge with c2
Merge made by octopus.
c1.c | 1 +
c2.c | 1 +
2 files changed, 2 insertions(+), 0 deletions(-)
create mode 100644 c1.c
create mode 100644 c2.c
EOF
test_expect_success 'merge fast-forward output uses pretty names' '
git reset --hard c0 &&
git merge c1 c2 >actual &&
test_cmp actual expected
'
test_done

View File

@@ -105,6 +105,8 @@ do
verbose=t; shift ;;
-q|--q|--qu|--qui|--quie|--quiet)
quiet=t; shift ;;
--with-dashes)
with_dashes=t; shift ;;
--no-color)
color=; shift ;;
--no-python)
@@ -211,6 +213,17 @@ test_set_editor () {
export EDITOR
}
test_decode_color () {
sed -e 's/.\[1m/<WHITE>/g' \
-e 's/.\[31m/<RED>/g' \
-e 's/.\[32m/<GREEN>/g' \
-e 's/.\[33m/<YELLOW>/g' \
-e 's/.\[34m/<BLUE>/g' \
-e 's/.\[35m/<MAGENTA>/g' \
-e 's/.\[36m/<CYAN>/g' \
-e 's/.\[m/<RESET>/g'
}
test_tick () {
if test -z "${test_tick+set}"
then
@@ -551,19 +564,8 @@ test_done () {
# Test the binaries we have just built. The tests are kept in
# t/ subdirectory and are run in 'trash directory' subdirectory.
TEST_DIRECTORY=$(pwd)
if test -z "$valgrind"
if test -n "$valgrind"
then
if test -z "$GIT_TEST_INSTALLED"
then
PATH=$TEST_DIRECTORY/..:$PATH
GIT_EXEC_PATH=$TEST_DIRECTORY/..
else
GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
error "Cannot run git from $GIT_TEST_INSTALLED."
PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
fi
else
make_symlink () {
test -h "$2" &&
test "$1" = "$(readlink "$2")" || {
@@ -625,6 +627,24 @@ else
PATH=$GIT_VALGRIND/bin:$PATH
GIT_EXEC_PATH=$GIT_VALGRIND/bin
export GIT_VALGRIND
elif test -n "$GIT_TEST_INSTALLED" ; then
GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
error "Cannot run git from $GIT_TEST_INSTALLED."
PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
else # normal case, use ../bin-wrappers only unless $with_dashes:
git_bin_dir="$TEST_DIRECTORY/../bin-wrappers"
if ! test -x "$git_bin_dir/git" ; then
if test -z "$with_dashes" ; then
say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
fi
with_dashes=t
fi
PATH="$git_bin_dir:$PATH"
GIT_EXEC_PATH=$TEST_DIRECTORY/..
if test -n "$with_dashes" ; then
PATH="$TEST_DIRECTORY/..:$PATH"
fi
fi
GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
unset GIT_CONFIG
@@ -632,20 +652,29 @@ GIT_CONFIG_NOSYSTEM=1
GIT_CONFIG_NOGLOBAL=1
export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
. ../GIT-BUILD-OPTIONS
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
export GITPERLLIB
test -d ../templates/blt || {
error "You haven't built things yet, have you?"
}
if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
then
GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib"
export GITPYTHONLIB
test -d ../git_remote_helpers/build || {
error "You haven't built git_remote_helpers yet, have you?"
}
fi
if ! test -x ../test-chmtime; then
echo >&2 'You need to build test-chmtime:'
echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
exit 1
fi
. ../GIT-BUILD-OPTIONS
# Test repository
test="trash directory.$(basename "$0" .sh)"
test -n "$root" && test="$root/$test"
@@ -729,6 +758,7 @@ case $(uname -s) in
esac
test -z "$NO_PERL" && test_set_prereq PERL
test -z "$NO_PYTHON" && test_set_prereq PYTHON
# test whether the filesystem supports symbolic links
ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS

Some files were not shown because too many files have changed in this diff Show More