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

This commit is contained in:
Johannes Sixt
2009-04-20 08:07:54 +02:00
62 changed files with 1606 additions and 739 deletions

4
.gitignore vendored
View File

@@ -11,6 +11,7 @@ git-apply
git-archimport
git-archive
git-bisect
git-bisect--helper
git-blame
git-branch
git-bundle
@@ -35,6 +36,8 @@ git-diff
git-diff-files
git-diff-index
git-diff-tree
git-difftool
git-difftool--helper
git-describe
git-fast-export
git-fast-import
@@ -78,6 +81,7 @@ git-merge-recursive
git-merge-resolve
git-merge-subtree
git-mergetool
git-mergetool--lib
git-mktag
git-mktree
git-name-rev

View File

@@ -0,0 +1,31 @@
GIT v1.6.2.4 Release Notes
==========================
Fixes since v1.6.2.3
--------------------
* The configuration parser had a buffer overflow while parsing an overlong
value.
* "git-checkout <tree-ish> <submodule>" did not update the index entry at
the named path; it now does.
* "git init" segfaulted when given an overlong template location via
the --template= option.
* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
deciding to descend into a subdirectory but they did not match the
individual paths correctly. This caused pathspecs "abc/d ab" to match
"abc/0" ("abc/d" made them decide to descend into the directory "abc/",
and then "ab" incorrectly matched "abc/0" when it shouldn't).
* "git-merge-recursive" was broken when a submodule entry was involved in
a criss-cross merge situation.
Many small documentation updates are included as well.
---
exec >/var/tmp/1
echo O=$(git describe maint)
O=v1.6.2.3-21-ga51609a
git shortlog --no-merges $O..maint

View File

@@ -47,6 +47,8 @@ Updates since v1.6.2
(usability, bells and whistles)
* Boolean configuration variable yes/no can be written as on/off.
* rsync:/path/to/repo can be used to run git over rsync for local
repositories. It may not be useful in practice; meant primarily for
testing.
@@ -63,6 +65,9 @@ Updates since v1.6.2
* "--oneline" is a synonym for "--pretty=oneline --abbrev-commit".
* "--graph" to the "git log" family can draw the commit ancestry graph
in colors.
* If you realize that you botched the patch when you are editing hunks
with the 'edit' action in git-add -i/-p, you can abort the editor to
tell git not to apply it.
@@ -75,8 +80,16 @@ Updates since v1.6.2
1.6.2, but the initial implementation did not teach this to a few
commands. Now the syntax works with "branch -m @{-1} newname".
* "git-add -p" now supports "q"uit action.
* git-archive learned --output=<file> option.
* git-archive takes attributes from the tree being archived; strictly
speaking, this is an incompatible behaviour change, but is a good one.
Use --worktree-attributes option to allow it to read attributes from
the work tree as before (deprecated git-tar tree command always reads
attributes from the work tree).
* git-bisect shows not just the number of remaining commits whose goodness
is unknown, but also shows the estimated number of remaining rounds.
@@ -93,6 +106,9 @@ Updates since v1.6.2
* git-clone runs post-checkout hook when run without --no-checkout.
* git-difftool is now part of the officially supported command, primarily
maintained by David Aguilar.
* git-fast-export choked when seeing a tag that does not point at commit.
* git-for-each-ref learned a new "upstream" token.
@@ -156,26 +172,18 @@ release, unless otherwise noted.
Here are fixes that this release has, but have not been backported to
v1.6.2.X series.
* "git-apply" rejected a patch that swaps two files (i.e. renames A to B
and B to A at the same time). May need to be backported by cherry
picking d8c81df and then 7fac0ee).
* The initial checkout did not read the attributes from the .gitattribute
file that is being checked out.
* "git-checkout <tree-ish> <submodule>" did not update the index entry at
the named path; it now does.
* git-gc spent excessive amount of time to decide if an object appears
in a locally existing pack (if needed, backport by merging 69e020a).
* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
deciding to descend into a subdirectory but they did not match the
individual paths correctly. This caused pathspecs "abc/d ab" to match
"abc/0" ("abc/d" made them decide to descend into the directory "abc/",
and then "ab" incorrectly matched "abc/0" when it shouldn't).
* "git-merge-recursive" was broken when a submodule entry was involved in
a criss-cross merge situation.
---
exec >/var/tmp/1
O=v1.6.2.3-497-g54a4749
O=v1.6.3-rc0-74-g9824a38
echo O=$(git describe master)
git shortlog --no-merges $O..master ^maint

View File

@@ -61,7 +61,7 @@ Internal whitespace within a variable value is retained verbatim.
The values following the equals sign in variable assign are all either
a string, an integer, or a boolean. Boolean values may be given as yes/no,
0/1 or true/false. Case is not significant in boolean values, when
0/1, true/false or on/off. Case is not significant in boolean values, when
converting value to the canonical form using '--bool' type specifier;
'git-config' will ensure that the output is "true" or "false".
@@ -667,6 +667,27 @@ diff.suppressBlankEmpty::
A boolean to inhibit the standard behavior of printing a space
before each empty output line. Defaults to false.
diff.tool::
Controls which diff tool is used. `diff.tool` overrides
`merge.tool` when used by linkgit:git-difftool[1] and has
the same valid values as `merge.tool` minus "tortoisemerge"
and plus "kompare".
difftool.<tool>.path::
Override the path for the given tool. This is useful in case
your tool is not in the PATH.
difftool.<tool>.cmd::
Specify the command to invoke the specified diff tool.
The specified command is evaluated in shell with the following
variables available: 'LOCAL' is set to the name of the temporary
file containing the contents of the diff pre-image and 'REMOTE'
is set to the name of the temporary file containing the contents
of the diff post-image.
difftool.prompt::
Prompt before each invocation of the diff tool.
diff.wordRegex::
A POSIX Extended Regular Expression used to determine what is a "word"
when performing word-by-word difference calculations. Character

View File

@@ -245,8 +245,11 @@ patch::
y - stage this hunk
n - do not stage this hunk
q - quit, do not stage this hunk nor any of the remaining ones
a - stage this and all the remaining hunks in the file
d - do not stage this hunk nor any of the remaining hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk

View File

@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
[--output=<file>]
[--output=<file>] [--worktree-attributes]
[--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
[path...]
@@ -51,6 +51,9 @@ OPTIONS
--output=<file>::
Write the archive to <file> instead of stdout.
--worktree-attributes::
Look for attributes in .gitattributes in working directory too.
<extra>::
This can be any options that the archiver backend understands.
See next section.

View File

@@ -112,19 +112,22 @@ OPTIONS
Display the full sha1s in the output listing rather than abbreviating them.
--track::
When creating a new branch, set up the configuration so that 'git-pull'
will automatically retrieve data from the start point, which must be
a branch. Use this if you always pull from the same upstream branch
into the new branch, and if you do not want to use "git pull
<repository> <refspec>" explicitly. This behavior is the default
when the start point is a remote branch. Set the
branch.autosetupmerge configuration variable to `false` if you want
'git-checkout' and 'git-branch' to always behave as if '--no-track' were
given. Set it to `always` if you want this behavior when the
start-point is either a local or remote branch.
When creating a new branch, set up configuration to mark the
start-point branch as "upstream" from the new branch. This
configuration will tell git to show the relationship between the
two branches in `git status` and `git branch -v`. Furthermore,
it directs `git pull` without arguments to pull from the
upstream when the new branch is checked out.
+
This behavior is the default when the start point is a remote branch.
Set the branch.autosetupmerge configuration variable to `false` if you
want `git checkout` and `git branch` to always behave as if '--no-track'
were given. Set it to `always` if you want this behavior when the
start-point is either a local or remote branch.
--no-track::
Ignore the branch.autosetupmerge configuration variable.
Do not set up "upstream" configuration, even if the
branch.autosetupmerge configuration variable is true.
--contains <commit>::
Only list branches which contain the specified commit.

View File

@@ -8,22 +8,22 @@ git-checkout - Checkout a branch or paths to the working tree
SYNOPSIS
--------
[verse]
'git checkout' [-q] [-f] [-t | --track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
'git checkout' [-q] [-f] [-m] [<branch>]
'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
DESCRIPTION
-----------
When <paths> are not given, this command switches branches by
updating the index and working tree to reflect the specified
branch, <branch>, and updating HEAD to be <branch> or, if
specified, <new_branch>. Using -b will cause <new_branch> to
be created; in this case you can use the --track or --no-track
options, which will be passed to `git branch`.
updating the index, working tree, and HEAD to reflect the specified
branch.
As a convenience, --track will default to creating a branch whose
name is constructed from the specified branch name by stripping
the first namespace level.
If `-b` is given, a new branch is created and checked out, as if
linkgit:git-branch[1] were called; in this case you can
use the --track or --no-track options, which will be passed to `git
branch`. As a convenience, --track without `-b` implies branch
creation; see the description of --track below.
When <paths> are given, this command does *not* switch
branches. It updates the named paths in the working tree from
@@ -62,22 +62,12 @@ entries; instead, unmerged entries are ignored.
-b::
Create a new branch named <new_branch> and start it at
<branch>. The new branch name must pass all checks defined
by linkgit:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name.
<start_point>; see linkgit:git-branch[1] for details.
-t::
--track::
When creating a new branch, set up configuration so that 'git-pull'
will automatically retrieve data from the start point, which must be
a branch. Use this if you always pull from the same upstream branch
into the new branch, and if you don't want to use "git pull
<repository> <refspec>" explicitly. This behavior is the default
when the start point is a remote branch. Set the
branch.autosetupmerge configuration variable to `false` if you want
'git checkout' and 'git branch' to always behave as if '--no-track' were
given. Set it to `always` if you want this behavior when the
start point is either a local or remote branch.
When creating a new branch, set up "upstream" configuration. See
"--track" in linkgit:git-branch[1] for details.
+
If no '-b' option is given, the name of the new branch will be
derived from the remote branch. If "remotes/" or "refs/remotes/"
@@ -90,12 +80,12 @@ guessing results in an empty name, the guessing is aborted. You can
explicitly give a name with '-b' in such a case.
--no-track::
Ignore the branch.autosetupmerge configuration variable.
Do not set up "upstream" configuration, even if the
branch.autosetupmerge configuration variable is true.
-l::
Create the new branch's reflog. This activates recording of
all changes made to the branch ref, enabling use of date
based sha1 expressions such as "<branchname>@\{yesterday}".
Create the new branch's reflog; see linkgit:git-branch[1] for
details.
-m::
--merge::
@@ -123,23 +113,28 @@ the conflicted merge in the specified paths.
"merge" (default) and "diff3" (in addition to what is shown by
"merge" style, shows the original contents).
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
when prepended with "refs/heads/", is a valid ref), then that
branch is checked out. Otherwise, if it refers to a valid
commit, your HEAD becomes "detached" and you are no longer on
any branch (see below for details).
+
As a special case, the `"@\{-N\}"` syntax for the N-th last branch
checks out the branch (instead of detaching). You may also specify
`-` which is synonymous with `"@\{-1\}"`.
<new_branch>::
Name for the new branch.
<start_point>::
The name of a commit at which to start the new branch; see
linkgit:git-branch[1] for details. Defaults to HEAD.
<tree-ish>::
Tree to checkout from (when paths are given). If not specified,
the index will be used.
<branch>::
Branch to checkout (when no paths are given); may be any object
ID that resolves to a commit. Defaults to HEAD.
+
When this parameter names a non-branch (but still a valid commit object),
your HEAD becomes 'detached'.
+
As a special case, the `"@\{-N\}"` syntax for the N-th last branch
checks out the branch (instead of detaching). You may also specify
`-` which is synonymous with `"@\{-1\}"`.
Detached HEAD

View File

@@ -48,7 +48,7 @@ OPTIONS
'git-daemon' will refuse to start when this option is enabled and no
whitelist is specified.
--base-path::
--base-path=path::
Remap all the path requests as relative to the given path.
This is sort of "GIT root" - if you run 'git-daemon' with
'--base-path=/srv/git' on example.com, then if you later try to pull
@@ -81,8 +81,8 @@ OPTIONS
Incompatible with --port, --listen, --user and --group options.
--listen=host_or_ipaddr::
Listen on an a specific IP address or hostname. IP addresses can
be either an IPv4 address or an IPV6 address if supported. If IPv6
Listen on a specific IP address or hostname. IP addresses can
be either an IPv4 address or an IPv6 address if supported. If IPv6
is not supported, then --listen=hostname is also not supported and
--listen must be given an IPv4 address.
Incompatible with '--inetd' option.
@@ -90,17 +90,17 @@ OPTIONS
--port=n::
Listen on an alternative port. Incompatible with '--inetd' option.
--init-timeout::
--init-timeout=n::
Timeout between the moment the connection is established and the
client request is received (typically a rather low value, since
that should be basically immediate).
--timeout::
--timeout=n::
Timeout for specific client sub-requests. This includes the time
it takes for the server to process the sub-request and time spent
waiting for next client's request.
it takes for the server to process the sub-request and the time spent
waiting for the next client's request.
--max-connections::
--max-connections=n::
Maximum number of concurrent clients, defaults to 32. Set it to
zero for no limit.
@@ -150,7 +150,7 @@ the facility of inet daemon to achieve the same before spawning
Enable/disable the service site-wide per default. Note
that a service disabled site-wide can still be enabled
per repository if it is marked overridable and the
repository enables the service with an configuration
repository enables the service with a configuration
item.
--allow-override=service::

View File

@@ -3,35 +3,37 @@ git-difftool(1)
NAME
----
git-difftool - compare changes using common merge tools
git-difftool - Show changes using common diff tools
SYNOPSIS
--------
'git difftool' [--tool=<tool>] [--no-prompt] ['git diff' options]
'git difftool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<'git diff' options>]
DESCRIPTION
-----------
'git-difftool' is a git command that allows you to compare and edit files
between revisions using common merge tools. At its most basic level,
'git-difftool' does what 'git-mergetool' does but its use is for non-merge
situations such as when preparing commits or comparing changes against
the index.
'git difftool' is a frontend to 'git diff' and accepts the same
arguments and options.
See linkgit:git-diff[1] for the full list of supported options.
between revisions using common diff tools. 'git difftool' is a frontend
to 'git-diff' and accepts the same options and arguments.
OPTIONS
-------
-y::
--no-prompt::
Do not prompt before launching a diff tool.
--prompt::
Prompt before each invocation of the diff tool.
This is the default behaviour; the option is provided to
override any configuration settings.
-t <tool>::
--tool=<tool>::
Use the merge resolution program specified by <tool>.
Use the diff tool specified by <tool>.
Valid merge tools are:
kdiff3, kompare, tkdiff, meld, xxdiff, emerge,
vimdiff, gvimdiff, ecmerge, and opendiff
kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
ecmerge, diffuse and opendiff
+
If a merge resolution program is not specified, 'git-difftool'
If a diff tool is not specified, 'git-difftool'
will use the configuration variable `diff.tool`. If the
configuration variable `diff.tool` is not set, 'git-difftool'
will pick a suitable default.
@@ -42,7 +44,7 @@ can configure the absolute path to kdiff3 by setting
`difftool.kdiff3.path`. Otherwise, 'git-difftool' assumes the
tool is available in PATH.
+
Instead of running one of the known merge tool programs,
Instead of running one of the known diff tools,
'git-difftool' can be customized to run an alternative program
by specifying the command line to invoke in a configuration
variable `difftool.<tool>.cmd`.
@@ -56,8 +58,7 @@ is set to the name of the temporary file containing the contents
of the diff post-image. `$BASE` is provided for compatibility
with custom merge tool commands and has the same value as `$LOCAL`.
--no-prompt::
Do not prompt before launching a diff tool.
See linkgit:git-diff[1] for the full list of supported options.
CONFIG VARIABLES
----------------
@@ -65,20 +66,19 @@ CONFIG VARIABLES
difftool equivalents have not been defined.
diff.tool::
The default merge tool to use.
The default diff tool to use.
difftool.<tool>.path::
Override the path for the given tool. This is useful in case
your tool is not in the PATH.
difftool.<tool>.cmd::
Specify the command to invoke the specified merge tool.
Specify the command to invoke the specified diff tool.
+
See the `--tool=<tool>` option above for more details.
merge.keepBackup::
The original, unedited file content can be saved to a file with
a `.orig` extension. Defaults to `true` (i.e. keep the backup files).
difftool.prompt::
Prompt before each invocation of the diff tool.
SEE ALSO
--------

View File

@@ -75,6 +75,8 @@ For all objects, the following names can be used:
refname::
The name of the ref (the part after $GIT_DIR/).
For a non-ambiguous short name of the ref append `:short`.
The option core.warnAmbiguousRefs is used to select the strict
abbreviation mode.
objecttype::
The type of the object (`blob`, `tree`, `commit`, `tag`).

View File

@@ -51,7 +51,7 @@ imap.host::
imap.user::
The username to use when logging in to the server.
imap.password::
imap.pass::
The password to use when logging in to the server.
imap.port::

View File

@@ -0,0 +1,54 @@
git-mergetool--lib(1)
=====================
NAME
----
git-mergetool--lib - Common git merge tool shell scriptlets
SYNOPSIS
--------
'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool--lib"'
DESCRIPTION
-----------
This is not a command the end user would want to run. Ever.
This documentation is meant for people who are studying the
Porcelain-ish scripts and/or are writing new ones.
The 'git-mergetool--lib' scriptlet is designed to be sourced (using
`.`) by other shell scripts to set up functions for working
with git merge tools.
Before sourcing 'git-mergetool--lib', your script must set `TOOL_MODE`
to define the operation mode for the functions listed below.
'diff' and 'merge' are valid values.
FUNCTIONS
---------
get_merge_tool::
returns a merge tool.
get_merge_tool_cmd::
returns the custom command for a merge tool.
get_merge_tool_path::
returns the custom path for a merge tool.
run_merge_tool::
launches a merge tool given the tool name and a true/false
flag to indicate whether a merge base is present.
'$MERGED', '$LOCAL', '$REMOTE', and '$BASE' must be defined
for use by the merge tool.
Author
------
Written by David Aguilar <davvid@gmail.com>
Documentation
--------------
Documentation by David Aguilar and the git-list <git@vger.kernel.org>.
GIT
---
Part of the linkgit:git[1] suite

View File

@@ -26,7 +26,8 @@ OPTIONS
--tool=<tool>::
Use the merge resolution program specified by <tool>.
Valid merge tools are:
kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
diffuse, tortoisemerge and opendiff
+
If a merge resolution program is not specified, 'git-mergetool'
will use the configuration variable `merge.tool`. If the

View File

@@ -84,6 +84,11 @@ OPTIONS
unfortunately named tag "master"), and show them as full
refnames (e.g. "refs/heads/master").
--abbrev-ref[={strict|loose}]::
A non-ambiguous short name of the objects name.
The option core.warnAmbiguousRefs is used to select the strict
abbreviation mode.
--all::
Show all refs found in `$GIT_DIR/refs`.

View File

@@ -18,9 +18,9 @@ of server-side GIT commands implementing the pull/push functionality.
The commands can be executed only by the '-c' option; the shell is not
interactive.
Currently, only three commands are permitted to be called, 'git-receive-pack'
'git-upload-pack' with a single required argument or 'cvs server' (to invoke
'git-cvsserver').
Currently, only four commands are permitted to be called, 'git-receive-pack'
'git-upload-pack' and 'git-upload-archive' with a single required argument, or
'cvs server' (to invoke 'git-cvsserver').
Author
------

View File

@@ -297,7 +297,8 @@ for paths.
Then, you would define a "diff.tex.xfuncname" configuration to
specify a regular expression that matches a line that you would
want to appear as the hunk header "TEXT", like this:
want to appear as the hunk header "TEXT". Add a section to your
`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
------------------------
[diff "tex"]
@@ -345,7 +346,8 @@ split words in a line, by specifying an appropriate regular expression
in the "diff.*.wordRegex" configuration variable. For example, in TeX
a backslash followed by a sequence of letters forms a command, but
several such commands can be run together without intervening
whitespace. To separate them, use a regular expression such as
whitespace. To separate them, use a regular expression in your
`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
------------------------
[diff "tex"]
@@ -373,7 +375,8 @@ resulting text on stdout.
For example, to show the diff of the exif information of a
file instead of the binary information (assuming you have the
exif tool installed):
exif tool installed), add the following section to your
`$GIT_DIR/config` file (or `$HOME/.gitconfig` file):
------------------------
[diff "jpg"]

View File

@@ -22,7 +22,8 @@ merge.stat::
merge.tool::
Controls which merge resolution program is used by
linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3",
"tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and
"tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff",
"diffuse", "ecmerge", "tortoisemerge", and
"opendiff". Any other value is treated is custom merge tool
and there must be a corresponding mergetool.<tool>.cmd option.

View File

@@ -280,12 +280,14 @@ TEST_PROGRAMS =
SCRIPT_SH += git-am.sh
SCRIPT_SH += git-bisect.sh
SCRIPT_SH += git-difftool--helper.sh
SCRIPT_SH += git-filter-branch.sh
SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-mergetool--lib.sh
SCRIPT_SH += git-parse-remote.sh
SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
@@ -299,6 +301,7 @@ SCRIPT_SH += git-submodule.sh
SCRIPT_SH += git-web--browse.sh
SCRIPT_PERL += git-add--interactive.perl
SCRIPT_PERL += git-difftool.perl
SCRIPT_PERL += git-archimport.perl
SCRIPT_PERL += git-cvsexportcommit.perl
SCRIPT_PERL += git-cvsimport.perl

View File

@@ -4,6 +4,7 @@
#include "attr.h"
#include "archive.h"
#include "parse-options.h"
#include "unpack-trees.h"
static char const * const archive_usage[] = {
"git archive [options] <tree-ish> [path...]",
@@ -150,6 +151,8 @@ int write_archive_entries(struct archiver_args *args,
write_archive_entry_fn_t write_entry)
{
struct archiver_context context;
struct unpack_trees_options opts;
struct tree_desc t;
int err;
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
@@ -168,6 +171,22 @@ int write_archive_entries(struct archiver_args *args,
context.args = args;
context.write_entry = write_entry;
/*
* Setup index and instruct attr to read index only
*/
if (!args->worktree_attributes) {
memset(&opts, 0, sizeof(opts));
opts.index_only = 1;
opts.head_idx = -1;
opts.src_index = &the_index;
opts.dst_index = &the_index;
opts.fn = oneway_merge;
init_tree_desc(&t, args->tree->buffer, args->tree->size);
if (unpack_trees(1, &t, &opts))
return -1;
git_attr_set_direction(GIT_ATTR_INDEX, &the_index);
}
err = read_tree_recursive(args->tree, args->base, args->baselen, 0,
args->pathspec, write_archive_entry, &context);
if (err == READ_TREE_RECURSIVE)
@@ -258,6 +277,7 @@ static int parse_archive_args(int argc, const char **argv,
int verbose = 0;
int i;
int list = 0;
int worktree_attributes = 0;
struct option opts[] = {
OPT_GROUP(""),
OPT_STRING(0, "format", &format, "fmt", "archive format"),
@@ -265,6 +285,8 @@ static int parse_archive_args(int argc, const char **argv,
"prepend prefix to each pathname in the archive"),
OPT_STRING(0, "output", &output, "file",
"write the archive to this file"),
OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
"read .gitattributes in working directory"),
OPT__VERBOSE(&verbose),
OPT__COMPR('0', &compression_level, "store only", 0),
OPT__COMPR('1', &compression_level, "compress faster", 1),
@@ -324,6 +346,7 @@ static int parse_archive_args(int argc, const char **argv,
args->verbose = verbose;
args->base = base;
args->baselen = strlen(base);
args->worktree_attributes = worktree_attributes;
return argc;
}

View File

@@ -10,6 +10,7 @@ struct archiver_args {
time_t time;
const char **pathspec;
unsigned int verbose : 1;
unsigned int worktree_attributes : 1;
int compression_level;
};

12
attr.c
View File

@@ -405,7 +405,7 @@ static struct attr_stack *read_attr(const char *path, int macro_ok)
if (!res)
res = read_attr_from_file(path, macro_ok);
}
else {
else if (direction == GIT_ATTR_CHECKIN) {
res = read_attr_from_file(path, macro_ok);
if (!res)
/*
@@ -415,6 +415,8 @@ static struct attr_stack *read_attr(const char *path, int macro_ok)
*/
res = read_attr_from_index(path, macro_ok);
}
else
res = read_attr_from_index(path, macro_ok);
if (!res)
res = xcalloc(1, sizeof(*res));
return res;
@@ -466,7 +468,7 @@ static void bootstrap_attr_stack(void)
elem->prev = attr_stack;
attr_stack = elem;
if (!is_bare_repository()) {
if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
elem = read_attr(GITATTRIBUTES_FILE, 1);
elem->origin = strdup("");
elem->prev = attr_stack;
@@ -533,7 +535,7 @@ static void prepare_attr_stack(const char *path, int dirlen)
/*
* Read from parent directories and push them down
*/
if (!is_bare_repository()) {
if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
while (1) {
char *cp;
@@ -674,6 +676,10 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
{
enum git_attr_direction old = direction;
if (is_bare_repository() && new != GIT_ATTR_INDEX)
die("BUG: non-INDEX attr direction in a bare repo");
direction = new;
if (new != old)
drop_attr_stack();

3
attr.h
View File

@@ -33,7 +33,8 @@ int git_checkattr(const char *path, int, struct git_attr_check *);
enum git_attr_direction {
GIT_ATTR_CHECKIN,
GIT_ATTR_CHECKOUT
GIT_ATTR_CHECKOUT,
GIT_ATTR_INDEX,
};
void git_attr_set_direction(enum git_attr_direction, struct index_state *);

View File

@@ -2271,6 +2271,25 @@ static struct patch *in_fn_table(const char *name)
return NULL;
}
/*
* item->util in the filename table records the status of the path.
* Usually it points at a patch (whose result records the contents
* of it after applying it), but it could be PATH_WAS_DELETED for a
* path that a previously applied patch has already removed.
*/
#define PATH_TO_BE_DELETED ((struct patch *) -2)
#define PATH_WAS_DELETED ((struct patch *) -1)
static int to_be_deleted(struct patch *patch)
{
return patch == PATH_TO_BE_DELETED;
}
static int was_deleted(struct patch *patch)
{
return patch == PATH_WAS_DELETED;
}
static void add_to_fn_table(struct patch *patch)
{
struct string_list_item *item;
@@ -2291,7 +2310,22 @@ static void add_to_fn_table(struct patch *patch)
*/
if ((patch->new_name == NULL) || (patch->is_rename)) {
item = string_list_insert(patch->old_name, &fn_table);
item->util = (struct patch *) -1;
item->util = PATH_WAS_DELETED;
}
}
static void prepare_fn_table(struct patch *patch)
{
/*
* store information about incoming file deletion
*/
while (patch) {
if ((patch->new_name == NULL) || (patch->is_rename)) {
struct string_list_item *item;
item = string_list_insert(patch->old_name, &fn_table);
item->util = PATH_TO_BE_DELETED;
}
patch = patch->next;
}
}
@@ -2304,8 +2338,8 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
struct patch *tpatch;
if (!(patch->is_copy || patch->is_rename) &&
((tpatch = in_fn_table(patch->old_name)) != NULL)) {
if (tpatch == (struct patch *) -1) {
(tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
if (was_deleted(tpatch)) {
return error("patch %s has been renamed/deleted",
patch->old_name);
}
@@ -2399,10 +2433,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
assert(patch->is_new <= 0);
if (!(patch->is_copy || patch->is_rename) &&
(tpatch = in_fn_table(old_name)) != NULL) {
if (tpatch == (struct patch *) -1) {
(tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
if (was_deleted(tpatch))
return error("%s: has been deleted/renamed", old_name);
}
st_mode = tpatch->new_mode;
} else if (!cached) {
stat_ret = lstat(old_name, st);
@@ -2410,6 +2443,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
return error("%s: %s", old_name, strerror(errno));
}
if (to_be_deleted(tpatch))
tpatch = NULL;
if (check_index && !tpatch) {
int pos = cache_name_pos(old_name, strlen(old_name));
if (pos < 0) {
@@ -2471,6 +2507,7 @@ static int check_patch(struct patch *patch)
const char *new_name = patch->new_name;
const char *name = old_name ? old_name : new_name;
struct cache_entry *ce = NULL;
struct patch *tpatch;
int ok_if_exists;
int status;
@@ -2481,7 +2518,8 @@ static int check_patch(struct patch *patch)
return status;
old_name = patch->old_name;
if (in_fn_table(new_name) == (struct patch *) -1)
if ((tpatch = in_fn_table(new_name)) &&
(was_deleted(tpatch) || to_be_deleted(tpatch)))
/*
* A type-change diff is always split into a patch to
* delete old, immediately followed by a patch to
@@ -2533,6 +2571,7 @@ static int check_patch_list(struct patch *patch)
{
int err = 0;
prepare_fn_table(patch);
while (patch) {
if (apply_verbosely)
say_patch_name(stderr,

View File

@@ -311,14 +311,14 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
if (branch && branch->merge && branch->merge[0]->dst &&
show_upstream_ref)
strbuf_addf(stat, "[%s] ",
shorten_unambiguous_ref(branch->merge[0]->dst));
shorten_unambiguous_ref(branch->merge[0]->dst, 0));
return;
}
strbuf_addch(stat, '[');
if (show_upstream_ref)
strbuf_addf(stat, "%s: ",
shorten_unambiguous_ref(branch->merge[0]->dst));
shorten_unambiguous_ref(branch->merge[0]->dst, 0));
if (!ours)
strbuf_addf(stat, "behind %d] ", theirs);
else if (!theirs)

View File

@@ -601,7 +601,8 @@ static void populate_value(struct refinfo *ref)
if (formatp) {
formatp++;
if (!strcmp(formatp, "short"))
refname = shorten_unambiguous_ref(refname);
refname = shorten_unambiguous_ref(refname,
warn_ambiguous_refs);
else
die("unknown %.*s format %s",
(int)(formatp - name), name, formatp);
@@ -917,6 +918,9 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
sort = default_sort();
sort_atom_limit = used_atom_cnt;
/* for warn_ambiguous_refs */
git_config(git_default_config, NULL);
memset(&cbdata, 0, sizeof(cbdata));
cbdata.grab_pattern = argv;
for_each_ref(grab_single_ref, &cbdata);

View File

@@ -122,8 +122,10 @@ static void copy_templates(const char *template_dir)
template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
if (!template_dir[0])
return;
template_len = strlen(template_dir);
if (PATH_MAX <= (template_len+strlen("/config")))
die("insanely long template path %s", template_dir);
strcpy(template_path, template_dir);
template_len = strlen(template_path);
if (template_path[template_len-1] != '/') {
template_path[template_len++] = '/';
template_path[template_len] = 0;

View File

@@ -1907,13 +1907,19 @@ static void show_commit(struct commit *commit, void *data)
commit->object.flags |= OBJECT_ADDED;
}
static void show_object(struct object_array_entry *p, void *data)
static void show_object(struct object *obj, const struct name_path *path, const char *last)
{
add_preferred_base_object(p->name);
add_object_entry(p->item->sha1, p->item->type, p->name, 0);
p->item->flags |= OBJECT_ADDED;
free((char *)p->name);
p->name = NULL;
char *name = path_name(path, last);
add_preferred_base_object(name);
add_object_entry(obj->sha1, obj->type, name, 0);
obj->flags |= OBJECT_ADDED;
/*
* We will have generated the hash from the name,
* but not saved a pointer to it - we can free it
*/
free((char *)name);
}
static void show_edge(struct commit *commit)

View File

@@ -156,27 +156,29 @@ static void finish_commit(struct commit *commit, void *data)
commit->buffer = NULL;
}
static void finish_object(struct object_array_entry *p, void *data)
static void finish_object(struct object *obj, const struct name_path *path, const char *name)
{
if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
die("missing blob object '%s'", sha1_to_hex(obj->sha1));
}
static void show_object(struct object_array_entry *p, void *data)
static void show_object(struct object *obj, const struct name_path *path, const char *component)
{
char *name = path_name(path, component);
/* An object with name "foo\n0000000..." can be used to
* confuse downstream "git pack-objects" very badly.
*/
const char *ep = strchr(p->name, '\n');
const char *ep = strchr(name, '\n');
finish_object(p, data);
finish_object(obj, path, name);
if (ep) {
printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
(int) (ep - p->name),
p->name);
printf("%s %.*s\n", sha1_to_hex(obj->sha1),
(int) (ep - name),
name);
}
else
printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name);
printf("%s %s\n", sha1_to_hex(obj->sha1), name);
free(name);
}
static void show_edge(struct commit *commit)

View File

@@ -26,6 +26,8 @@ static int show_type = NORMAL;
#define SHOW_SYMBOLIC_FULL 2
static int symbolic;
static int abbrev;
static int abbrev_ref;
static int abbrev_ref_strict;
static int output_sq;
/*
@@ -109,8 +111,8 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
return;
def = NULL;
if (symbolic && name) {
if (symbolic == SHOW_SYMBOLIC_FULL) {
if ((symbolic || abbrev_ref) && name) {
if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
unsigned char discard[20];
char *full;
@@ -125,6 +127,9 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
*/
break;
case 1: /* happy */
if (abbrev_ref)
full = shorten_unambiguous_ref(full,
abbrev_ref_strict);
show_with_type(type, full);
break;
default: /* ambiguous */
@@ -506,6 +511,20 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
symbolic = SHOW_SYMBOLIC_FULL;
continue;
}
if (!prefixcmp(arg, "--abbrev-ref") &&
(!arg[12] || arg[12] == '=')) {
abbrev_ref = 1;
abbrev_ref_strict = warn_ambiguous_refs;
if (arg[12] == '=') {
if (!strcmp(arg + 13, "strict"))
abbrev_ref_strict = 1;
else if (!strcmp(arg + 13, "loose"))
abbrev_ref_strict = 0;
else
die("unknown mode for %s", arg);
}
continue;
}
if (!strcmp(arg, "--all")) {
for_each_ref(show_reference, NULL);
continue;

View File

@@ -24,7 +24,7 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix)
* git archive --format-tar --prefix=basedir tree-ish
*/
int i;
const char **nargv = xcalloc(sizeof(*nargv), argc + 2);
const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
char *basedir_arg;
int nargc = 0;
@@ -36,6 +36,13 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix)
argv++;
argc--;
}
/*
* Because it's just a compatibility wrapper, tar-tree supports only
* the old behaviour of reading attributes from the work tree.
*/
nargv[nargc++] = "--worktree-attributes";
switch (argc) {
default:
usage(tar_tree_usage);

View File

@@ -11,6 +11,7 @@
#define GIT_COLOR_GREEN "\033[32m"
#define GIT_COLOR_YELLOW "\033[33m"
#define GIT_COLOR_BLUE "\033[34m"
#define GIT_COLOR_MAGENTA "\033[35m"
#define GIT_COLOR_CYAN "\033[36m"
#define GIT_COLOR_BG_RED "\033[41m"

View File

@@ -33,6 +33,7 @@ git-diff mainporcelain common
git-diff-files plumbinginterrogators
git-diff-index plumbinginterrogators
git-diff-tree plumbinginterrogators
git-difftool ancillaryinterrogators
git-fast-export ancillarymanipulators
git-fast-import ancillarymanipulators
git-fetch mainporcelain common

View File

@@ -51,7 +51,7 @@ static char *parse_value(void)
for (;;) {
int c = get_next_char();
if (len >= sizeof(value))
if (len >= sizeof(value) - 1)
return NULL;
if (c == '\n') {
if (quote)
@@ -331,9 +331,9 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
return 1;
if (!*value)
return 0;
if (!strcasecmp(value, "true") || !strcasecmp(value, "yes"))
if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on"))
return 1;
if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off"))
return 0;
*is_bool = 0;
return git_config_int(name, value);

View File

@@ -910,6 +910,26 @@ _git_diff ()
__git_complete_file
}
__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
tkdiff vimdiff gvimdiff xxdiff
"
_git_difftool ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}"
return
;;
--*)
__gitcomp "--tool="
return
;;
esac
COMPREPLY=()
}
__git_fetch_options="
--quiet --verbose --append --upload-pack --force --keep --depth=
--tags --no-tags
@@ -1172,10 +1192,7 @@ _git_mergetool ()
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
__gitcomp "
kdiff3 tkdiff meld xxdiff emerge
vimdiff gvimdiff ecmerge opendiff
" "" "${cur##--tool=}"
__gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}"
return
;;
--*)
@@ -1901,6 +1918,7 @@ _git ()
config) _git_config ;;
describe) _git_describe ;;
diff) _git_diff ;;
difftool) _git_difftool ;;
fetch) _git_fetch ;;
format-patch) _git_format_patch ;;
fsck) _git_fsck ;;

View File

@@ -1,249 +0,0 @@
#!/bin/sh
# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff,
# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools.
# This script is typically launched by using the 'git difftool'
# convenience command.
#
# Copyright (c) 2009 David Aguilar
# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt.
should_prompt () {
! test -n "$GIT_DIFFTOOL_NO_PROMPT"
}
# Should we keep the backup .orig file?
keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
keep_backup () {
test "$keep_backup_mode" = "true"
}
# This function manages the backup .orig file.
# A backup $MERGED.orig file is created if changes are detected.
cleanup_temp_files () {
if test -n "$MERGED"; then
if keep_backup && test "$MERGED" -nt "$BACKUP"; then
test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
else
rm -f -- "$BACKUP"
fi
fi
}
# This is called when users Ctrl-C out of git-difftool-helper
sigint_handler () {
cleanup_temp_files
exit 1
}
# This function prepares temporary files and launches the appropriate
# merge tool.
launch_merge_tool () {
# Merged is the filename as it appears in the work tree
# Local is the contents of a/filename
# Remote is the contents of b/filename
# Custom merge tool commands might use $BASE so we provide it
MERGED="$1"
LOCAL="$2"
REMOTE="$3"
BASE="$1"
ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
BACKUP="$MERGED.BACKUP.$ext"
# Create and ensure that we clean up $BACKUP
test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
trap sigint_handler INT
# $LOCAL and $REMOTE are temporary files so prompt
# the user with the real $MERGED name before launching $merge_tool.
if should_prompt; then
printf "\nViewing: '$MERGED'\n"
printf "Hit return to launch '%s': " "$merge_tool"
read ans
fi
# Run the appropriate merge tool command
case "$merge_tool" in
kdiff3)
basename=$(basename "$MERGED")
"$merge_tool_path" --auto \
--L1 "$basename (A)" \
--L2 "$basename (B)" \
-o "$MERGED" "$LOCAL" "$REMOTE" \
> /dev/null 2>&1
;;
kompare)
"$merge_tool_path" "$LOCAL" "$REMOTE"
;;
tkdiff)
"$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
;;
meld)
"$merge_tool_path" "$LOCAL" "$REMOTE"
;;
vimdiff)
"$merge_tool_path" -c "wincmd l" "$LOCAL" "$REMOTE"
;;
gvimdiff)
"$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$REMOTE"
;;
xxdiff)
"$merge_tool_path" \
-X \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" \
"$LOCAL" "$REMOTE"
;;
opendiff)
"$merge_tool_path" "$LOCAL" "$REMOTE" \
-merge "$MERGED" | cat
;;
ecmerge)
"$merge_tool_path" "$LOCAL" "$REMOTE" \
--default --mode=merge2 --to="$MERGED"
;;
emerge)
"$merge_tool_path" -f emerge-files-command \
"$LOCAL" "$REMOTE" "$(basename "$MERGED")"
;;
*)
if test -n "$merge_tool_cmd"; then
( eval $merge_tool_cmd )
fi
;;
esac
cleanup_temp_files
}
# Verifies that (difftool|mergetool).<tool>.cmd exists
valid_custom_tool() {
merge_tool_cmd="$(git config difftool.$1.cmd)"
test -z "$merge_tool_cmd" &&
merge_tool_cmd="$(git config mergetool.$1.cmd)"
test -n "$merge_tool_cmd"
}
# Verifies that the chosen merge tool is properly setup.
# Built-in merge tools are always valid.
valid_tool() {
case "$1" in
kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
;; # happy
*)
if ! valid_custom_tool "$1"
then
return 1
fi
;;
esac
}
# Sets up the merge_tool_path variable.
# This handles the difftool.<tool>.path configuration.
# This also falls back to mergetool defaults.
init_merge_tool_path() {
merge_tool_path=$(git config difftool."$1".path)
test -z "$merge_tool_path" &&
merge_tool_path=$(git config mergetool."$1".path)
if test -z "$merge_tool_path"; then
case "$1" in
emerge)
merge_tool_path=emacs
;;
*)
merge_tool_path="$1"
;;
esac
fi
}
# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
# If merge tool was not specified then use the diff.tool
# configuration variable. If that's invalid then reset merge_tool.
# Fallback to merge.tool.
if test -z "$merge_tool"; then
merge_tool=$(git config diff.tool)
test -z "$merge_tool" &&
merge_tool=$(git config merge.tool)
if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
echo >&2 "git config option diff.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
unset merge_tool
fi
fi
# Try to guess an appropriate merge tool if no tool has been set.
if test -z "$merge_tool"; then
# We have a $DISPLAY so try some common UNIX merge tools
if test -n "$DISPLAY"; then
# If gnome then prefer meld, otherwise, prefer kdiff3 or kompare
if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff"
else
merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff"
fi
fi
if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
# $EDITOR is emacs so add emerge as a candidate
merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
# $EDITOR is vim so add vimdiff as a candidate
merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
else
merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
fi
echo "merge tool candidates: $merge_tool_candidates"
# Loop over each candidate and stop when a valid merge tool is found.
for i in $merge_tool_candidates
do
init_merge_tool_path $i
if type "$merge_tool_path" > /dev/null 2>&1; then
merge_tool=$i
break
fi
done
if test -z "$merge_tool" ; then
echo "No known merge resolution program available."
exit 1
fi
else
# A merge tool has been set, so verify that it's valid.
if ! valid_tool "$merge_tool"; then
echo >&2 "Unknown merge tool $merge_tool"
exit 1
fi
init_merge_tool_path "$merge_tool"
if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
exit 1
fi
fi
# Launch the merge tool on each path provided by 'git diff'
while test $# -gt 6
do
launch_merge_tool "$1" "$2" "$5"
shift 7
done

View File

@@ -620,11 +620,12 @@ sub parse_diff {
if ($diff_use_color) {
@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);
}
my (@hunk) = { TEXT => [], DISPLAY => [] };
my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
for (my $i = 0; $i < @diff; $i++) {
if ($diff[$i] =~ /^@@ /) {
push @hunk, { TEXT => [], DISPLAY => [] };
push @hunk, { TEXT => [], DISPLAY => [],
TYPE => 'hunk' };
}
push @{$hunk[-1]{TEXT}}, $diff[$i];
push @{$hunk[-1]{DISPLAY}},
@@ -636,8 +637,8 @@ sub parse_diff {
sub parse_diff_header {
my $src = shift;
my $head = { TEXT => [], DISPLAY => [] };
my $mode = { TEXT => [], DISPLAY => [] };
my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ?
@@ -684,6 +685,7 @@ sub split_hunk {
my $this = +{
TEXT => [],
DISPLAY => [],
TYPE => 'hunk',
OLD => $o_ofs,
NEW => $n_ofs,
OCNT => 0,
@@ -873,7 +875,11 @@ sub edit_hunk_loop {
if (!defined $text) {
return undef;
}
my $newhunk = { TEXT => $text, USE => 1 };
my $newhunk = {
TEXT => $text,
TYPE => $hunk->[$ix]->{TYPE},
USE => 1
};
if (diff_applies($head,
@{$hunk}[0..$ix-1],
$newhunk,
@@ -894,6 +900,7 @@ sub help_patch_cmd {
print colored $help_color, <<\EOF ;
y - stage this hunk
n - do not stage this hunk
q - quit, do not stage this hunk nor any of the remaining ones
a - stage this and all the remaining hunks in the file
d - do not stage this hunk nor any of the remaining hunks in the file
g - select a hunk to go to
@@ -930,7 +937,7 @@ sub patch_update_cmd {
@mods);
}
for (@them) {
patch_update_file($_->{VALUE});
return 0 if patch_update_file($_->{VALUE});
}
}
@@ -976,6 +983,7 @@ sub display_hunks {
}
sub patch_update_file {
my $quit = 0;
my ($ix, $num);
my $path = shift;
my ($head, @hunk) = parse_diff($path);
@@ -985,32 +993,7 @@ sub patch_update_file {
}
if (@{$mode->{TEXT}}) {
while (1) {
print @{$mode->{DISPLAY}};
print colored $prompt_color,
"Stage mode change [y/n/a/d/?]? ";
my $line = prompt_single_character;
if ($line =~ /^y/i) {
$mode->{USE} = 1;
last;
}
elsif ($line =~ /^n/i) {
$mode->{USE} = 0;
last;
}
elsif ($line =~ /^a/i) {
$_->{USE} = 1 foreach ($mode, @hunk);
last;
}
elsif ($line =~ /^d/i) {
$_->{USE} = 0 foreach ($mode, @hunk);
last;
}
else {
help_patch_cmd('');
next;
}
}
unshift @hunk, $mode;
}
$num = scalar @hunk;
@@ -1054,14 +1037,19 @@ sub patch_update_file {
}
last if (!$undecided);
if (hunk_splittable($hunk[$ix]{TEXT})) {
if ($hunk[$ix]{TYPE} eq 'hunk' &&
hunk_splittable($hunk[$ix]{TEXT})) {
$other .= ',s';
}
$other .= ',e';
if ($hunk[$ix]{TYPE} eq 'hunk') {
$other .= ',e';
}
for (@{$hunk[$ix]{DISPLAY}}) {
print;
}
print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? ";
print colored $prompt_color, 'Stage ',
($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),
" [y,n,a,d,/$other,?]? ";
my $line = prompt_single_character;
if ($line) {
if ($line =~ /^y/i) {
@@ -1113,6 +1101,16 @@ sub patch_update_file {
}
next;
}
elsif ($line =~ /^q/i) {
while ($ix < $num) {
if (!defined $hunk[$ix]{USE}) {
$hunk[$ix]{USE} = 0;
}
$ix++;
}
$quit = 1;
next;
}
elsif ($line =~ m|^/(.*)|) {
my $regex = $1;
if ($1 eq "") {
@@ -1193,7 +1191,7 @@ sub patch_update_file {
$num = scalar @hunk;
next;
}
elsif ($line =~ /^e/) {
elsif ($other =~ /e/ && $line =~ /^e/) {
my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
if (defined $newhunk) {
splice @hunk, $ix, 1, $newhunk;
@@ -1214,9 +1212,6 @@ sub patch_update_file {
my $n_lofs = 0;
my @result = ();
if ($mode->{USE}) {
push @result, @{$mode->{TEXT}};
}
for (@hunk) {
if ($_->{USE}) {
push @result, @{$_->{TEXT}};
@@ -1239,6 +1234,7 @@ sub patch_update_file {
}
print "\n";
return $quit;
}
sub diff_cmd {

View File

@@ -36,6 +36,13 @@ cd_to_toplevel
git var GIT_COMMITTER_IDENT >/dev/null ||
die "You need to set your committer info first"
if git rev-parse --verify -q HEAD >/dev/null
then
HAS_HEAD=yes
else
HAS_HEAD=
fi
sq () {
for sqarg
do
@@ -290,16 +297,26 @@ else
: >"$dotest/rebasing"
else
: >"$dotest/applying"
git update-ref ORIG_HEAD HEAD
if test -n "$HAS_HEAD"
then
git update-ref ORIG_HEAD HEAD
else
git update-ref -d ORIG_HEAD >/dev/null 2>&1
fi
fi
fi
case "$resolved" in
'')
files=$(git diff-index --cached --name-only HEAD --) || exit
case "$HAS_HEAD" in
'')
files=$(git ls-files) ;;
?*)
files=$(git diff-index --cached --name-only HEAD --) ;;
esac || exit
if test "$files"
then
: >"$dotest/dirtyindex"
test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
die "Dirty index: cannot apply patches (dirty: $files)"
fi
esac
@@ -541,18 +558,20 @@ do
fi
tree=$(git write-tree) &&
parent=$(git rev-parse --verify HEAD) &&
commit=$(
if test -n "$ignore_date"
then
GIT_AUTHOR_DATE=
fi
parent=$(git rev-parse --verify -q HEAD) ||
echo >&2 "applying to an empty history"
if test -n "$committer_date_is_author_date"
then
GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
export GIT_COMMITTER_DATE
fi &&
git commit-tree $tree -p $parent <"$dotest/final-commit"
git commit-tree $tree ${parent:+-p $parent} <"$dotest/final-commit"
) &&
git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
stop_here $this

59
git-difftool--helper.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/sh
# git-difftool--helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
# This script is typically launched by using the 'git difftool'
# convenience command.
#
# Copyright (c) 2009 David Aguilar
# Load common functions from git-mergetool--lib
TOOL_MODE=diff
. git-mergetool--lib
# difftool.prompt controls the default prompt/no-prompt behavior
# and is overridden with $GIT_DIFFTOOL*_PROMPT.
should_prompt () {
prompt=$(git config --bool difftool.prompt || echo true)
if test "$prompt" = true; then
test -z "$GIT_DIFFTOOL_NO_PROMPT"
else
test -n "$GIT_DIFFTOOL_PROMPT"
fi
}
# Sets up shell variables and runs a merge tool
launch_merge_tool () {
# Merged is the filename as it appears in the work tree
# Local is the contents of a/filename
# Remote is the contents of b/filename
# Custom merge tool commands might use $BASE so we provide it
MERGED="$1"
LOCAL="$2"
REMOTE="$3"
BASE="$1"
# $LOCAL and $REMOTE are temporary files so prompt
# the user with the real $MERGED name before launching $merge_tool.
if should_prompt; then
printf "\nViewing: '$MERGED'\n"
printf "Hit return to launch '%s': " "$merge_tool"
read ans
fi
# Run the appropriate merge tool command
run_merge_tool "$merge_tool"
}
# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
if test -z "$merge_tool"; then
merge_tool="$(get_merge_tool)" || exit
fi
# Launch the merge tool on each path provided by 'git diff'
while test $# -gt 6
do
launch_merge_tool "$1" "$2" "$5"
shift 7
done

View File

@@ -2,9 +2,12 @@
# Copyright (c) 2009 David Aguilar
#
# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
# git-difftool-helper script. This script exports
# GIT_EXTERNAL_DIFF and GIT_PAGER for use by git, and
# GIT_DIFFTOOL_NO_PROMPT and GIT_DIFF_TOOL for use by git-difftool-helper.
# git-difftool--helper script.
#
# This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL
# are exported for use by git-difftool--helper.
#
# Any arguments that are unknown to this script are forwarded to 'git diff'.
use strict;
@@ -18,7 +21,7 @@ my $DIR = abs_path(dirname($0));
sub usage
{
print << 'USAGE';
usage: git difftool [--tool=<tool>] [--no-prompt] ["git diff" options]
usage: git difftool [--tool=<tool>] [-y|--no-prompt] ["git diff" options]
USAGE
exit 1;
}
@@ -27,13 +30,16 @@ sub setup_environment
{
$ENV{PATH} = "$DIR:$ENV{PATH}";
$ENV{GIT_PAGER} = '';
$ENV{GIT_EXTERNAL_DIFF} = 'git-difftool-helper';
$ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
}
sub exe
{
my $exe = shift;
return defined $ENV{COMSPEC} ? "$exe.exe" : $exe;
if ($^O eq 'MSWin32' || $^O eq 'msys') {
return "$exe.exe";
}
return $exe;
}
sub generate_command
@@ -47,7 +53,7 @@ sub generate_command
$skip_next = 0;
next;
}
if ($arg eq '-t' or $arg eq '--tool') {
if ($arg eq '-t' || $arg eq '--tool') {
usage() if $#ARGV <= $idx;
$ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
$skip_next = 1;
@@ -57,11 +63,17 @@ sub generate_command
$ENV{GIT_DIFF_TOOL} = substr($arg, 7);
next;
}
if ($arg eq '--no-prompt') {
if ($arg eq '-y' || $arg eq '--no-prompt') {
$ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
delete $ENV{GIT_DIFFTOOL_PROMPT};
next;
}
if ($arg eq '-h' or $arg eq '--help') {
if ($arg eq '--prompt') {
$ENV{GIT_DIFFTOOL_PROMPT} = 'true';
delete $ENV{GIT_DIFFTOOL_NO_PROMPT};
next;
}
if ($arg eq '-h' || $arg eq '--help') {
usage();
}
push @command, $arg;

385
git-mergetool--lib.sh Normal file
View File

@@ -0,0 +1,385 @@
# git-mergetool--lib is a library for common merge tool functions
diff_mode() {
test "$TOOL_MODE" = diff
}
merge_mode() {
test "$TOOL_MODE" = merge
}
translate_merge_tool_path () {
case "$1" in
vimdiff)
echo vim
;;
gvimdiff)
echo gvim
;;
emerge)
echo emacs
;;
*)
echo "$1"
;;
esac
}
check_unchanged () {
if test "$MERGED" -nt "$BACKUP"; then
status=0
else
while true; do
echo "$MERGED seems unchanged."
printf "Was the merge successful? [y/n] "
read answer < /dev/tty
case "$answer" in
y*|Y*) status=0; break ;;
n*|N*) status=1; break ;;
esac
done
fi
}
valid_tool () {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | \
emerge | vimdiff | gvimdiff | ecmerge | diffuse)
;; # happy
tortoisemerge)
if ! merge_mode; then
return 1
fi
;;
kompare)
if ! diff_mode; then
return 1
fi
;;
*)
if test -z "$(get_merge_tool_cmd "$1")"; then
return 1
fi
;;
esac
}
get_merge_tool_cmd () {
# Prints the custom command for a merge tool
if test -n "$1"; then
merge_tool="$1"
else
merge_tool="$(get_merge_tool)"
fi
if diff_mode; then
echo "$(git config difftool.$merge_tool.cmd ||
git config mergetool.$merge_tool.cmd)"
else
echo "$(git config mergetool.$merge_tool.cmd)"
fi
}
run_merge_tool () {
merge_tool_path="$(get_merge_tool_path "$1")" || exit
base_present="$2"
status=0
case "$1" in
kdiff3)
if merge_mode; then
if $base_present; then
("$merge_tool_path" --auto \
--L1 "$MERGED (Base)" \
--L2 "$MERGED (Local)" \
--L3 "$MERGED (Remote)" \
-o "$MERGED" \
"$BASE" "$LOCAL" "$REMOTE" \
> /dev/null 2>&1)
else
("$merge_tool_path" --auto \
--L1 "$MERGED (Local)" \
--L2 "$MERGED (Remote)" \
-o "$MERGED" \
"$LOCAL" "$REMOTE" \
> /dev/null 2>&1)
fi
status=$?
else
("$merge_tool_path" --auto \
--L1 "$MERGED (A)" \
--L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
> /dev/null 2>&1)
fi
;;
kompare)
"$merge_tool_path" "$LOCAL" "$REMOTE"
;;
tkdiff)
if merge_mode; then
if $base_present; then
"$merge_tool_path" -a "$BASE" \
-o "$MERGED" "$LOCAL" "$REMOTE"
else
"$merge_tool_path" \
-o "$MERGED" "$LOCAL" "$REMOTE"
fi
status=$?
else
"$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
meld)
if merge_mode; then
touch "$BACKUP"
"$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
else
"$merge_tool_path" "$LOCAL" "$REMOTE"
fi
;;
diffuse)
if merge_mode; then
touch "$BACKUP"
if $base_present; then
"$merge_tool_path" \
"$LOCAL" "$MERGED" "$REMOTE" \
"$BASE" | cat
else
"$merge_tool_path" \
"$LOCAL" "$MERGED" "$REMOTE" | cat
fi
check_unchanged
else
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
fi
;;
vimdiff)
if merge_mode; then
touch "$BACKUP"
"$merge_tool_path" -d -c "wincmd l" \
"$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
else
"$merge_tool_path" -d -c "wincmd l" \
"$LOCAL" "$REMOTE"
fi
;;
gvimdiff)
if merge_mode; then
touch "$BACKUP"
"$merge_tool_path" -d -c "wincmd l" -f \
"$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
else
"$merge_tool_path" -d -c "wincmd l" -f \
"$LOCAL" "$REMOTE"
fi
;;
xxdiff)
if merge_mode; then
touch "$BACKUP"
if $base_present; then
"$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" \
"$LOCAL" "$BASE" "$REMOTE"
else
"$merge_tool_path" -X $extra \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" \
"$LOCAL" "$REMOTE"
fi
check_unchanged
else
"$merge_tool_path" \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
"$LOCAL" "$REMOTE"
fi
;;
opendiff)
if merge_mode; then
touch "$BACKUP"
if $base_present; then
"$merge_tool_path" "$LOCAL" "$REMOTE" \
-ancestor "$BASE" \
-merge "$MERGED" | cat
else
"$merge_tool_path" "$LOCAL" "$REMOTE" \
-merge "$MERGED" | cat
fi
check_unchanged
else
"$merge_tool_path" "$LOCAL" "$REMOTE" | cat
fi
;;
ecmerge)
if merge_mode; then
touch "$BACKUP"
if $base_present; then
"$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
--default --mode=merge3 --to="$MERGED"
else
"$merge_tool_path" "$LOCAL" "$REMOTE" \
--default --mode=merge2 --to="$MERGED"
fi
check_unchanged
else
"$merge_tool_path" "$LOCAL" "$REMOTE" \
--default --mode=merge2 --to="$MERGED"
fi
;;
emerge)
if merge_mode; then
if $base_present; then
"$merge_tool_path" \
-f emerge-files-with-ancestor-command \
"$LOCAL" "$REMOTE" "$BASE" \
"$(basename "$MERGED")"
else
"$merge_tool_path" \
-f emerge-files-command \
"$LOCAL" "$REMOTE" \
"$(basename "$MERGED")"
fi
status=$?
else
"$merge_tool_path" -f emerge-files-command \
"$LOCAL" "$REMOTE" "$(basename "$MERGED")"
fi
;;
tortoisemerge)
if $base_present; then
touch "$BACKUP"
"$merge_tool_path" \
-base:"$BASE" -mine:"$LOCAL" \
-theirs:"$REMOTE" -merged:"$MERGED"
check_unchanged
else
echo "TortoiseMerge cannot be used without a base" 1>&2
status=1
fi
;;
*)
merge_tool_cmd="$(get_merge_tool_cmd "$1")"
if test -z "$merge_tool_cmd"; then
if merge_mode; then
status=1
fi
break
fi
if merge_mode; then
trust_exit_code="$(git config --bool \
mergetool."$1".trustExitCode || echo false)"
if test "$trust_exit_code" = "false"; then
touch "$BACKUP"
( eval $merge_tool_cmd )
check_unchanged
else
( eval $merge_tool_cmd )
status=$?
fi
else
( eval $merge_tool_cmd )
fi
;;
esac
return $status
}
guess_merge_tool () {
if merge_mode; then
tools="tortoisemerge"
else
tools="kompare"
fi
if test -n "$DISPLAY"; then
if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
else
tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
fi
tools="$tools gvimdiff diffuse ecmerge"
fi
if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then
# $EDITOR is emacs so add emerge as a candidate
tools="$tools emerge vimdiff"
elif echo "${VISUAL:-$EDITOR}" | grep vim > /dev/null 2>&1; then
# $EDITOR is vim so add vimdiff as a candidate
tools="$tools vimdiff emerge"
else
tools="$tools emerge vimdiff"
fi
echo >&2 "merge tool candidates: $tools"
# Loop over each candidate and stop when a valid merge tool is found.
for i in $tools
do
merge_tool_path="$(translate_merge_tool_path "$i")"
if type "$merge_tool_path" > /dev/null 2>&1; then
echo "$i"
return 0
fi
done
echo >&2 "No known merge resolution program available."
return 1
}
get_configured_merge_tool () {
# Diff mode first tries diff.tool and falls back to merge.tool.
# Merge mode only checks merge.tool
if diff_mode; then
merge_tool=$(git config diff.tool || git config merge.tool)
else
merge_tool=$(git config merge.tool)
fi
if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
return 1
fi
echo "$merge_tool"
}
get_merge_tool_path () {
# A merge tool has been set, so verify that it's valid.
if test -n "$1"; then
merge_tool="$1"
else
merge_tool="$(get_merge_tool)"
fi
if ! valid_tool "$merge_tool"; then
echo >&2 "Unknown merge tool $merge_tool"
exit 1
fi
if diff_mode; then
merge_tool_path=$(git config difftool."$merge_tool".path ||
git config mergetool."$merge_tool".path)
else
merge_tool_path=$(git config mergetool."$merge_tool".path)
fi
if test -z "$merge_tool_path"; then
merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
fi
if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
! type "$merge_tool_path" > /dev/null 2>&1; then
echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
"'$merge_tool_path'"
exit 1
fi
echo "$merge_tool_path"
}
get_merge_tool () {
# Check if a merge tool has been configured
merge_tool=$(get_configured_merge_tool)
# Try to guess an appropriate merge tool if no tool has been set.
if test -z "$merge_tool"; then
merge_tool="$(guess_merge_tool)" || exit
fi
echo "$merge_tool"
}

View File

@@ -11,7 +11,9 @@
USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
TOOL_MODE=merge
. git-sh-setup
. git-mergetool--lib
require_work_tree
# Returns true if the mode reflects a symlink
@@ -110,22 +112,6 @@ resolve_deleted_merge () {
done
}
check_unchanged () {
if test "$MERGED" -nt "$BACKUP" ; then
status=0;
else
while true; do
echo "$MERGED seems unchanged."
printf "Was the merge successful? [y/n] "
read answer < /dev/tty
case "$answer" in
y*|Y*) status=0; break ;;
n*|N*) status=1; break ;;
esac
done
fi
}
checkout_staged_file () {
tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ')
@@ -137,7 +123,7 @@ checkout_staged_file () {
merge_file () {
MERGED="$1"
f=`git ls-files -u -- "$MERGED"`
f=$(git ls-files -u -- "$MERGED")
if test -z "$f" ; then
if test ! -f "$MERGED" ; then
echo "$MERGED: file not found"
@@ -156,9 +142,9 @@ merge_file () {
mv -- "$MERGED" "$BACKUP"
cp -- "$BACKUP" "$MERGED"
base_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}'`
local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'`
remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'`
base_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
local_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}')
remote_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}')
base_present && checkout_staged_file 1 "$MERGED" "$BASE"
local_present && checkout_staged_file 2 "$MERGED" "$LOCAL"
@@ -188,97 +174,13 @@ merge_file () {
read ans
fi
case "$merge_tool" in
kdiff3)
if base_present ; then
("$merge_tool_path" --auto --L1 "$MERGED (Base)" --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" \
-o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
else
("$merge_tool_path" --auto --L1 "$MERGED (Local)" --L2 "$MERGED (Remote)" \
-o "$MERGED" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
fi
status=$?
;;
tkdiff)
if base_present ; then
"$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"
else
"$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
fi
status=$?
;;
meld)
touch "$BACKUP"
"$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
;;
vimdiff)
touch "$BACKUP"
"$merge_tool_path" -c "wincmd l" "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
;;
gvimdiff)
touch "$BACKUP"
"$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$MERGED" "$REMOTE"
check_unchanged
;;
xxdiff)
touch "$BACKUP"
if base_present ; then
"$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
else
"$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" "$LOCAL" "$REMOTE"
fi
check_unchanged
;;
opendiff)
touch "$BACKUP"
if base_present; then
"$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED" | cat
else
"$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED" | cat
fi
check_unchanged
;;
ecmerge)
touch "$BACKUP"
if base_present; then
"$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"
else
"$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"
fi
check_unchanged
;;
emerge)
if base_present ; then
"$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$MERGED")"
else
"$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
fi
status=$?
;;
*)
if test -n "$merge_tool_cmd"; then
if test "$merge_tool_trust_exit_code" = "false"; then
touch "$BACKUP"
( eval $merge_tool_cmd )
check_unchanged
else
( eval $merge_tool_cmd )
status=$?
fi
fi
;;
esac
if test "$status" -ne 0; then
if base_present; then
present=true
else
present=false
fi
if ! run_merge_tool "$merge_tool" "$present"; then
echo "merge of $MERGED failed" 1>&2
mv -- "$BACKUP" "$MERGED"
@@ -308,7 +210,7 @@ do
-t|--tool*)
case "$#,$1" in
*,*=*)
merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'`
merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
;;
1,*)
usage ;;
@@ -337,38 +239,6 @@ do
shift
done
valid_custom_tool()
{
merge_tool_cmd="$(git config mergetool.$1.cmd)"
test -n "$merge_tool_cmd"
}
valid_tool() {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
;; # happy
*)
if ! valid_custom_tool "$1"; then
return 1
fi
;;
esac
}
init_merge_tool_path() {
merge_tool_path=`git config mergetool.$1.path`
if test -z "$merge_tool_path" ; then
case "$1" in
emerge)
merge_tool_path=emacs
;;
*)
merge_tool_path=$1
;;
esac
fi
}
prompt_after_failed_merge() {
while true; do
printf "Continue merging other unresolved paths (y/n) ? "
@@ -387,67 +257,16 @@ prompt_after_failed_merge() {
}
if test -z "$merge_tool"; then
merge_tool=`git config merge.tool`
if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
unset merge_tool
fi
fi
if test -z "$merge_tool" ; then
if test -n "$DISPLAY"; then
if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
merge_tool_candidates="meld kdiff3 tkdiff xxdiff gvimdiff"
else
merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
fi
fi
if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff"
elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge"
else
merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
fi
echo "merge tool candidates: $merge_tool_candidates"
for i in $merge_tool_candidates; do
init_merge_tool_path $i
if type "$merge_tool_path" > /dev/null 2>&1; then
merge_tool=$i
break
fi
done
if test -z "$merge_tool" ; then
echo "No known merge resolution program available."
exit 1
fi
else
if ! valid_tool "$merge_tool"; then
echo >&2 "Unknown merge_tool $merge_tool"
exit 1
fi
init_merge_tool_path "$merge_tool"
merge_keep_backup="$(git config --bool merge.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
exit 1
fi
if ! test -z "$merge_tool_cmd"; then
merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
fi
merge_tool=$(get_merge_tool "$merge_tool") || exit
fi
merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
last_status=0
rollup_status=0
if test $# -eq 0 ; then
files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u`
files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u)
if test -z "$files" ; then
echo "No files need merging"
exit 0

246
graph.c
View File

@@ -1,5 +1,6 @@
#include "cache.h"
#include "commit.h"
#include "color.h"
#include "graph.h"
#include "diff.h"
#include "revision.h"
@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
/*
* TODO:
* - Add colors to the graph.
* Pick a color for each column, and print all characters
* in that column with the specified color.
*
* - Limit the number of columns, similar to the way gitk does.
* If we reach more than a specified number of columns, omit
* sections of some columns.
@@ -72,9 +69,10 @@ struct column {
*/
struct commit *commit;
/*
* XXX: Once we add support for colors, struct column could also
* contain the color of its branch line.
* The color to (optionally) print this column in. This is an
* index into column_colors.
*/
unsigned short color;
};
enum graph_state {
@@ -86,6 +84,41 @@ enum graph_state {
GRAPH_COLLAPSING
};
/*
* The list of available column colors.
*/
static char column_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RED,
GIT_COLOR_GREEN,
GIT_COLOR_YELLOW,
GIT_COLOR_BLUE,
GIT_COLOR_MAGENTA,
GIT_COLOR_CYAN,
GIT_COLOR_BOLD GIT_COLOR_RED,
GIT_COLOR_BOLD GIT_COLOR_GREEN,
GIT_COLOR_BOLD GIT_COLOR_YELLOW,
GIT_COLOR_BOLD GIT_COLOR_BLUE,
GIT_COLOR_BOLD GIT_COLOR_MAGENTA,
GIT_COLOR_BOLD GIT_COLOR_CYAN,
};
#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
static const char *column_get_color_code(const struct column *c)
{
return column_colors[c->color];
}
static void strbuf_write_column(struct strbuf *sb, const struct column *c,
char col_char)
{
if (c->color < COLUMN_COLORS_MAX)
strbuf_addstr(sb, column_get_color_code(c));
strbuf_addch(sb, col_char);
if (c->color < COLUMN_COLORS_MAX)
strbuf_addstr(sb, GIT_COLOR_RESET);
}
struct git_graph {
/*
* The commit currently being processed
@@ -185,6 +218,11 @@ struct git_graph {
* temporary array each time we have to output a collapsing line.
*/
int *new_mapping;
/*
* The current default column color being used. This is
* stored as an index into the array column_colors.
*/
unsigned short default_column_color;
};
struct git_graph *graph_init(struct rev_info *opt)
@@ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt)
graph->num_columns = 0;
graph->num_new_columns = 0;
graph->mapping_size = 0;
graph->default_column_color = 0;
/*
* Allocate a reasonably large default number of columns
@@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
return next_interesting_parent(graph, parents);
}
static unsigned short graph_get_current_column_color(const struct git_graph *graph)
{
if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
return COLUMN_COLORS_MAX;
return graph->default_column_color;
}
/*
* Update the graph's default column color.
*/
static void graph_increment_column_color(struct git_graph *graph)
{
graph->default_column_color = (graph->default_column_color + 1) %
COLUMN_COLORS_MAX;
}
static unsigned short graph_find_commit_color(const struct git_graph *graph,
const struct commit *commit)
{
int i;
for (i = 0; i < graph->num_columns; i++) {
if (graph->columns[i].commit == commit)
return graph->columns[i].color;
}
return graph_get_current_column_color(graph);
}
static void graph_insert_into_new_columns(struct git_graph *graph,
struct commit *commit,
int *mapping_index)
@@ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
* This commit isn't already in new_columns. Add it.
*/
graph->new_columns[graph->num_new_columns].commit = commit;
graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);
graph->mapping[*mapping_index] = graph->num_new_columns;
*mapping_index += 2;
graph->num_new_columns++;
@@ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph)
for (parent = first_interesting_parent(graph);
parent;
parent = next_interesting_parent(graph, parent)) {
/*
* If this is a merge increment the current
* color.
*/
if (graph->num_parents > 1)
graph_increment_column_color(graph);
graph_insert_into_new_columns(graph,
parent->item,
&mapping_idx);
@@ -560,7 +633,8 @@ static int graph_is_mapping_correct(struct git_graph *graph)
return 1;
}
static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
int chars_written)
{
/*
* Add additional spaces to the end of the strbuf, so that all
@@ -570,10 +644,10 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
* aligned for the entire commit.
*/
int extra;
if (sb->len >= graph->width)
if (chars_written >= graph->width)
return;
extra = graph->width - sb->len;
extra = graph->width - chars_written;
strbuf_addf(sb, "%*s", (int) extra, "");
}
@@ -596,10 +670,11 @@ static void graph_output_padding_line(struct git_graph *graph,
* Output a padding row, that leaves all branch lines unchanged
*/
for (i = 0; i < graph->num_new_columns; i++) {
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, &graph->new_columns[i], '|');
strbuf_addch(sb, ' ');
}
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
}
static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
@@ -609,7 +684,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
* of the graph is missing.
*/
strbuf_addstr(sb, "...");
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, 3);
if (graph->num_parents >= 3 &&
graph->commit_index < (graph->num_columns - 1))
@@ -623,6 +698,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
{
int num_expansion_rows;
int i, seen_this;
int chars_written;
/*
* This function formats a row that increases the space around a commit
@@ -645,11 +721,14 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
* Output the row
*/
seen_this = 0;
chars_written = 0;
for (i = 0; i < graph->num_columns; i++) {
struct column *col = &graph->columns[i];
if (col->commit == graph->commit) {
seen_this = 1;
strbuf_addf(sb, "| %*s", graph->expansion_row, "");
strbuf_write_column(sb, col, '|');
strbuf_addf(sb, " %*s", graph->expansion_row, "");
chars_written += 2 + graph->expansion_row;
} else if (seen_this && (graph->expansion_row == 0)) {
/*
* This is the first line of the pre-commit output.
@@ -662,17 +741,22 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
*/
if (graph->prev_state == GRAPH_POST_MERGE &&
graph->prev_commit_index < i)
strbuf_addstr(sb, "\\ ");
strbuf_write_column(sb, col, '\\');
else
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, col, '|');
chars_written++;
} else if (seen_this && (graph->expansion_row > 0)) {
strbuf_addstr(sb, "\\ ");
strbuf_write_column(sb, col, '\\');
chars_written++;
} else {
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, col, '|');
chars_written++;
}
strbuf_addch(sb, ' ');
chars_written++;
}
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, chars_written);
/*
* Increment graph->expansion_row,
@@ -714,10 +798,34 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
strbuf_addch(sb, '*');
}
/*
* Draw an octopus merge and return the number of characters written.
*/
static int graph_draw_octopus_merge(struct git_graph *graph,
struct strbuf *sb)
{
/*
* Here dashless_commits represents the number of parents
* which don't need to have dashes (because their edges fit
* neatly under the commit).
*/
const int dashless_commits = 2;
int col_num, i;
int num_dashes =
((graph->num_parents - dashless_commits) * 2) - 1;
for (i = 0; i < num_dashes; i++) {
col_num = (i / 2) + dashless_commits;
strbuf_write_column(sb, &graph->new_columns[col_num], '-');
}
col_num = (i / 2) + dashless_commits;
strbuf_write_column(sb, &graph->new_columns[col_num], '.');
return num_dashes + 1;
}
static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
{
int seen_this = 0;
int i, j;
int i, chars_written;
/*
* Output the row containing this commit
@@ -727,7 +835,9 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
* children that we have already processed.)
*/
seen_this = 0;
chars_written = 0;
for (i = 0; i <= graph->num_columns; i++) {
struct column *col = &graph->columns[i];
struct commit *col_commit;
if (i == graph->num_columns) {
if (seen_this)
@@ -740,18 +850,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
if (col_commit == graph->commit) {
seen_this = 1;
graph_output_commit_char(graph, sb);
chars_written++;
if (graph->num_parents < 3)
strbuf_addch(sb, ' ');
else {
int num_dashes =
((graph->num_parents - 2) * 2) - 1;
for (j = 0; j < num_dashes; j++)
strbuf_addch(sb, '-');
strbuf_addstr(sb, ". ");
}
if (graph->num_parents > 3)
chars_written += graph_draw_octopus_merge(graph,
sb);
} else if (seen_this && (graph->num_parents > 2)) {
strbuf_addstr(sb, "\\ ");
strbuf_write_column(sb, col, '\\');
chars_written++;
} else if (seen_this && (graph->num_parents == 2)) {
/*
* This is a 2-way merge commit.
@@ -768,15 +874,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
*/
if (graph->prev_state == GRAPH_POST_MERGE &&
graph->prev_commit_index < i)
strbuf_addstr(sb, "\\ ");
strbuf_write_column(sb, col, '\\');
else
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, col, '|');
chars_written++;
} else {
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, col, '|');
chars_written++;
}
strbuf_addch(sb, ' ');
chars_written++;
}
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, chars_written);
/*
* Update graph->state
@@ -789,37 +899,75 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
graph_update_state(graph, GRAPH_COLLAPSING);
}
static struct column *find_new_column_by_commit(struct git_graph *graph,
struct commit *commit)
{
int i;
for (i = 0; i < graph->num_new_columns; i++) {
if (graph->new_columns[i].commit == commit)
return &graph->new_columns[i];
}
return 0;
}
static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
{
int seen_this = 0;
int i, j;
int i, j, chars_written;
/*
* Output the post-merge row
*/
chars_written = 0;
for (i = 0; i <= graph->num_columns; i++) {
struct column *col = &graph->columns[i];
struct commit *col_commit;
if (i == graph->num_columns) {
if (seen_this)
break;
col_commit = graph->commit;
} else {
col_commit = graph->columns[i].commit;
col_commit = col->commit;
}
if (col_commit == graph->commit) {
/*
* Since the current commit is a merge find
* the columns for the parent commits in
* new_columns and use those to format the
* edges.
*/
struct commit_list *parents = NULL;
struct column *par_column;
seen_this = 1;
strbuf_addch(sb, '|');
for (j = 0; j < graph->num_parents - 1; j++)
strbuf_addstr(sb, "\\ ");
parents = first_interesting_parent(graph);
assert(parents);
par_column = find_new_column_by_commit(graph, parents->item);
assert(par_column);
strbuf_write_column(sb, par_column, '|');
chars_written++;
for (j = 0; j < graph->num_parents - 1; j++) {
parents = next_interesting_parent(graph, parents);
assert(parents);
par_column = find_new_column_by_commit(graph, parents->item);
assert(par_column);
strbuf_write_column(sb, par_column, '\\');
strbuf_addch(sb, ' ');
}
chars_written += j * 2;
} else if (seen_this) {
strbuf_addstr(sb, "\\ ");
strbuf_write_column(sb, col, '\\');
strbuf_addch(sb, ' ');
chars_written += 2;
} else {
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, col, '|');
strbuf_addch(sb, ' ');
chars_written += 2;
}
}
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, chars_written);
/*
* Update graph->state
@@ -912,12 +1060,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
if (target < 0)
strbuf_addch(sb, ' ');
else if (target * 2 == i)
strbuf_addch(sb, '|');
strbuf_write_column(sb, &graph->new_columns[target], '|');
else
strbuf_addch(sb, '/');
strbuf_write_column(sb, &graph->new_columns[target], '/');
}
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, graph->mapping_size);
/*
* Swap mapping and new_mapping
@@ -979,9 +1127,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
* children that we have already processed.)
*/
for (i = 0; i < graph->num_columns; i++) {
struct commit *col_commit = graph->columns[i].commit;
struct column *col = &graph->columns[i];
struct commit *col_commit = col->commit;
if (col_commit == graph->commit) {
strbuf_addch(sb, '|');
strbuf_write_column(sb, col, '|');
if (graph->num_parents < 3)
strbuf_addch(sb, ' ');
@@ -991,11 +1140,12 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
strbuf_addch(sb, ' ');
}
} else {
strbuf_addstr(sb, "| ");
strbuf_write_column(sb, col, '|');
strbuf_addch(sb, ' ');
}
}
graph_pad_horizontally(graph, sb);
graph_pad_horizontally(graph, sb, graph->num_columns);
/*
* Update graph->prev_state since we have output a padding line

View File

@@ -10,7 +10,7 @@
static void process_blob(struct rev_info *revs,
struct blob *blob,
struct object_array *p,
show_object_fn show,
struct name_path *path,
const char *name)
{
@@ -23,7 +23,7 @@ static void process_blob(struct rev_info *revs,
if (obj->flags & (UNINTERESTING | SEEN))
return;
obj->flags |= SEEN;
add_object(obj, p, path, name);
show(obj, path, name);
}
/*
@@ -50,7 +50,7 @@ static void process_blob(struct rev_info *revs,
*/
static void process_gitlink(struct rev_info *revs,
const unsigned char *sha1,
struct object_array *p,
show_object_fn show,
struct name_path *path,
const char *name)
{
@@ -59,7 +59,7 @@ static void process_gitlink(struct rev_info *revs,
static void process_tree(struct rev_info *revs,
struct tree *tree,
struct object_array *p,
show_object_fn show,
struct name_path *path,
const char *name)
{
@@ -77,7 +77,7 @@ static void process_tree(struct rev_info *revs,
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
add_object(obj, p, path, name);
show(obj, path, name);
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
@@ -88,14 +88,14 @@ static void process_tree(struct rev_info *revs,
if (S_ISDIR(entry.mode))
process_tree(revs,
lookup_tree(entry.sha1),
p, &me, entry.path);
show, &me, entry.path);
else if (S_ISGITLINK(entry.mode))
process_gitlink(revs, entry.sha1,
p, &me, entry.path);
show, &me, entry.path);
else
process_blob(revs,
lookup_blob(entry.sha1),
p, &me, entry.path);
show, &me, entry.path);
}
free(tree->buffer);
tree->buffer = NULL;
@@ -134,6 +134,11 @@ void mark_edges_uninteresting(struct commit_list *list,
}
}
static void add_pending_tree(struct rev_info *revs, struct tree *tree)
{
add_pending_object(revs, &tree->object, "");
}
void traverse_commit_list(struct rev_info *revs,
show_commit_fn show_commit,
show_object_fn show_object,
@@ -141,10 +146,9 @@ void traverse_commit_list(struct rev_info *revs,
{
int i;
struct commit *commit;
struct object_array objects = { 0, 0, NULL };
while ((commit = get_revision(revs)) != NULL) {
process_tree(revs, commit->tree, &objects, NULL, "");
add_pending_tree(revs, commit->tree);
show_commit(commit, data);
}
for (i = 0; i < revs->pending.nr; i++) {
@@ -155,25 +159,22 @@ void traverse_commit_list(struct rev_info *revs,
continue;
if (obj->type == OBJ_TAG) {
obj->flags |= SEEN;
add_object_array(obj, name, &objects);
show_object(obj, NULL, name);
continue;
}
if (obj->type == OBJ_TREE) {
process_tree(revs, (struct tree *)obj, &objects,
process_tree(revs, (struct tree *)obj, show_object,
NULL, name);
continue;
}
if (obj->type == OBJ_BLOB) {
process_blob(revs, (struct blob *)obj, &objects,
process_blob(revs, (struct blob *)obj, show_object,
NULL, name);
continue;
}
die("unknown pending object %s (%s)",
sha1_to_hex(obj->sha1), name);
}
for (i = 0; i < objects.nr; i++)
show_object(&objects.objects[i], data);
free(objects.objects);
if (revs->pending.nr) {
free(revs->pending.objects);
revs->pending.nr = 0;

View File

@@ -2,7 +2,7 @@
#define LIST_OBJECTS_H
typedef void (*show_commit_fn)(struct commit *, void *);
typedef void (*show_object_fn)(struct object_array_entry *, void *);
typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *);
typedef void (*show_edge_fn)(struct commit *);
void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);

18
refs.c
View File

@@ -1681,7 +1681,7 @@ static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
return;
}
char *shorten_unambiguous_ref(const char *ref)
char *shorten_unambiguous_ref(const char *ref, int strict)
{
int i;
static char **scanf_fmts;
@@ -1718,6 +1718,7 @@ char *shorten_unambiguous_ref(const char *ref)
/* skip first rule, it will always match */
for (i = nr_rules - 1; i > 0 ; --i) {
int j;
int rules_to_fail = i;
int short_name_len;
if (1 != sscanf(ref, scanf_fmts[i], short_name))
@@ -1725,15 +1726,26 @@ char *shorten_unambiguous_ref(const char *ref)
short_name_len = strlen(short_name);
/*
* in strict mode, all (except the matched one) rules
* must fail to resolve to a valid non-ambiguous ref
*/
if (strict)
rules_to_fail = nr_rules;
/*
* check if the short name resolves to a valid ref,
* but use only rules prior to the matched one
*/
for (j = 0; j < i; j++) {
for (j = 0; j < rules_to_fail; j++) {
const char *rule = ref_rev_parse_rules[j];
unsigned char short_objectname[20];
char refname[PATH_MAX];
/* skip matched rule */
if (i == j)
continue;
/*
* the short name is ambiguous, if it resolves
* (with this previous rule) to a valid ref
@@ -1749,7 +1761,7 @@ char *shorten_unambiguous_ref(const char *ref)
* short name is non-ambiguous if all previous rules
* haven't resolved to a valid ref
*/
if (j == i)
if (j == rules_to_fail)
return short_name;
}

2
refs.h
View File

@@ -81,7 +81,7 @@ extern int for_each_reflog(each_ref_fn, void *);
extern int check_ref_format(const char *target);
extern const char *prettify_ref(const struct ref *ref);
extern char *shorten_unambiguous_ref(const char *ref);
extern char *shorten_unambiguous_ref(const char *ref, int strict);
/** rename ref, return 0 on success **/
extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);

View File

@@ -1461,11 +1461,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
return 0;
base = branch->merge[0]->dst;
if (!prefixcmp(base, "refs/remotes/")) {
base += strlen("refs/remotes/");
} else if (!prefixcmp(base, "refs/heads/")) {
base += strlen("refs/heads/");
}
base = shorten_unambiguous_ref(base, 0);
if (!num_theirs)
strbuf_addf(sb, "Your branch is ahead of '%s' "
"by %d commit%s.\n",
@@ -1504,7 +1500,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla
struct ref *get_local_heads(void)
{
struct ref *local_refs, **local_tail = &local_refs;
struct ref *local_refs = NULL, **local_tail = &local_refs;
for_each_ref(one_local_ref, &local_tail);
return local_refs;
}

View File

@@ -15,9 +15,9 @@
volatile show_early_output_fn_t show_early_output;
static char *path_name(struct name_path *path, const char *name)
char *path_name(const struct name_path *path, const char *name)
{
struct name_path *p;
const struct name_path *p;
char *n, *m;
int nlen = strlen(name);
int len = nlen + 1;

View File

@@ -146,6 +146,8 @@ struct name_path {
const char *elem;
};
char *path_name(const struct name_path *path, const char *name);
extern void add_object(struct object *obj,
struct object_array *p,
struct name_path *path,

View File

@@ -40,6 +40,7 @@ static struct commands {
} cmd_list[] = {
{ "git-receive-pack", do_generic_cmd },
{ "git-upload-pack", do_generic_cmd },
{ "git-upload-archive", do_generic_cmd },
{ "cvs", do_cvs_cmd },
{ NULL },
};

View File

@@ -199,4 +199,13 @@ test_expect_success 'init honors global core.sharedRepository' '
x`git config -f shared-honor-global/.git/config core.sharedRepository`
'
test_expect_success 'init rejects insanely long --template' '
(
insane=$(printf "x%09999dx" 1) &&
mkdir test &&
cd test &&
test_must_fail git init --template=$insane
)
'
test_done

View File

@@ -10,7 +10,7 @@ setup() {
check() {
echo "$2" >expected
git config --get "$1" >actual
git config --get "$1" >actual 2>&1
test_cmp actual expected
}
@@ -40,4 +40,11 @@ test_expect_success 'make sure git config escapes section names properly' '
check "$SECTION" bar
'
LONG_VALUE=$(printf "x%01021dx a" 7)
test_expect_success 'do not crash on special long config line' '
setup &&
git config section.key "$LONG_VALUE" &&
check section.key "fatal: bad config file line 2 in .git/config"
'
test_done

View File

@@ -0,0 +1,38 @@
#!/bin/sh
test_description='git apply handling criss-cross rename patch.'
. ./test-lib.sh
create_file() {
cnt=0
while test $cnt -le 100
do
cnt=$(($cnt + 1))
echo "$2" >> "$1"
done
}
test_expect_success 'setup' '
create_file file1 "File1 contents" &&
create_file file2 "File2 contents" &&
git add file1 file2 &&
git commit -m 1
'
test_expect_success 'criss-cross rename' '
mv file1 tmp &&
mv file2 file1 &&
mv tmp file2
'
test_expect_success 'diff -M -B' '
git diff -M -B > diff &&
git reset --hard
'
test_expect_success 'apply' '
git apply diff
'
test_done

View File

@@ -294,4 +294,19 @@ test_expect_success 'am --ignore-date' '
echo "$at" | grep "+0000"
'
test_expect_success 'am into an unborn branch' '
rm -fr subdir &&
mkdir -p subdir &&
git format-patch --numbered-files -o subdir -1 first &&
(
cd subdir &&
git init &&
git am 1
) &&
result=$(
cd subdir && git rev-parse HEAD^{tree}
) &&
test "z$result" = "z$(git rev-parse first^{tree})"
'
test_done

View File

@@ -50,7 +50,7 @@ test_expect_success \
test_expect_success \
'add ignored file' \
'echo ignore me >a/ignored &&
echo ignored export-ignore >.gitattributes'
echo ignored export-ignore >.git/info/attributes'
test_expect_success \
'add files to repository' \
@@ -64,7 +64,7 @@ test_expect_success \
test_expect_success \
'create bare clone' \
'git clone --bare . bare.git &&
cp .gitattributes bare.git/info/attributes'
cp .git/info/attributes bare.git/info/attributes'
test_expect_success \
'remove ignored file' \
@@ -139,10 +139,11 @@ test_expect_success \
test_expect_success \
'create archives with substfiles' \
'echo "substfile?" export-subst >a/.gitattributes &&
'cp .git/info/attributes .git/info/attributes.before &&
echo "substfile?" export-subst >>.git/info/attributes &&
git archive HEAD >f.tar &&
git archive --prefix=prefix/ HEAD >g.tar &&
rm a/.gitattributes'
mv .git/info/attributes.before .git/info/attributes'
test_expect_success \
'extract substfiles' \

91
t/t5001-archive-attr.sh Executable file
View File

@@ -0,0 +1,91 @@
#!/bin/sh
test_description='git archive attribute tests'
. ./test-lib.sh
SUBSTFORMAT=%H%n
test_expect_exists() {
test_expect_success " $1 exists" "test -e $1"
}
test_expect_missing() {
test_expect_success " $1 does not exist" "test ! -e $1"
}
test_expect_success 'setup' '
echo ignored >ignored &&
echo ignored export-ignore >>.git/info/attributes &&
git add ignored &&
echo ignored by tree >ignored-by-tree &&
echo ignored-by-tree export-ignore >.gitattributes &&
git add ignored-by-tree .gitattributes &&
echo ignored by worktree >ignored-by-worktree &&
echo ignored-by-worktree export-ignore >.gitattributes &&
git add ignored-by-worktree &&
printf "A\$Format:%s\$O" "$SUBSTFORMAT" >nosubstfile &&
printf "A\$Format:%s\$O" "$SUBSTFORMAT" >substfile1 &&
printf "A not substituted O" >substfile2 &&
echo "substfile?" export-subst >>.git/info/attributes &&
git add nosubstfile substfile1 substfile2 &&
git commit -m. &&
git clone --bare . bare &&
cp .git/info/attributes bare/info/attributes
'
test_expect_success 'git archive' '
git archive HEAD >archive.tar &&
(mkdir archive && cd archive && "$TAR" xf -) <archive.tar
'
test_expect_missing archive/ignored
test_expect_missing archive/ignored-by-tree
test_expect_exists archive/ignored-by-worktree
test_expect_success 'git archive with worktree attributes' '
git archive --worktree-attributes HEAD >worktree.tar &&
(mkdir worktree && cd worktree && "$TAR" xf -) <worktree.tar
'
test_expect_missing worktree/ignored
test_expect_exists worktree/ignored-by-tree
test_expect_missing worktree/ignored-by-worktree
test_expect_success 'git archive vs. bare' '
(cd bare && git archive HEAD) >bare-archive.tar &&
test_cmp archive.tar bare-archive.tar
'
test_expect_success 'git archive with worktree attributes, bare' '
(cd bare && git archive --worktree-attributes HEAD) >bare-worktree.tar &&
(mkdir bare-worktree && cd bare-worktree && "$TAR" xf -) <bare-worktree.tar
'
test_expect_missing bare-worktree/ignored
test_expect_exists bare-worktree/ignored-by-tree
test_expect_exists bare-worktree/ignored-by-worktree
test_expect_success 'export-subst' '
git log "--pretty=format:A${SUBSTFORMAT}O" HEAD >substfile1.expected &&
test_cmp nosubstfile archive/nosubstfile &&
test_cmp substfile1.expected archive/substfile1 &&
test_cmp substfile2 archive/substfile2
'
test_expect_success 'git tar-tree vs. git archive with worktree attributes' '
git tar-tree HEAD >tar-tree.tar &&
test_cmp worktree.tar tar-tree.tar
'
test_expect_success 'git tar-tree vs. git archive with worktree attrs, bare' '
(cd bare && git tar-tree HEAD) >bare-tar-tree.tar &&
test_cmp bare-worktree.tar bare-tar-tree.tar
'
test_done

View File

@@ -301,10 +301,11 @@ test_expect_success 'Check for invalid refname format' '
cat >expected <<\EOF
heads/master
master
tags/master
EOF
test_expect_success 'Check ambiguous head and tag refs' '
test_expect_success 'Check ambiguous head and tag refs (strict)' '
git config --bool core.warnambiguousrefs true &&
git checkout -b newtag &&
echo "Using $datestamp" > one &&
git add one &&
@@ -315,12 +316,23 @@ test_expect_success 'Check ambiguous head and tag refs' '
test_cmp expected actual
'
cat >expected <<\EOF
heads/master
master
EOF
test_expect_success 'Check ambiguous head and tag refs (loose)' '
git config --bool core.warnambiguousrefs false &&
git for-each-ref --format "%(refname:short)" refs/heads/master refs/tags/master >actual &&
test_cmp expected actual
'
cat >expected <<\EOF
heads/ambiguous
ambiguous
EOF
test_expect_success 'Check ambiguous head and tag refs II' '
test_expect_success 'Check ambiguous head and tag refs II (loose)' '
git checkout master &&
git tag ambiguous testtag^0 &&
git branch ambiguous testtag^0 &&

211
t/t7800-difftool.sh Executable file
View File

@@ -0,0 +1,211 @@
#!/bin/sh
#
# Copyright (c) 2009 David Aguilar
#
test_description='git-difftool
Testing basic diff tool invocation
'
. ./test-lib.sh
remove_config_vars()
{
# Unset all config variables used by git-difftool
git config --unset diff.tool
git config --unset difftool.test-tool.cmd
git config --unset difftool.prompt
git config --unset merge.tool
git config --unset mergetool.test-tool.cmd
return 0
}
restore_test_defaults()
{
# Restores the test defaults used by several tests
remove_config_vars
unset GIT_DIFF_TOOL
unset GIT_MERGE_TOOL
unset GIT_DIFFTOOL_PROMPT
unset GIT_DIFFTOOL_NO_PROMPT
git config diff.tool test-tool &&
git config difftool.test-tool.cmd 'cat $LOCAL'
}
prompt_given()
{
prompt="$1"
test "$prompt" = "Hit return to launch 'test-tool': branch"
}
# Create a file on master and change it on branch
test_expect_success 'setup' '
echo master >file &&
git add file &&
git commit -m "added file" &&
git checkout -b branch master &&
echo branch >file &&
git commit -a -m "branch changed file" &&
git checkout master
'
# Configure a custom difftool.<tool>.cmd and use it
test_expect_success 'custom commands' '
restore_test_defaults &&
git config difftool.test-tool.cmd "cat \$REMOTE" &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "master" &&
restore_test_defaults &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "branch"
'
# Ensures that git-difftool ignores bogus --tool values
test_expect_success 'difftool ignores bad --tool values' '
diff=$(git difftool --no-prompt --tool=bogus-tool branch)
test "$?" = 1 &&
test "$diff" = ""
'
# Specify the diff tool using $GIT_DIFF_TOOL
test_expect_success 'GIT_DIFF_TOOL variable' '
git config --unset diff.tool
GIT_DIFF_TOOL=test-tool &&
export GIT_DIFF_TOOL &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "branch" &&
restore_test_defaults
'
# Test the $GIT_*_TOOL variables and ensure
# that $GIT_DIFF_TOOL always wins unless --tool is specified
test_expect_success 'GIT_DIFF_TOOL overrides' '
git config diff.tool bogus-tool &&
git config merge.tool bogus-tool &&
GIT_MERGE_TOOL=test-tool &&
export GIT_MERGE_TOOL &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "branch" &&
unset GIT_MERGE_TOOL &&
GIT_MERGE_TOOL=bogus-tool &&
GIT_DIFF_TOOL=test-tool &&
export GIT_MERGE_TOOL &&
export GIT_DIFF_TOOL &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "branch" &&
GIT_DIFF_TOOL=bogus-tool &&
export GIT_DIFF_TOOL &&
diff=$(git difftool --no-prompt --tool=test-tool branch) &&
test "$diff" = "branch" &&
restore_test_defaults
'
# Test that we don't have to pass --no-prompt to difftool
# when $GIT_DIFFTOOL_NO_PROMPT is true
test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
GIT_DIFFTOOL_NO_PROMPT=true &&
export GIT_DIFFTOOL_NO_PROMPT &&
diff=$(git difftool branch) &&
test "$diff" = "branch" &&
restore_test_defaults
'
# git-difftool supports the difftool.prompt variable.
# Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
git config difftool.prompt false &&
GIT_DIFFTOOL_PROMPT=true &&
export GIT_DIFFTOOL_PROMPT &&
prompt=$(echo | git difftool --prompt branch | tail -1) &&
prompt_given "$prompt" &&
restore_test_defaults
'
# Test that we don't have to pass --no-prompt when difftool.prompt is false
test_expect_success 'difftool.prompt config variable is false' '
git config difftool.prompt false &&
diff=$(git difftool branch) &&
test "$diff" = "branch" &&
restore_test_defaults
'
# Test that the -y flag can override difftool.prompt = true
test_expect_success 'difftool.prompt can overridden with -y' '
git config difftool.prompt true &&
diff=$(git difftool -y branch) &&
test "$diff" = "branch" &&
restore_test_defaults
'
# Test that the --prompt flag can override difftool.prompt = false
test_expect_success 'difftool.prompt can overridden with --prompt' '
git config difftool.prompt false &&
prompt=$(echo | git difftool --prompt branch | tail -1) &&
prompt_given "$prompt" &&
restore_test_defaults
'
# Test that the last flag passed on the command-line wins
test_expect_success 'difftool last flag wins' '
diff=$(git difftool --prompt --no-prompt branch) &&
test "$diff" = "branch" &&
restore_test_defaults &&
prompt=$(echo | git difftool --no-prompt --prompt branch | tail -1) &&
prompt_given "$prompt" &&
restore_test_defaults
'
# git-difftool falls back to git-mergetool config variables
# so test that behavior here
test_expect_success 'difftool + mergetool config variables' '
remove_config_vars
git config merge.tool test-tool &&
git config mergetool.test-tool.cmd "cat \$LOCAL" &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "branch" &&
# set merge.tool to something bogus, diff.tool to test-tool
git config merge.tool bogus-tool &&
git config diff.tool test-tool &&
diff=$(git difftool --no-prompt branch) &&
test "$diff" = "branch" &&
restore_test_defaults
'
test_expect_success 'difftool.<tool>.path' '
git config difftool.tkdiff.path echo &&
diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
git config --unset difftool.tkdiff.path &&
lines=$(echo "$diff" | grep file | wc -l) &&
test "$lines" -eq 1
'
test_done

View File

@@ -16,6 +16,9 @@
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
@@ -39,6 +42,7 @@ fi
# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
# check for no description
@@ -52,7 +56,8 @@ esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git-cat-file -t $newrev)
@@ -80,6 +85,10 @@ case "$refname","$newrev_type" in
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch

View File

@@ -87,7 +87,8 @@ static int check_updates(struct unpack_trees_options *o)
cnt = 0;
}
git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result);
if (o->update)
git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result);
for (i = 0; i < index->cache_nr; i++) {
struct cache_entry *ce = index->cache[i];
@@ -112,7 +113,8 @@ static int check_updates(struct unpack_trees_options *o)
}
}
stop_progress(&progress);
git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
if (o->update)
git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
return errs != 0;
}

View File

@@ -78,20 +78,22 @@ static void show_commit(struct commit *commit, void *data)
commit->buffer = NULL;
}
static void show_object(struct object_array_entry *p, void *data)
static void show_object(struct object *obj, const struct name_path *path, const char *component)
{
/* An object with name "foo\n0000000..." can be used to
* confuse downstream git-pack-objects very badly.
*/
const char *ep = strchr(p->name, '\n');
const char *name = path_name(path, component);
const char *ep = strchr(name, '\n');
if (ep) {
fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(p->item->sha1),
(int) (ep - p->name),
p->name);
fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1),
(int) (ep - name),
name);
}
else
fprintf(pack_pipe, "%s %s\n",
sha1_to_hex(p->item->sha1), p->name);
sha1_to_hex(obj->sha1), name);
free((char *)name);
}
static void show_edge(struct commit *commit)