summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/1.7.5.2.txt57
-rw-r--r--Documentation/RelNotes/1.7.6.txt89
-rw-r--r--Documentation/config.txt8
-rw-r--r--Documentation/diff-config.txt44
-rw-r--r--Documentation/diff-options.txt54
-rw-r--r--Documentation/git-add.txt6
-rw-r--r--Documentation/git-checkout.txt7
-rw-r--r--Documentation/git-commit.txt24
-rw-r--r--Documentation/git-fsck.txt4
-rw-r--r--Documentation/git-reset.txt7
-rw-r--r--Documentation/git-stash.txt9
-rw-r--r--Documentation/git.txt3
-rw-r--r--Documentation/merge-config.txt10
-rw-r--r--LGPL-2.1511
-rw-r--r--Makefile50
-rw-r--r--builtin/add.c55
-rw-r--r--builtin/apply.c9
-rw-r--r--builtin/commit.c50
-rw-r--r--builtin/log.c74
-rw-r--r--builtin/merge.c62
-rw-r--r--builtin/mktag.c4
-rw-r--r--builtin/rerere.c77
-rw-r--r--builtin/revert.c4
-rw-r--r--builtin/send-pack.c11
-rw-r--r--builtin/tag.c26
-rw-r--r--cache.h21
-rw-r--r--commit.h4
-rw-r--r--config.c12
-rw-r--r--connect.c35
-rwxr-xr-xcontrib/completion/git-completion.bash20
-rwxr-xr-xcontrib/fast-import/git-p4217
-rw-r--r--contrib/fast-import/git-p4.txt36
-rw-r--r--convert.c158
-rw-r--r--diff.c171
-rw-r--r--diff.h3
-rw-r--r--environment.c4
-rw-r--r--fast-import.c6
-rwxr-xr-xgit-add--interactive.perl25
-rwxr-xr-xgit-svn.perl2
-rwxr-xr-xgitweb/gitweb.perl397
-rw-r--r--http-push.c152
-rw-r--r--http-walker.c4
-rw-r--r--http.c12
-rw-r--r--http.h6
-rw-r--r--ident.c1
-rw-r--r--merge-recursive.c30
-rw-r--r--object.c4
-rw-r--r--remote-curl.c2
-rw-r--r--replace_object.c4
-rw-r--r--rerere.c84
-rw-r--r--rerere.h2
-rw-r--r--revision.c205
-rw-r--r--sha1_file.c33
-rw-r--r--submodule.c31
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh53
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh7
-rwxr-xr-xt/t2200-add-update.sh24
-rwxr-xr-xt/t3102-ls-tree-wildcards.sh14
-rwxr-xr-xt/t3503-cherry-pick-root.sh27
-rwxr-xr-xt/t3701-add-interactive.sh36
-rwxr-xr-xt/t4047-diff-dirstat.sh979
-rwxr-xr-xt/t5400-send-pack.sh4
-rwxr-xr-xt/t5532-fetch-proxy.sh43
-rwxr-xr-xt/t5541-http-push.sh18
-rwxr-xr-xt/t6010-merge-base.sh50
-rwxr-xr-xt/t6017-rev-list-stdin.sh17
-rwxr-xr-xt/t6018-rev-list-glob.sh50
-rwxr-xr-xt/t6022-merge-rename.sh63
-rwxr-xr-xt/t6050-replace.sh18
-rwxr-xr-xt/t7500-commit.sh14
-rwxr-xr-xt/t7500/add-whitespaced-content8
-rwxr-xr-xt/t7501-commit.sh21
-rwxr-xr-xt/t7506-status-submodule.sh92
-rwxr-xr-xt/t7600-merge.sh235
-rwxr-xr-xt/t9116-git-svn-log.sh15
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh49
-rwxr-xr-xt/t9502-gitweb-standalone-parse-output.sh74
-rwxr-xr-xt/t9800-git-p4.sh125
-rw-r--r--unpack-trees.c63
-rw-r--r--xdiff-interface.c2
80 files changed, 3927 insertions, 1110 deletions
diff --git a/Documentation/RelNotes/1.7.5.2.txt b/Documentation/RelNotes/1.7.5.2.txt
new file mode 100644
index 0000000..951eb7c
--- /dev/null
+++ b/Documentation/RelNotes/1.7.5.2.txt
@@ -0,0 +1,57 @@
+Git v1.7.5.2 Release Notes
+==========================
+
+The release notes to 1.7.5.1 forgot to mention:
+
+ * "git stash -p --no-keep-index" and "git stash --no-keep-index -p" now
+ mean the same thing.
+
+ * "git upload-pack" (hence "git push" over git native protocol) had a
+ subtle race condition that could lead to a deadlock.
+
+Fixes since v1.7.5.1
+--------------------
+
+ * "git add -p" did not work correctly when a hunk is split and then
+ one of them was given to the editor.
+
+ * "git add -u" did not resolve a conflict where our history deleted and
+ their history modified the same file, and the working tree resolved to
+ keep a file.
+
+ * "git cvsimport" did not know that CVSNT stores its password file in a
+ location different from the traditional CVS.
+
+ * "git diff-files" did not show the mode information from the working
+ tree side of an unmerged path correctly.
+
+ * "git diff -M --cached" used to use unmerged path as a possible rename
+ source candidate, which made no sense.
+
+ * The option name parser in "git fast-import" used prefix matches for
+ some options where it shouldn't, and accepted non-existent options,
+ e.g. "--relative-marksmith" or "--forceps".
+
+ * "git format-patch" did not quote RFC822 special characters in the
+ email address (e.g From: Junio C. Hamano <jch@example.com>, not
+ From: "Junio C. Hamano" <jch@example.com>).
+
+ * "git format-patch" when run with "--quiet" option used to produce a
+ nonsense result that consists of alternating empty output.
+
+ * In "git merge", per-branch branch.<name>.mergeoptions configuration
+ variables did not override the fallback default merge.<option>
+ configuration variables such as merge.ff, merge.log, etc.
+
+ * "git merge-one-file" did not honor GIT_WORK_TREE settings when
+ handling a "both sides added, differently" conflict.
+
+ * "git mergetool" did not handle conflicted submoudules gracefully.
+
+ * "git-p4" (in contrib) used a wrong base image while merge a file that
+ was added on both branches differently.
+
+ * "git rebase -i -p" failed to preserve the history when there is a
+ redundant merge created with the --no-ff option.
+
+And other minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.6.txt b/Documentation/RelNotes/1.7.6.txt
index 8554918..f5faf28 100644
--- a/Documentation/RelNotes/1.7.6.txt
+++ b/Documentation/RelNotes/1.7.6.txt
@@ -6,6 +6,11 @@ Updates since v1.7.5
* Various git-svn updates.
+ * Updates the way content tags are handled in gitweb.
+
+ * Similar to branch names, tagnames that begin with "-" are now
+ disallowed.
+
* Clean-up of the C part of i18n (but not l10n---please wait)
continues.
@@ -16,14 +21,24 @@ Updates since v1.7.5
* "git blame" learned "--abbrev[=<n>]" option to control the minimum
number of hexdigits shown for commit object names.
- * "git cvsimport" learned that CVSNT stores its password file in a
- location different from the traditional CVS.
+ * Aborting "git commit --interactive" discards updates to the index
+ made during the interctive session.
+
+ * "git commit" learned a "--patch" option to directly jump to the
+ per-hunk selection UI of the interactive mode.
* "git diff -C -C" used to disable the rename detection entirely when
there are too many copy candidate paths in the tree; now it falls
back to "-C" when doing so would keep the copy candidate paths
under the rename detection limit.
+ * "git diff" and its family of commands learned --dirstat=0 to show
+ directories that contribute less than 0.1% of changes.
+
+ * "git diff" and its family of commands learned --dirstat=lines mode to
+ assess damage to the directory based on number of lines in the patch
+ output, not based on the similarity numbers.
+
* "git format-patch" learned "--quiet" option to suppress the output of
the names of generated files.
@@ -38,8 +53,16 @@ Updates since v1.7.5
* "git merge" learned "-" as a short-hand for "the previous branch", just
like the way "git checkout -" works.
- * "git merge-one-file" learned to honor GIT_WORK_TREE settings when
- handling "both sides added, differently" conflict.
+ * "git merge" uses "merge.ff" configuration variable to decide to always
+ create a merge commit (i.e. --no-ff, aka merge.ff=no), refuse to create
+ a merge commit (i.e. --ff-only, aka merge.ff=only). Setting merge.ff=yes
+ (or not setting it at all) restores the default behaviour of allowing
+ fast-forward to happen when possible.
+
+ * p4-import (from contrib) learned a new option --preserve-user.
+
+ * "git rebase" that does not specify on top of which branch to rebase
+ the current branch now uses @{upstream} of the current branch.
* "git rev-list --count" used with "--cherry-mark" counts the cherry-picked
commits separately, producing more a useful output.
@@ -47,13 +70,14 @@ Updates since v1.7.5
* "git submodule update" learned "--force" option to get rid of local
changes in submodules and replace them with the up-to-date version.
+ * "git status" and friends ignore .gitmodules file while the file is
+ still in a conflicted state during a merge, to avoid using information
+ that is not final and possibly corrupt with conflict markers.
+
* Compressed tarball gitweb generates is made without the timestamp of
the tarball generation; snapshot from the same tree should result in
a same tarball.
- * "git-p4" (in contrib) learned to merge a file that was added on both
- branches differently by using 2-way merge.
-
Also contains various documentation updates and minor miscellaneous
changes.
@@ -64,43 +88,36 @@ Fixes since v1.7.5
Unless otherwise noted, all the fixes in 1.7.5.X maintenance track are
included in this release.
- * "git config" used to choke with an insanely long line.
- (merge ef/maint-strbuf-init later)
-
- * "git diff-files" did not show the mode information from the working
- tree side of an unmerged path correctly.
- (merge jc/fix-diff-files-unmerged later)
+ * Setting $(prefix) in config.mak did not affect where etc/gitconfig
+ file is read from, even though passing it from the command line of
+ $(MAKE) did.
+ (merge kk/maint-prefix-in-config-mak later)
- * "git diff -M --cached" used to use unmerged path as a possible rename
- source candidate, which made no sense.
- (merge mz/maint-rename-unmerged later)
+ * The bash completion scripts should correctly work using zsh's bash
+ completion emulation layer now.
+ (merge fc/completion-zsh later)
- * "git format-patch" when run with "--quiet" option used to produce a
- nonsense result that consists of alternating empty output.
- (merge early part of cn/format-patch-quiet later)
+ * The logic to handle "&" (expand to UNIX username) in GECOS field
+ miscounted the length of the name it formatted.
+ (merge rg/copy-gecos-username later)
- * "git format-patch" did not quote RFC822 special characters in the
- email address (e.g From: Junio C. Hamano <jch@example.com>, not
- From: "Junio C. Hamano" <jch@example.com>).
- (merge jk/format-patch-quote-special-in-from later)
+ * "git cherry-pick -s resolve" failed to cherry-pick a root commit.
+ (merge jk/cherry-pick-root-with-resolve later)
- * "git mergetool" did not handle conflicted submoudules gracefully.
- (merge jm/mergetool-submodules later)
-
- * "git rebase -i -p" failed to preserve the history when there is a
- redundant merge created with the --no-ff option.
- (merge aw/maint-rebase-i-p-no-ff later)
+ * "git config" used to choke with an insanely long line.
+ (merge ef/maint-strbuf-init later)
- * "git stash -p --no-keep-index" and "git stash --no-keep-index -p" now
- mean the same thing.
- (merge dm/stash-k-i-p later)
+ * "git log --stdin path" with an input that has additional pathspec
+ used to corrupt memory.
+ (merge jc/maint-pathspec-stdin-and-cmdline later)
- * "git upload-pack" (hence "git push" over git native protocol) had a
- subtle race condition that could lead to a deadlock.
- (merge jk/maint-upload-pack-shallow later)
+ * "git send-pack" (hence "git push") over smalt-HTTP protocol could
+ deadlock when the client side pack-object died early.
+ (merge js/maint-send-pack-stateless-rpc-deadlock-fix later)
+ (merge jk/git-connection-deadlock-fix later)
---
exec >/var/tmp/1
echo O=$(git describe master)
-O=v1.7.5.1-216-g3970fc5
+O=v1.7.5.1-339-g254fd97
git shortlog --no-merges ^maint ^$O master
diff --git a/Documentation/config.txt b/Documentation/config.txt
index c96aec1..1a060ec 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1309,9 +1309,11 @@ instaweb.port::
interactive.singlekey::
In interactive commands, allow the user to provide one-letter
input with a single key (i.e., without hitting enter).
- Currently this is used only by the `\--patch` mode of
- linkgit:git-add[1]. Note that this setting is silently
- ignored if portable keystroke input is not available.
+ Currently this is used by the `\--patch` mode of
+ linkgit:git-add[1], linkgit:git-checkout[1], linkgit:git-commit[1],
+ linkgit:git-reset[1], and linkgit:git-stash[1]. Note that this
+ setting is silently ignored if portable keystroke input
+ is not available.
log.date::
Set the default date-time mode for the 'log' command.
diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt
index 2b1605f..1aed79e 100644
--- a/Documentation/diff-config.txt
+++ b/Documentation/diff-config.txt
@@ -8,6 +8,50 @@ diff.autorefreshindex::
affects only 'git diff' Porcelain, and not lower level
'diff' commands such as 'git diff-files'.
+diff.dirstat::
+ A comma separated list of `--dirstat` parameters specifying the
+ default behavior of the `--dirstat` option to linkgit:git-diff[1]`
+ and friends. The defaults can be overridden on the command line
+ (using `--dirstat=<param1,param2,...>`). The fallback defaults
+ (when not changed by `diff.dirstat`) are `changes,noncumulative,3`.
+ The following parameters are available:
++
+--
+`changes`;;
+ Compute the dirstat numbers by counting the lines that have been
+ removed from the source, or added to the destination. This ignores
+ the amount of pure code movements within a file. In other words,
+ rearranging lines in a file is not counted as much as other changes.
+ This is the default behavior when no parameter is given.
+`lines`;;
+ Compute the dirstat numbers by doing the regular line-based diff
+ analysis, and summing the removed/added line counts. (For binary
+ files, count 64-byte chunks instead, since binary files have no
+ natural concept of lines). This is a more expensive `--dirstat`
+ behavior than the `changes` behavior, but it does count rearranged
+ lines within a file as much as other changes. The resulting output
+ is consistent with what you get from the other `--*stat` options.
+`files`;;
+ Compute the dirstat numbers by counting the number of files changed.
+ Each changed file counts equally in the dirstat analysis. This is
+ the computationally cheapest `--dirstat` behavior, since it does
+ not have to look at the file contents at all.
+`cumulative`;;
+ Count changes in a child directory for the parent directory as well.
+ Note that when using `cumulative`, the sum of the percentages
+ reported may exceed 100%. The default (non-cumulative) behavior can
+ be specified with the `noncumulative` parameter.
+<limit>;;
+ An integer parameter specifies a cut-off percent (3% by default).
+ Directories contributing less than this percentage of the changes
+ are not shown in the output.
+--
++
+Example: The following will count changed files, while ignoring
+directories with less than 10% of the total amount of changed files,
+and accumulating child directory counts in the parent directories:
+`files,10,cumulative`.
+
diff.external::
If this config variable is set, diff generation is not
performed using the internal diff machinery, but using the
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 9ca565d..c7ed946 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -66,19 +66,49 @@ endif::git-format-patch[]
number of modified files, as well as number of added and deleted
lines.
---dirstat[=<limit>]::
- Output the distribution of relative amount of changes (number of lines added or
- removed) for each sub-directory. Directories with changes below
- a cut-off percent (3% by default) are not shown. The cut-off percent
- can be set with `--dirstat=<limit>`. Changes in a child directory are not
- counted for the parent directory, unless `--cumulative` is used.
+--dirstat[=<param1,param2,...>]::
+ Output the distribution of relative amount of changes for each
+ sub-directory. The behavior of `--dirstat` can be customized by
+ passing it a comma separated list of parameters.
+ The defaults are controlled by the `diff.dirstat` configuration
+ variable (see linkgit:git-config[1]).
+ The following parameters are available:
+
-Note that the `--dirstat` option computes the changes while ignoring
-the amount of pure code movements within a file. In other words,
-rearranging lines in a file is not counted as much as other changes.
-
---dirstat-by-file[=<limit>]::
- Same as `--dirstat`, but counts changed files instead of lines.
+--
+`changes`;;
+ Compute the dirstat numbers by counting the lines that have been
+ removed from the source, or added to the destination. This ignores
+ the amount of pure code movements within a file. In other words,
+ rearranging lines in a file is not counted as much as other changes.
+ This is the default behavior when no parameter is given.
+`lines`;;
+ Compute the dirstat numbers by doing the regular line-based diff
+ analysis, and summing the removed/added line counts. (For binary
+ files, count 64-byte chunks instead, since binary files have no
+ natural concept of lines). This is a more expensive `--dirstat`
+ behavior than the `changes` behavior, but it does count rearranged
+ lines within a file as much as other changes. The resulting output
+ is consistent with what you get from the other `--*stat` options.
+`files`;;
+ Compute the dirstat numbers by counting the number of files changed.
+ Each changed file counts equally in the dirstat analysis. This is
+ the computationally cheapest `--dirstat` behavior, since it does
+ not have to look at the file contents at all.
+`cumulative`;;
+ Count changes in a child directory for the parent directory as well.
+ Note that when using `cumulative`, the sum of the percentages
+ reported may exceed 100%. The default (non-cumulative) behavior can
+ be specified with the `noncumulative` parameter.
+<limit>;;
+ An integer parameter specifies a cut-off percent (3% by default).
+ Directories contributing less than this percentage of the changes
+ are not shown in the output.
+--
++
+Example: The following will count changed files, while ignoring
+directories with less than 10% of the total amount of changed files,
+and accumulating child directory counts in the parent directories:
+`--dirstat=files,10,cumulative`.
--summary::
Output a condensed summary of extended header information
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 35cb5d3..9c1d395 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -274,7 +274,8 @@ patch::
This lets you choose one path out of a 'status' like selection.
After choosing the path, it presents the diff between the index
and the working tree file and asks you if you want to stage
- the change of each hunk. You can say:
+ the change of each hunk. You can select one of the following
+ options and type return:
y - stage this hunk
n - do not stage this hunk
@@ -293,6 +294,9 @@ patch::
+
After deciding the fate for all hunks, if there is any hunk
that was chosen, the index is updated with the selected hunks.
++
+You can omit having to type return here, by setting the configuration
+variable `interactive.singlekey` to `true`.
diff::
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 1063f69..c0a96e6 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -12,7 +12,7 @@ SYNOPSIS
'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
-'git checkout' --patch [<tree-ish>] [--] [<paths>...]
+'git checkout' [-p|--patch] [<tree-ish>] [--] [<paths>...]
DESCRIPTION
-----------
@@ -45,7 +45,7 @@ $ git checkout <branch>
that is to say, the branch is not reset/created unless "git checkout" is
successful.
-'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...::
+'git checkout' [-p|--patch] [<tree-ish>] [--] <pathspec>...::
When <paths> or `--patch` are given, 'git checkout' does *not*
switch branches. It updates the named paths in the working tree
@@ -183,7 +183,8 @@ the conflicted merge in the specified paths.
working tree (and if a <tree-ish> was specified, the index).
+
This means that you can use `git checkout -p` to selectively discard
-edits from your current working tree.
+edits from your current working tree. See the ``Interactive Mode''
+section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
<branch>::
Branch to checkout; if it refers to a branch (i.e., a name that,
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index d0534b8..7951cb7 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -8,11 +8,12 @@ git-commit - Record changes to the repository
SYNOPSIS
--------
[verse]
-'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
- [(-c | -C | --fixup | --squash) <commit>] [-F <file> | -m <msg>]
- [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify]
- [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>]
- [--status | --no-status] [-i | -o] [--] [<file>...]
+'git commit' [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
+ [--dry-run] [(-c | -C | --fixup | --squash) <commit>]
+ [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
+ [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
+ [--date=<date>] [--cleanup=<mode>] [--status | --no-status]
+ [-i | -o] [--] [<file>...]
DESCRIPTION
-----------
@@ -39,9 +40,10 @@ The content to be added can be specified in several ways:
that have been removed from the working tree, and then perform the
actual commit;
-5. by using the --interactive switch with the 'commit' command to decide one
- by one which files should be part of the commit, before finalizing the
- operation. Currently, this is done by invoking 'git add --interactive'.
+5. by using the --interactive or --patch switches with the 'commit' command
+ to decide one by one which files or hunks should be part of the commit,
+ before finalizing the operation. See the ``Interactive Mode`` section of
+ linkgit:git-add[1] to learn how to operate these modes.
The `--dry-run` option can be used to obtain a
summary of what is included by any of the above for the next
@@ -59,6 +61,12 @@ OPTIONS
been modified and deleted, but new files you have not
told git about are not affected.
+-p::
+--patch::
+ Use the interactive patch selection interface to chose
+ which changes to commit. See linkgit:git-add[1] for
+ details.
+
-C <commit>::
--reuse-message=<commit>::
Take an existing commit object, and reuse the log message
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index c9ede79..a2a508d 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -26,7 +26,7 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
--no-reflogs is given) as heads.
--unreachable::
- Print out objects that exist but that aren't readable from any
+ Print out objects that exist but that aren't reachable from any
of the reference nodes.
--root::
@@ -76,7 +76,7 @@ It tests SHA1 and general object sanity, and it does full tracking of
the resulting reachability and everything else. It prints out any
corruption it finds (missing or bad objects), and if you use the
'--unreachable' flag it will also print out objects that exist but
-that aren't readable from any of the specified head nodes.
+that aren't reachable from any of the specified head nodes.
So for example
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 8481f9d..b2832fc 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' --patch [<commit>] [--] [<paths>...]
+'git reset' [--patch|-p] [<commit>] [--] [<paths>...]
'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
DESCRIPTION
@@ -39,8 +39,9 @@ working tree in one go.
and <commit> (defaults to HEAD). The chosen hunks are applied
in reverse to the index.
+
-This means that `git reset -p` is the opposite of `git add -p` (see
-linkgit:git-add[1]).
+This means that `git reset -p` is the opposite of `git add -p`, i.e.
+you can use it to selectively reset hunks. See the ``Interactive Mode''
+section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
'git reset' [--<mode>] [<commit>]::
This form resets the current branch head to <commit> and
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 79abc38..15f051f 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -13,7 +13,7 @@ SYNOPSIS
'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
-'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
+'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
'git stash' clear
'git stash' create
@@ -42,7 +42,7 @@ is also possible).
OPTIONS
-------
-save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
+save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. The <message> part is optional and gives
@@ -54,12 +54,13 @@ save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
If the `--keep-index` option is used, all changes already added to the
index are left intact.
+
-With `--patch`, you can interactively select hunks from in the diff
+With `--patch`, you can interactively select hunks from the diff
between HEAD and the working tree to be stashed. The stash entry is
constructed such that its index state is the same as the index state
of your repository, and its worktree contains only the changes you
selected interactively. The selected changes are then rolled back
-from your worktree.
+from your worktree. See the ``Interactive Mode'' section of
+linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+
The `--patch` option implies `--keep-index`. You can use
`--no-keep-index` to override this.
diff --git a/Documentation/git.txt b/Documentation/git.txt
index f1292fc..19d4def 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -44,9 +44,10 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.7.5.1/git.html[documentation for release 1.7.5.1]
+* link:v1.7.5.2/git.html[documentation for release 1.7.5.2]
* release notes for
+ link:RelNotes/1.7.5.2.txt[1.7.5.2],
link:RelNotes/1.7.5.1.txt[1.7.5.1],
link:RelNotes/1.7.5.txt[1.7.5].
diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt
index 8920258..861bd6f 100644
--- a/Documentation/merge-config.txt
+++ b/Documentation/merge-config.txt
@@ -16,6 +16,16 @@ merge.defaultToUpstream::
to their corresponding remote tracking branches, and the tips of
these tracking branches are merged.
+merge.ff::
+ By default, git does not create an extra merge commit when merging
+ a commit that is a descendant of the current commit. Instead, the
+ tip of the current branch is fast-forwarded. When set to `false`,
+ this variable tells git to create an extra merge commit in such
+ a case (equivalent to giving the `--no-ff` option from the command
+ line). When set to `only`, only such fast-forward merges are
+ allowed (equivalent to giving the `--ff-only` option from the
+ command line).
+
merge.log::
In addition to branch names, populate the log message with at
most the specified number of one-line descriptions from the
diff --git a/LGPL-2.1 b/LGPL-2.1
new file mode 100644
index 0000000..d38b1b9
--- /dev/null
+++ b/LGPL-2.1
@@ -0,0 +1,511 @@
+
+ While most of this project is under the GPL (see COPYING), the xdiff/
+ library and some libc code from compat/ are licensed under the
+ GNU LGPL, version 2.1 or (at your option) any later version and some
+ other files are under other licenses. Check the individual files to
+ be sure.
+
+----------------------------------------
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/Makefile b/Makefile
index ef57c1c..d09ee70 100644
--- a/Makefile
+++ b/Makefile
@@ -274,8 +274,7 @@ STRIP ?= strip
# mandir
# infodir
# htmldir
-# ETC_GITCONFIG (but not sysconfdir)
-# ETC_GITATTRIBUTES
+# sysconfdir
# can be specified as a relative path some/where/else;
# this is interpreted as relative to $(prefix) and "git" at
# runtime figures out where they are based on the path to the executable.
@@ -291,15 +290,8 @@ sharedir = $(prefix)/share
gitwebdir = $(sharedir)/gitweb
template_dir = share/git-core/templates
htmldir = share/doc/git-doc
-ifeq ($(prefix),/usr)
-sysconfdir = /etc
ETC_GITCONFIG = $(sysconfdir)/gitconfig
ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
-else
-sysconfdir = $(prefix)/etc
-ETC_GITCONFIG = etc/gitconfig
-ETC_GITATTRIBUTES = etc/gitattributes
-endif
lib = lib
# DESTDIR=
pathsep = :
@@ -423,8 +415,10 @@ TEST_PROGRAMS_NEED_X += test-date
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
TEST_PROGRAMS_NEED_X += test-genrandom
+TEST_PROGRAMS_NEED_X += test-index-version
TEST_PROGRAMS_NEED_X += test-line-buffer
TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS_NEED_X += test-obj-pool
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
@@ -435,8 +429,6 @@ TEST_PROGRAMS_NEED_X += test-string-pool
TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe
TEST_PROGRAMS_NEED_X += test-treap
-TEST_PROGRAMS_NEED_X += test-index-version
-TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
@@ -1195,6 +1187,14 @@ endif
-include config.mak.autogen
-include config.mak
+ifndef sysconfdir
+ifeq ($(prefix),/usr)
+sysconfdir = /etc
+else
+sysconfdir = etc
+endif
+endif
+
ifdef CHECK_HEADER_DEPENDENCIES
COMPUTE_HEADER_DEPENDENCIES =
USE_COMPUTED_HEADER_DEPENDENCIES =
@@ -1753,33 +1753,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
gitweb:
$(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
-ifdef JSMIN
-GITWEB_PROGRAMS += gitweb/static/gitweb.min.js
-GITWEB_JS = gitweb/static/gitweb.min.js
-else
-GITWEB_JS = gitweb/static/gitweb.js
-endif
-ifdef CSSMIN
-GITWEB_PROGRAMS += gitweb/static/gitweb.min.css
-GITWEB_CSS = gitweb/static/gitweb.min.css
-else
-GITWEB_CSS = gitweb/static/gitweb.css
-endif
-OTHER_PROGRAMS += gitweb/gitweb.cgi $(GITWEB_PROGRAMS)
-gitweb/gitweb.cgi: gitweb/gitweb.perl $(GITWEB_PROGRAMS)
- $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
-
-ifdef JSMIN
-gitweb/static/gitweb.min.js: gitweb/static/gitweb.js
- $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
-endif # JSMIN
-ifdef CSSMIN
-gitweb/static/gitweb.min.css: gitweb/static/gitweb.css
- $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) $(patsubst gitweb/%,%,$@)
-endif # CSSMIN
-
-
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/static/gitweb.css gitweb/static/gitweb.js
+git-instaweb: git-instaweb.sh gitweb
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
diff --git a/builtin/add.c b/builtin/add.c
index d39a6ab..c59b0c9 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -26,6 +26,27 @@ struct update_callback_data {
int add_errors;
};
+static int fix_unmerged_status(struct diff_filepair *p,
+ struct update_callback_data *data)
+{
+ if (p->status != DIFF_STATUS_UNMERGED)
+ return p->status;
+ if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode)
+ /*
+ * This is not an explicit add request, and the
+ * path is missing from the working tree (deleted)
+ */
+ return DIFF_STATUS_DELETED;
+ else
+ /*
+ * Either an explicit add request, or path exists
+ * in the working tree. An attempt to explicitly
+ * add a path that does not exist in the working tree
+ * will be caught as an error by the caller immediately.
+ */
+ return DIFF_STATUS_MODIFIED;
+}
+
static void update_callback(struct diff_queue_struct *q,
struct diff_options *opt, void *cbdata)
{
@@ -35,30 +56,9 @@ static void update_callback(struct diff_queue_struct *q,
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
- switch (p->status) {
+ switch (fix_unmerged_status(p, data)) {
default:
die(_("unexpected diff status %c"), p->status);
- case DIFF_STATUS_UNMERGED:
- /*
- * ADD_CACHE_IGNORE_REMOVAL is unset if "git
- * add -u" is calling us, In such a case, a
- * missing work tree file needs to be removed
- * if there is an unmerged entry at stage #2,
- * but such a diff record is followed by
- * another with DIFF_STATUS_DELETED (and if
- * there is no stage #2, we won't see DELETED
- * nor MODIFIED). We can simply continue
- * either way.
- */
- if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL))
- continue;
- /*
- * Otherwise, it is "git add path" is asking
- * to explicitly add it; we fall through. A
- * missing work tree file is an error and is
- * caught by add_file_to_index() in such a
- * case.
- */
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
if (add_file_to_index(&the_index, path, data->flags)) {
@@ -91,6 +91,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
data.flags = flags;
data.add_errors = 0;
rev.diffopt.format_callback_data = &data;
+ rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
return !!data.add_errors;
}
@@ -241,7 +242,7 @@ int run_add_interactive(const char *revision, const char *patch_mode,
return status;
}
-int interactive_add(int argc, const char **argv, const char *prefix)
+int interactive_add(int argc, const char **argv, const char *prefix, int patch)
{
const char **pathspec = NULL;
@@ -252,7 +253,7 @@ int interactive_add(int argc, const char **argv, const char *prefix)
}
return run_add_interactive(NULL,
- patch_interactive ? "--patch" : NULL,
+ patch ? "--patch" : NULL,
pathspec);
}
@@ -330,8 +331,8 @@ static struct option builtin_add_options[] = {
static int add_config(const char *var, const char *value, void *cb)
{
- if (!strcasecmp(var, "add.ignoreerrors") ||
- !strcasecmp(var, "add.ignore-errors")) {
+ if (!strcmp(var, "add.ignoreerrors") ||
+ !strcmp(var, "add.ignore-errors")) {
ignore_add_errors = git_config_bool(var, value);
return 0;
}
@@ -377,7 +378,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (patch_interactive)
add_interactive = 1;
if (add_interactive)
- exit(interactive_add(argc - 1, argv + 1, prefix));
+ exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
if (edit_interactive)
return(edit_patch(argc, argv, prefix));
diff --git a/builtin/apply.c b/builtin/apply.c
index 36e1507..530d4bb 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -43,6 +43,7 @@ static int apply = 1;
static int apply_in_reverse;
static int apply_with_reject;
static int apply_verbosely;
+static int allow_overlap;
static int no_add;
static const char *fake_ancestor;
static int line_termination = '\n';
@@ -2430,9 +2431,9 @@ static void update_image(struct image *img,
memcpy(img->line + applied_pos,
postimage->line,
postimage->nr * sizeof(*img->line));
- for (i = 0; i < postimage->nr; i++)
- img->line[applied_pos + i].flag |= LINE_PATCHED;
-
+ if (!allow_overlap)
+ for (i = 0; i < postimage->nr; i++)
+ img->line[applied_pos + i].flag |= LINE_PATCHED;
img->nr = nr;
}
@@ -3889,6 +3890,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
"don't expect at least one line of context"),
OPT_BOOLEAN(0, "reject", &apply_with_reject,
"leave the rejected hunks in corresponding *.rej files"),
+ OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
+ "allow overlapping hunks"),
OPT__VERBOSE(&apply_verbosely, "be verbose"),
OPT_BIT(0, "inaccurate-eof", &options,
"tolerate incorrectly detected missing new-line at the end of file",
diff --git a/builtin/commit.c b/builtin/commit.c
index 67757e9..5286432 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -83,7 +83,7 @@ static const char *template_file;
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
static char *fixup_message, *squash_message;
-static int all, edit_flag, also, interactive, only, amend, signoff;
+static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
@@ -152,6 +152,7 @@ static struct option builtin_commit_options[] = {
OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "interactively add changes"),
OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
@@ -336,18 +337,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
int fd;
struct string_list partial;
const char **pathspec = NULL;
+ char *old_index_env = NULL;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
- if (interactive) {
- if (interactive_add(argc, argv, prefix) != 0)
- die(_("interactive add failed"));
- if (read_cache_preload(NULL) < 0)
- die(_("index file corrupt"));
- commit_style = COMMIT_AS_IS;
- return get_index_file();
- }
if (*argv)
pathspec = get_pathspec(prefix, argv);
@@ -355,6 +349,33 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
if (read_cache_preload(pathspec) < 0)
die(_("index file corrupt"));
+ if (interactive) {
+ fd = hold_locked_index(&index_lock, 1);
+
+ refresh_cache_or_die(refresh_flags);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&index_lock))
+ die(_("unable to create temporary index"));
+
+ old_index_env = getenv(INDEX_ENVIRONMENT);
+ setenv(INDEX_ENVIRONMENT, index_lock.filename, 1);
+
+ if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
+ die(_("interactive add failed"));
+
+ if (old_index_env && *old_index_env)
+ setenv(INDEX_ENVIRONMENT, old_index_env, 1);
+ else
+ unsetenv(INDEX_ENVIRONMENT);
+
+ discard_cache();
+ read_cache_from(index_lock.filename);
+
+ commit_style = COMMIT_NORMAL;
+ return index_lock.filename;
+ }
+
/*
* Non partial, non as-is commit.
*
@@ -615,6 +636,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
const char *hook_arg1 = NULL;
const char *hook_arg2 = NULL;
int ident_shown = 0;
+ int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
@@ -681,6 +703,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (strbuf_read_file(&sb, template_file, 0) < 0)
die_errno(_("could not read '%s'"), template_file);
hook_arg1 = "template";
+ clean_message_contents = 0;
}
/*
@@ -708,7 +731,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (s->fp == NULL)
die_errno(_("could not open '%s'"), git_path(commit_editmsg));
- if (cleanup_mode != CLEANUP_NONE)
+ if (clean_message_contents)
stripspace(&sb, 0);
if (signoff) {
@@ -1041,8 +1064,11 @@ static int parse_and_validate_options(int argc, const char *argv[],
author_message_buffer = read_commit_message(author_message);
}
+ if (patch_interactive)
+ interactive = 1;
+
if (!!also + !!only + !!all + !!interactive > 1)
- die(_("Only one of --include/--only/--all/--interactive can be used."));
+ die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
if (argc == 0 && (also || (only && !amend)))
die(_("No paths with --include/--only does not make sense."));
if (argc == 0 && only && amend)
@@ -1064,8 +1090,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (all && argc > 0)
die(_("Paths with -a does not make sense."));
- else if (interactive && argc > 0)
- die(_("Paths with --interactive does not make sense."));
if (null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
diff --git a/builtin/log.c b/builtin/log.c
index d43ad3a..f621990 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -25,12 +25,15 @@ static const char *default_date_mode = NULL;
static int default_show_root = 1;
static int decoration_style;
+static int decoration_given;
static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
-static const char * const builtin_log_usage =
+static const char * const builtin_log_usage[] = {
"git log [<options>] [<since>..<until>] [[--] <path>...]\n"
- " or: git show [options] <object>...";
+ " or: git show [options] <object>...",
+ NULL
+};
static int parse_decoration_style(const char *var, const char *value)
{
@@ -49,6 +52,23 @@ static int parse_decoration_style(const char *var, const char *value)
return -1;
}
+static int decorate_callback(const struct option *opt, const char *arg, int unset)
+{
+ if (unset)
+ decoration_style = 0;
+ else if (arg)
+ decoration_style = parse_decoration_style("command line", arg);
+ else
+ decoration_style = DECORATE_SHORT_REFS;
+
+ if (decoration_style < 0)
+ die("invalid --decorate option: %s", arg);
+
+ decoration_given = 1;
+
+ return 0;
+}
+
static void cmd_log_init_defaults(struct rev_info *rev)
{
rev->abbrev = DEFAULT_ABBREV;
@@ -68,17 +88,28 @@ static void cmd_log_init_defaults(struct rev_info *rev)
static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
struct rev_info *rev, struct setup_revision_opt *opt)
{
- int i;
- int decoration_given = 0;
struct userformat_want w;
- /*
- * Check for -h before setup_revisions(), or "git log -h" will
- * fail when run without a git directory.
- */
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(builtin_log_usage);
+ int quiet = 0, source = 0;
+
+ const struct option builtin_log_options[] = {
+ OPT_BOOLEAN(0, "quiet", &quiet, "supress diff output"),
+ OPT_BOOLEAN(0, "source", &source, "show source"),
+ { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options",
+ PARSE_OPT_OPTARG, decorate_callback},
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_log_options, builtin_log_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
+
argc = setup_revisions(argc, argv, rev, opt);
+ /* Any arguments at this point are not recognized */
+ if (argc > 1)
+ die("unrecognized argument: %s", argv[1]);
+
memset(&w, 0, sizeof(w));
userformat_find_requirements(NULL, &w);
@@ -94,26 +125,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
if (rev->diffopt.pathspec.nr != 1)
usage("git logs can only follow renames on one pathname at a time");
}
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
- if (!strcmp(arg, "--decorate")) {
- decoration_style = DECORATE_SHORT_REFS;
- decoration_given = 1;
- } else if (!prefixcmp(arg, "--decorate=")) {
- const char *v = skip_prefix(arg, "--decorate=");
- decoration_style = parse_decoration_style(arg, v);
- if (decoration_style < 0)
- die(_("invalid --decorate option: %s"), arg);
- decoration_given = 1;
- } else if (!strcmp(arg, "--no-decorate")) {
- decoration_style = 0;
- } else if (!strcmp(arg, "--source")) {
- rev->show_source = 1;
- } else if (!strcmp(arg, "-h")) {
- usage(builtin_log_usage);
- } else
- die(_("unrecognized argument: %s"), arg);
- }
+
+ if (source)
+ rev->show_source = 1;
/*
* defeat log.decorate configuration interacting with --pretty=raw
diff --git a/builtin/merge.c b/builtin/merge.c
index d171c63..5a2a1eb 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -56,6 +56,7 @@ static size_t use_strategies_nr, use_strategies_alloc;
static const char **xopts;
static size_t xopts_nr, xopts_alloc;
static const char *branch;
+static char *branch_mergeoptions;
static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
@@ -503,26 +504,34 @@ cleanup:
strbuf_release(&bname);
}
+static void parse_branch_merge_options(char *bmo)
+{
+ const char **argv;
+ int argc;
+
+ if (!bmo)
+ return;
+ argc = split_cmdline(bmo, &argv);
+ if (argc < 0)
+ die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+ split_cmdline_strerror(argc));
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ argv[0] = "branch.*.mergeoptions";
+ parse_options(argc, argv, NULL, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(argv);
+}
+
static int git_merge_config(const char *k, const char *v, void *cb)
{
if (branch && !prefixcmp(k, "branch.") &&
!prefixcmp(k + 7, branch) &&
!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
- const char **argv;
- int argc;
- char *buf;
-
- buf = xstrdup(v);
- argc = split_cmdline(buf, &argv);
- if (argc < 0)
- die(_("Bad branch.%s.mergeoptions string: %s"), branch,
- split_cmdline_strerror(argc));
- argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
- memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
- argc++;
- parse_options(argc, argv, NULL, builtin_merge_options,
- builtin_merge_usage, 0);
- free(buf);
+ free(branch_mergeoptions);
+ branch_mergeoptions = xstrdup(v);
+ return 0;
}
if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
@@ -541,6 +550,15 @@ static int git_merge_config(const char *k, const char *v, void *cb)
if (is_bool && shortlog_len)
shortlog_len = DEFAULT_MERGE_LOG_LEN;
return 0;
+ } else if (!strcmp(k, "merge.ff")) {
+ int boolval = git_config_maybe_bool(k, v);
+ if (0 <= boolval) {
+ allow_fast_forward = boolval;
+ } else if (v && !strcmp(v, "only")) {
+ allow_fast_forward = 1;
+ fast_forward_only = 1;
+ } /* do not barf on values from future versions of git */
+ return 0;
} else if (!strcmp(k, "merge.defaulttoupstream")) {
default_to_upstream = git_config_bool(k, v);
return 0;
@@ -590,6 +608,14 @@ static void write_tree_trivial(unsigned char *sha1)
die(_("git write-tree failed to write a tree"));
}
+static const char *merge_argument(struct commit *commit)
+{
+ if (commit)
+ return sha1_to_hex(commit->object.sha1);
+ else
+ return EMPTY_TREE_SHA1_HEX;
+}
+
int try_merge_command(const char *strategy, size_t xopts_nr,
const char **xopts, struct commit_list *common,
const char *head_arg, struct commit_list *remotes)
@@ -610,11 +636,11 @@ int try_merge_command(const char *strategy, size_t xopts_nr,
args[i++] = s;
}
for (j = common; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = xstrdup(merge_argument(j->item));
args[i++] = "--";
args[i++] = head_arg;
for (j = remotes; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = xstrdup(merge_argument(j->item));
args[i] = NULL;
ret = run_command_v_opt(args, RUN_GIT_CMD);
strbuf_release(&buf);
@@ -1010,6 +1036,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (diff_use_color_default == -1)
diff_use_color_default = git_use_color_default;
+ if (branch_mergeoptions)
+ parse_branch_merge_options(branch_mergeoptions);
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 324a267..640ab64 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -23,8 +23,8 @@ static int verify_object(const unsigned char *sha1, const char *expected_type)
int ret = -1;
enum object_type type;
unsigned long size;
- const unsigned char *repl;
- void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+ void *buffer = read_sha1_file(sha1, &type, &size);
+ const unsigned char *repl = lookup_replace_object(sha1);
if (buffer) {
if (type == type_from_string(expected_type))
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 8235885..08213c7 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -12,74 +12,6 @@ static const char * const rerere_usage[] = {
NULL,
};
-/* these values are days */
-static int cutoff_noresolve = 15;
-static int cutoff_resolve = 60;
-
-static time_t rerere_created_at(const char *name)
-{
- struct stat st;
- return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static time_t rerere_last_used_at(const char *name)
-{
- struct stat st;
- return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static void unlink_rr_item(const char *name)
-{
- unlink(rerere_path(name, "thisimage"));
- unlink(rerere_path(name, "preimage"));
- unlink(rerere_path(name, "postimage"));
- rmdir(git_path("rr-cache/%s", name));
-}
-
-static int git_rerere_gc_config(const char *var, const char *value, void *cb)
-{
- if (!strcmp(var, "gc.rerereresolved"))
- cutoff_resolve = git_config_int(var, value);
- else if (!strcmp(var, "gc.rerereunresolved"))
- cutoff_noresolve = git_config_int(var, value);
- else
- return git_default_config(var, value, cb);
- return 0;
-}
-
-static void garbage_collect(struct string_list *rr)
-{
- struct string_list to_remove = STRING_LIST_INIT_DUP;
- DIR *dir;
- struct dirent *e;
- int i, cutoff;
- time_t now = time(NULL), then;
-
- git_config(git_rerere_gc_config, NULL);
- dir = opendir(git_path("rr-cache"));
- if (!dir)
- die_errno("unable to open rr-cache directory");
- while ((e = readdir(dir))) {
- if (is_dot_or_dotdot(e->d_name))
- continue;
-
- then = rerere_last_used_at(e->d_name);
- if (then) {
- cutoff = cutoff_resolve;
- } else {
- then = rerere_created_at(e->d_name);
- if (!then)
- continue;
- cutoff = cutoff_noresolve;
- }
- if (then < now - cutoff * 86400)
- string_list_append(&to_remove, e->d_name);
- }
- for (i = 0; i < to_remove.nr; i++)
- unlink_rr_item(to_remove.items[i].string);
- string_list_clear(&to_remove, 0);
-}
-
static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
{
int i;
@@ -148,14 +80,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
return 0;
if (!strcmp(argv[0], "clear")) {
- for (i = 0; i < merge_rr.nr; i++) {
- const char *name = (const char *)merge_rr.items[i].util;
- if (!has_rerere_resolution(name))
- unlink_rr_item(name);
- }
- unlink_or_warn(git_path("MERGE_RR"));
+ rerere_clear(&merge_rr);
} else if (!strcmp(argv[0], "gc"))
- garbage_collect(&merge_rr);
+ rerere_gc(&merge_rr);
else if (!strcmp(argv[0], "status"))
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
diff --git a/builtin/revert.c b/builtin/revert.c
index f697e66..1f27c63 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -408,8 +408,6 @@ static int do_pick_commit(void)
discard_cache();
if (!commit->parents) {
- if (action == REVERT)
- die (_("Cannot revert a root commit"));
parent = NULL;
}
else if (commit->parents->next) {
@@ -467,7 +465,7 @@ static int do_pick_commit(void)
strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
- if (commit->parents->next) {
+ if (commit->parents && commit->parents->next) {
strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
}
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 8b0911c..c1f6ddd 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -228,8 +228,11 @@ static void print_helper_status(struct ref *ref)
static int sideband_demux(int in, int out, void *data)
{
- int *fd = data;
- int ret = recv_sideband("send-pack", fd[0], out);
+ int *fd = data, ret;
+#ifdef NO_PTHREADS
+ close(fd[1]);
+#endif
+ ret = recv_sideband("send-pack", fd[0], out);
close(out);
return ret;
}
@@ -339,6 +342,10 @@ int send_pack(struct send_pack_args *args,
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
ref->status = REF_STATUS_NONE;
+ if (args->stateless_rpc)
+ close(out);
+ if (git_connection_is_socket(conn))
+ shutdown(fd[0], SHUT_WR);
if (use_sideband)
finish_async(&demux);
return -1;
diff --git a/builtin/tag.c b/builtin/tag.c
index b66b34a..ec926fc 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -352,11 +352,22 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
+{
+ if (name[0] == '-')
+ return CHECK_REF_FORMAT_ERROR;
+
+ strbuf_reset(sb);
+ strbuf_addf(sb, "refs/tags/%s", name);
+
+ return check_ref_format(sb->buf);
+}
+
int cmd_tag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
+ struct strbuf ref = STRBUF_INIT;
unsigned char object[20], prev[20];
- char ref[PATH_MAX];
const char *object_ref, *tag;
struct ref_lock *lock;
@@ -452,12 +463,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (get_sha1(object_ref, object))
die(_("Failed to resolve '%s' as a valid ref."), object_ref);
- if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
- die(_("tag name too long: %.*s..."), 50, tag);
- if (check_ref_format(ref))
+ if (strbuf_check_tag_ref(&ref, tag))
die(_("'%s' is not a valid tag name."), tag);
- if (!resolve_ref(ref, prev, 1, NULL))
+ if (!resolve_ref(ref.buf, prev, 1, NULL))
hashclr(prev);
else if (!force)
die(_("tag '%s' already exists"), tag);
@@ -466,14 +475,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
create_tag(object, tag, &buf, msg.given || msgfile,
sign, prev, object);
- lock = lock_any_ref_for_update(ref, prev, 0);
+ lock = lock_any_ref_for_update(ref.buf, prev, 0);
if (!lock)
- die(_("%s: cannot lock the ref"), ref);
+ die(_("%s: cannot lock the ref"), ref.buf);
if (write_ref_sha1(lock, object, NULL) < 0)
- die(_("%s: cannot update the ref"), ref);
+ die(_("%s: cannot update the ref"), ref.buf);
if (force && hashcmp(prev, object))
printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
strbuf_release(&buf);
+ strbuf_release(&ref);
return 0;
}
diff --git a/cache.h b/cache.h
index 2b34116..66ddfb7 100644
--- a/cache.h
+++ b/cache.h
@@ -606,7 +606,7 @@ enum eol {
#endif
};
-extern enum eol eol;
+extern enum eol core_eol;
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
@@ -756,13 +756,23 @@ char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
int offset_1st_component(const char *path);
-/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
-extern int sha1_object_info(const unsigned char *, unsigned long *);
-extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement);
+/* object replacement */
+#define READ_SHA1_FILE_REPLACE 1
+extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
- return read_sha1_file_repl(sha1, type, size, NULL);
+ return read_sha1_file_extended(sha1, type, size, READ_SHA1_FILE_REPLACE);
+}
+extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1);
+static inline const unsigned char *lookup_replace_object(const unsigned char *sha1)
+{
+ if (!read_replace_refs)
+ return sha1;
+ return do_lookup_replace_object(sha1);
}
+
+/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
+extern int sha1_object_info(const unsigned char *, unsigned long *);
extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
@@ -975,6 +985,7 @@ extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
extern char *git_getpass(const char *prompt);
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn);
+extern int git_connection_is_socket(struct child_process *conn);
extern int path_match(const char *path, int nr, char **match);
struct extra_have_objects {
int nr, alloc;
diff --git a/commit.h b/commit.h
index b3c3bb7..3114bd1 100644
--- a/commit.h
+++ b/commit.h
@@ -145,8 +145,6 @@ struct commit_graft *read_graft_line(char *buf, int len);
int register_commit_graft(struct commit_graft *, int);
struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
-const unsigned char *lookup_replace_object(const unsigned char *sha1);
-
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
@@ -161,7 +159,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit **, int);
-extern int interactive_add(int argc, const char **argv, const char *prefix);
+extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
extern int run_add_interactive(const char *revision, const char *patch_mode,
const char **pathspec);
diff --git a/config.c b/config.c
index 5f9ec28..671c8df 100644
--- a/config.c
+++ b/config.c
@@ -583,7 +583,7 @@ static int git_default_core_config(const char *var, const char *value)
if (!strcmp(var, "core.autocrlf")) {
if (value && !strcasecmp(value, "input")) {
- if (eol == EOL_CRLF)
+ if (core_eol == EOL_CRLF)
return error("core.autocrlf=input conflicts with core.eol=crlf");
auto_crlf = AUTO_CRLF_INPUT;
return 0;
@@ -603,14 +603,14 @@ static int git_default_core_config(const char *var, const char *value)
if (!strcmp(var, "core.eol")) {
if (value && !strcasecmp(value, "lf"))
- eol = EOL_LF;
+ core_eol = EOL_LF;
else if (value && !strcasecmp(value, "crlf"))
- eol = EOL_CRLF;
+ core_eol = EOL_CRLF;
else if (value && !strcasecmp(value, "native"))
- eol = EOL_NATIVE;
+ core_eol = EOL_NATIVE;
else
- eol = EOL_UNSET;
- if (eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
+ core_eol = EOL_UNSET;
+ if (core_eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
return error("core.autocrlf=input conflicts with core.eol=crlf");
return 0;
}
diff --git a/connect.c b/connect.c
index 57dc20c..2119c3f 100644
--- a/connect.c
+++ b/connect.c
@@ -395,26 +395,28 @@ static int git_use_proxy(const char *host)
return (git_proxy_command && *git_proxy_command);
}
-static void git_proxy_connect(int fd[2], char *host)
+static struct child_process *git_proxy_connect(int fd[2], char *host)
{
const char *port = STR(DEFAULT_GIT_PORT);
- const char *argv[4];
- struct child_process proxy;
+ const char **argv;
+ struct child_process *proxy;
get_host_and_port(&host, &port);
+ argv = xmalloc(sizeof(*argv) * 4);
argv[0] = git_proxy_command;
argv[1] = host;
argv[2] = port;
argv[3] = NULL;
- memset(&proxy, 0, sizeof(proxy));
- proxy.argv = argv;
- proxy.in = -1;
- proxy.out = -1;
- if (start_command(&proxy))
+ proxy = xcalloc(1, sizeof(*proxy));
+ proxy->argv = argv;
+ proxy->in = -1;
+ proxy->out = -1;
+ if (start_command(proxy))
die("cannot start proxy %s", argv[0]);
- fd[0] = proxy.out; /* read from proxy stdout */
- fd[1] = proxy.in; /* write to proxy stdin */
+ fd[0] = proxy->out; /* read from proxy stdout */
+ fd[1] = proxy->in; /* write to proxy stdin */
+ return proxy;
}
#define MAX_CMD_LEN 1024
@@ -455,7 +457,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
char *host, *path;
char *end;
int c;
- struct child_process *conn;
+ struct child_process *conn = &no_fork;
enum protocol protocol = PROTO_LOCAL;
int free_path = 0;
char *port = NULL;
@@ -540,7 +542,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
*/
char *target_host = xstrdup(host);
if (git_use_proxy(host))
- git_proxy_connect(fd, host);
+ conn = git_proxy_connect(fd, host);
else
git_tcp_connect(fd, host, flags);
/*
@@ -558,7 +560,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
free(url);
if (free_path)
free(path);
- return &no_fork;
+ return conn;
}
conn = xcalloc(1, sizeof(*conn));
@@ -607,10 +609,15 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
return conn;
}
+int git_connection_is_socket(struct child_process *conn)
+{
+ return conn == &no_fork;
+}
+
int finish_connect(struct child_process *conn)
{
int code;
- if (!conn || conn == &no_fork)
+ if (!conn || git_connection_is_socket(conn))
return 0;
code = finish_command(conn);
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index b81f444..bb8d7d0 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -628,12 +628,12 @@ __git_refs_remotes ()
__git_remotes ()
{
local i ngoff IFS=$'\n' d="$(__gitdir)"
- shopt -q nullglob || ngoff=1
- shopt -s nullglob
+ __git_shopt -q nullglob || ngoff=1
+ __git_shopt -s nullglob
for i in "$d/remotes"/*; do
echo ${i#$d/remotes/}
done
- [ "$ngoff" ] && shopt -u nullglob
+ [ "$ngoff" ] && __git_shopt -u nullglob
for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
i="${i#remote.}"
echo "${i/.url*/}"
@@ -2608,6 +2608,10 @@ _git ()
if [[ -n ${ZSH_VERSION-} ]]; then
emulate -L bash
setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
fi
local cur words cword prev
@@ -2659,6 +2663,10 @@ _gitk ()
if [[ -n ${ZSH_VERSION-} ]]; then
emulate -L bash
setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
fi
local cur words cword prev
@@ -2699,7 +2707,7 @@ complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
fi
if [[ -n ${ZSH_VERSION-} ]]; then
- shopt () {
+ __git_shopt () {
local option
if [ $# -ne 2 ]; then
echo "USAGE: $0 (-q|-s|-u) <option>" >&2
@@ -2722,4 +2730,8 @@ if [[ -n ${ZSH_VERSION-} ]]; then
return 1
esac
}
+else
+ __git_shopt () {
+ shopt "$@"
+ }
fi
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index 78e5b3a..98d2aee 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -474,6 +474,47 @@ class Command:
self.usage = "usage: %prog [options]"
self.needsGit = True
+class P4UserMap:
+ def __init__(self):
+ self.userMapFromPerforceServer = False
+
+ def getUserCacheFilename(self):
+ home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
+ return home + "/.gitp4-usercache.txt"
+
+ def getUserMapFromPerforceServer(self):
+ if self.userMapFromPerforceServer:
+ return
+ self.users = {}
+ self.emails = {}
+
+ for output in p4CmdList("users"):
+ if not output.has_key("User"):
+ continue
+ self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+ self.emails[output["Email"]] = output["User"]
+
+
+ s = ''
+ for (key, val) in self.users.items():
+ s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
+
+ open(self.getUserCacheFilename(), "wb").write(s)
+ self.userMapFromPerforceServer = True
+
+ def loadUserMapFromCache(self):
+ self.users = {}
+ self.userMapFromPerforceServer = False
+ try:
+ cache = open(self.getUserCacheFilename(), "rb")
+ lines = cache.readlines()
+ cache.close()
+ for line in lines:
+ entry = line.strip().split("\t")
+ self.users[entry[0]] = entry[1]
+ except IOError:
+ self.getUserMapFromPerforceServer()
+
class P4Debug(Command):
def __init__(self):
Command.__init__(self)
@@ -554,13 +595,16 @@ class P4RollBack(Command):
return True
-class P4Submit(Command):
+class P4Submit(Command, P4UserMap):
def __init__(self):
Command.__init__(self)
+ P4UserMap.__init__(self)
self.options = [
optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--origin", dest="origin"),
optparse.make_option("-M", dest="detectRenames", action="store_true"),
+ # preserve the user, requires relevant p4 permissions
+ optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
@@ -568,7 +612,9 @@ class P4Submit(Command):
self.origin = ""
self.detectRenames = False
self.verbose = False
+ self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
self.isWindows = (platform.system() == "Windows")
+ self.myP4UserId = None
def check(self):
if len(p4CmdList("opened ...")) > 0:
@@ -602,6 +648,99 @@ class P4Submit(Command):
return result
+ def p4UserForCommit(self,id):
+ # Return the tuple (perforce user,git email) for a given git commit id
+ self.getUserMapFromPerforceServer()
+ gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
+ gitEmail = gitEmail.strip()
+ if not self.emails.has_key(gitEmail):
+ return (None,gitEmail)
+ else:
+ return (self.emails[gitEmail],gitEmail)
+
+ def checkValidP4Users(self,commits):
+ # check if any git authors cannot be mapped to p4 users
+ for id in commits:
+ (user,email) = self.p4UserForCommit(id)
+ if not user:
+ msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
+ if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
+ print "%s" % msg
+ else:
+ die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
+
+ def lastP4Changelist(self):
+ # Get back the last changelist number submitted in this client spec. This
+ # then gets used to patch up the username in the change. If the same
+ # client spec is being used by multiple processes then this might go
+ # wrong.
+ results = p4CmdList("client -o") # find the current client
+ client = None
+ for r in results:
+ if r.has_key('Client'):
+ client = r['Client']
+ break
+ if not client:
+ die("could not get client spec")
+ results = p4CmdList("changes -c %s -m 1" % client)
+ for r in results:
+ if r.has_key('change'):
+ return r['change']
+ die("Could not get changelist number for last submit - cannot patch up user details")
+
+ def modifyChangelistUser(self, changelist, newUser):
+ # fixup the user field of a changelist after it has been submitted.
+ changes = p4CmdList("change -o %s" % changelist)
+ if len(changes) != 1:
+ die("Bad output from p4 change modifying %s to user %s" %
+ (changelist, newUser))
+
+ c = changes[0]
+ if c['User'] == newUser: return # nothing to do
+ c['User'] = newUser
+ input = marshal.dumps(c)
+
+ result = p4CmdList("change -f -i", stdin=input)
+ for r in result:
+ if r.has_key('code'):
+ if r['code'] == 'error':
+ die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
+ if r.has_key('data'):
+ print("Updated user field for changelist %s to %s" % (changelist, newUser))
+ return
+ die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
+
+ def canChangeChangelists(self):
+ # check to see if we have p4 admin or super-user permissions, either of
+ # which are required to modify changelists.
+ results = p4CmdList("protects %s" % self.depotPath)
+ for r in results:
+ if r.has_key('perm'):
+ if r['perm'] == 'admin':
+ return 1
+ if r['perm'] == 'super':
+ return 1
+ return 0
+
+ def p4UserId(self):
+ if self.myP4UserId:
+ return self.myP4UserId
+
+ results = p4CmdList("user -o")
+ for r in results:
+ if r.has_key('User'):
+ self.myP4UserId = r['User']
+ return r['User']
+ die("Could not find your p4 user id")
+
+ def p4UserIsMe(self, p4User):
+ # return True if the given p4 user is actually me
+ me = self.p4UserId()
+ if not p4User or p4User != me:
+ return False
+ else:
+ return True
+
def prepareSubmitTemplate(self):
# remove lines in the Files section that show changes to files outside the depot path we're committing into
template = ""
@@ -631,6 +770,8 @@ class P4Submit(Command):
def applyCommit(self, id):
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+ (p4User, gitEmail) = self.p4UserForCommit(id)
+
if not self.detectRenames:
# If not explicitly set check the config variable
self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
@@ -748,6 +889,10 @@ class P4Submit(Command):
if self.interactive:
submitTemplate = self.prepareLogMessage(template, logMessage)
+
+ if self.preserveUser:
+ submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
+
if os.environ.has_key("P4DIFF"):
del(os.environ["P4DIFF"])
diff = ""
@@ -764,6 +909,11 @@ class P4Submit(Command):
newdiff += "+" + line
f.close()
+ if self.checkAuthorship and not self.p4UserIsMe(p4User):
+ submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
+ submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n"
+ submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n"
+
separatorLine = "######## everything below this line is just the diff #######\n"
[handle, fileName] = tempfile.mkstemp()
@@ -781,8 +931,13 @@ class P4Submit(Command):
editor = read_pipe("git var GIT_EDITOR").strip()
system(editor + " " + fileName)
+ if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+ checkModTime = False
+ else:
+ checkModTime = True
+
response = "y"
- if os.stat(fileName).st_mtime <= mtime:
+ if checkModTime and (os.stat(fileName).st_mtime <= mtime):
response = "x"
while response != "y" and response != "n":
response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
@@ -795,6 +950,14 @@ class P4Submit(Command):
if self.isWindows:
submitTemplate = submitTemplate.replace("\r\n", "\n")
p4_write_pipe("submit -i", submitTemplate)
+
+ if self.preserveUser:
+ if p4User:
+ # Get last changelist number. Cannot easily get it from
+ # the submit command output as the output is unmarshalled.
+ changelist = self.lastP4Changelist()
+ self.modifyChangelistUser(changelist, p4User)
+
else:
for f in editedFiles:
p4_system("revert \"%s\"" % f);
@@ -831,6 +994,10 @@ class P4Submit(Command):
if len(self.origin) == 0:
self.origin = upstream
+ if self.preserveUser:
+ if not self.canChangeChangelists():
+ die("Cannot preserve user names without p4 super-user or admin permissions")
+
if self.verbose:
print "Origin branch is " + self.origin
@@ -858,6 +1025,14 @@ class P4Submit(Command):
commits.append(line.strip())
commits.reverse()
+ if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"):
+ self.checkAuthorship = False
+ else:
+ self.checkAuthorship = True
+
+ if self.preserveUser:
+ self.checkValidP4Users(commits)
+
while len(commits) > 0:
commit = commits[0]
commits = commits[1:]
@@ -877,11 +1052,12 @@ class P4Submit(Command):
return True
-class P4Sync(Command):
+class P4Sync(Command, P4UserMap):
delete_actions = ( "delete", "move/delete", "purge" )
def __init__(self):
Command.__init__(self)
+ P4UserMap.__init__(self)
self.options = [
optparse.make_option("--branch", dest="branch"),
optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
@@ -1236,41 +1412,6 @@ class P4Sync(Command):
print ("Tag %s does not match with change %s: file count is different."
% (labelDetails["label"], change))
- def getUserCacheFilename(self):
- home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
- return home + "/.gitp4-usercache.txt"
-
- def getUserMapFromPerforceServer(self):
- if self.userMapFromPerforceServer:
- return
- self.users = {}
-
- for output in p4CmdList("users"):
- if not output.has_key("User"):
- continue
- self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
-
-
- s = ''
- for (key, val) in self.users.items():
- s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
-
- open(self.getUserCacheFilename(), "wb").write(s)
- self.userMapFromPerforceServer = True
-
- def loadUserMapFromCache(self):
- self.users = {}
- self.userMapFromPerforceServer = False
- try:
- cache = open(self.getUserCacheFilename(), "rb")
- lines = cache.readlines()
- cache.close()
- for line in lines:
- entry = line.strip().split("\t")
- self.users[entry[0]] = entry[1]
- except IOError:
- self.getUserMapFromPerforceServer()
-
def getLabels(self):
self.labels = {}
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
index e09da44..caa4bb3 100644
--- a/contrib/fast-import/git-p4.txt
+++ b/contrib/fast-import/git-p4.txt
@@ -110,6 +110,12 @@ is not your current git branch you can also pass that as an argument:
You can override the reference branch with the --origin=mysourcebranch option.
+The Perforce changelists will be created with the user who ran git-p4. If you
+use --preserve-user then git-p4 will attempt to create Perforce changelists
+with the Perforce user corresponding to the git commit author. You need to
+have sufficient permissions within Perforce, and the git users need to have
+Perforce accounts. Permissions can be granted using 'p4 protect'.
+
If a submit fails you may have to "p4 resolve" and submit manually. You can
continue importing the remaining changes with
@@ -196,6 +202,36 @@ able to find the relevant client. This client spec will be used to
both filter the files cloned by git and set the directory layout as
specified in the client (this implies --keep-path style semantics).
+git-p4.skipSubmitModTimeCheck
+
+ git config [--global] git-p4.skipSubmitModTimeCheck false
+
+If true, submit will not check if the p4 change template has been modified.
+
+git-p4.preserveUser
+
+ git config [--global] git-p4.preserveUser false
+
+If true, attempt to preserve user names by modifying the p4 changelists. See
+the "--preserve-user" submit option.
+
+git-p4.allowMissingPerforceUsers
+
+ git config [--global] git-p4.allowMissingP4Users false
+
+If git-p4 is setting the perforce user for a commit (--preserve-user) then
+if there is no perforce user corresponding to the git author, git-p4 will
+stop. With allowMissingPerforceUsers set to true, git-p4 will use the
+current user (i.e. the behavior without --preserve-user) and carry on with
+the perforce commit.
+
+git-p4.skipUserNameCheck
+
+ git config [--global] git-p4.skipUserNameCheck false
+
+When submitting, git-p4 checks that the git commits are authored by the current
+p4 user, and warns if they are not. This disables the check.
+
Implementation Details...
=========================
diff --git a/convert.c b/convert.c
index 7eb51b1..efc7e07 100644
--- a/convert.c
+++ b/convert.c
@@ -12,7 +12,7 @@
* translation when the "text" attribute or "auto_crlf" option is set.
*/
-enum action {
+enum crlf_action {
CRLF_GUESS = -1,
CRLF_BINARY = 0,
CRLF_TEXT,
@@ -94,9 +94,9 @@ static int is_binary(unsigned long size, struct text_stat *stats)
return 0;
}
-static enum eol determine_output_conversion(enum action action)
+static enum eol output_eol(enum crlf_action crlf_action)
{
- switch (action) {
+ switch (crlf_action) {
case CRLF_BINARY:
return EOL_UNSET;
case CRLF_CRLF:
@@ -113,19 +113,19 @@ static enum eol determine_output_conversion(enum action action)
return EOL_CRLF;
else if (auto_crlf == AUTO_CRLF_INPUT)
return EOL_LF;
- else if (eol == EOL_UNSET)
+ else if (core_eol == EOL_UNSET)
return EOL_NATIVE;
}
- return eol;
+ return core_eol;
}
-static void check_safe_crlf(const char *path, enum action action,
+static void check_safe_crlf(const char *path, enum crlf_action crlf_action,
struct text_stat *stats, enum safe_crlf checksafe)
{
if (!checksafe)
return;
- if (determine_output_conversion(action) == EOL_LF) {
+ if (output_eol(crlf_action) == EOL_LF) {
/*
* CRLFs would not be restored by checkout:
* check if we'd remove CRLFs
@@ -136,7 +136,7 @@ static void check_safe_crlf(const char *path, enum action action,
else /* i.e. SAFE_CRLF_FAIL */
die("CRLF would be replaced by LF in %s.", path);
}
- } else if (determine_output_conversion(action) == EOL_CRLF) {
+ } else if (output_eol(crlf_action) == EOL_CRLF) {
/*
* CRLFs would be added by checkout:
* check if we have "naked" LFs
@@ -188,18 +188,19 @@ static int has_cr_in_index(const char *path)
}
static int crlf_to_git(const char *path, const char *src, size_t len,
- struct strbuf *buf, enum action action, enum safe_crlf checksafe)
+ struct strbuf *buf,
+ enum crlf_action crlf_action, enum safe_crlf checksafe)
{
struct text_stat stats;
char *dst;
- if (action == CRLF_BINARY ||
- (action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
+ if (crlf_action == CRLF_BINARY ||
+ (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE) || !len)
return 0;
gather_stats(src, len, &stats);
- if (action == CRLF_AUTO || action == CRLF_GUESS) {
+ if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
/*
* We're currently not going to even try to convert stuff
* that has bare CR characters. Does anybody do that crazy
@@ -214,7 +215,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
if (is_binary(len, &stats))
return 0;
- if (action == CRLF_GUESS) {
+ if (crlf_action == CRLF_GUESS) {
/*
* If the file in the index has any CR in it, do not convert.
* This is the new safer autocrlf handling.
@@ -224,7 +225,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
}
}
- check_safe_crlf(path, action, &stats, checksafe);
+ check_safe_crlf(path, crlf_action, &stats, checksafe);
/* Optimization: No CR? Nothing to convert, regardless. */
if (!stats.cr)
@@ -234,7 +235,7 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
if (strbuf_avail(buf) + buf->len < len)
strbuf_grow(buf, len - buf->len);
dst = buf->buf;
- if (action == CRLF_AUTO || action == CRLF_GUESS) {
+ if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
/*
* If we guessed, we already know we rejected a file with
* lone CR, and we can strip a CR without looking at what
@@ -257,12 +258,12 @@ static int crlf_to_git(const char *path, const char *src, size_t len,
}
static int crlf_to_worktree(const char *path, const char *src, size_t len,
- struct strbuf *buf, enum action action)
+ struct strbuf *buf, enum crlf_action crlf_action)
{
char *to_free = NULL;
struct text_stat stats;
- if (!len || determine_output_conversion(action) != EOL_CRLF)
+ if (!len || output_eol(crlf_action) != EOL_CRLF)
return 0;
gather_stats(src, len, &stats);
@@ -275,8 +276,8 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
if (stats.lf == stats.crlf)
return 0;
- if (action == CRLF_AUTO || action == CRLF_GUESS) {
- if (action == CRLF_GUESS) {
+ if (crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS) {
+ if (crlf_action == CRLF_GUESS) {
/* If we have any CR or CRLF line endings, we do not touch it */
/* This is the new safer autocrlf-handling */
if (stats.cr > 0 || stats.crlf > 0)
@@ -474,30 +475,6 @@ static int read_convert_config(const char *var, const char *value, void *cb)
return 0;
}
-static void setup_convert_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_text;
- static struct git_attr *attr_crlf;
- static struct git_attr *attr_eol;
- static struct git_attr *attr_ident;
- static struct git_attr *attr_filter;
-
- if (!attr_text) {
- attr_text = git_attr("text");
- attr_crlf = git_attr("crlf");
- attr_eol = git_attr("eol");
- attr_ident = git_attr("ident");
- attr_filter = git_attr("filter");
- user_convert_tail = &user_convert;
- git_config(read_convert_config, NULL);
- }
- check[0].attr = attr_crlf;
- check[1].attr = attr_ident;
- check[2].attr = attr_filter;
- check[3].attr = attr_eol;
- check[4].attr = attr_text;
-}
-
static int count_ident(const char *cp, unsigned long size)
{
/*
@@ -715,7 +692,7 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
return !!ATTR_TRUE(value);
}
-static enum action determine_action(enum action text_attr, enum eol eol_attr)
+static enum crlf_action input_crlf_action(enum crlf_action text_attr, enum eol eol_attr)
{
if (text_attr == CRLF_BINARY)
return CRLF_BINARY;
@@ -726,66 +703,83 @@ static enum action determine_action(enum action text_attr, enum eol eol_attr)
return text_attr;
}
+struct conv_attrs {
+ struct convert_driver *drv;
+ enum crlf_action crlf_action;
+ enum eol eol_attr;
+ int ident;
+};
+
+static const char *conv_attr_name[] = {
+ "crlf", "ident", "filter", "eol", "text",
+};
+#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
+
+static void convert_attrs(struct conv_attrs *ca, const char *path)
+{
+ int i;
+ static struct git_attr_check ccheck[NUM_CONV_ATTRS];
+
+ if (!ccheck[0].attr) {
+ for (i = 0; i < NUM_CONV_ATTRS; i++)
+ ccheck[i].attr = git_attr(conv_attr_name[i]);
+ user_convert_tail = &user_convert;
+ git_config(read_convert_config, NULL);
+ }
+
+ if (!git_checkattr(path, NUM_CONV_ATTRS, ccheck)) {
+ ca->crlf_action = git_path_check_crlf(path, ccheck + 4);
+ if (ca->crlf_action == CRLF_GUESS)
+ ca->crlf_action = git_path_check_crlf(path, ccheck + 0);
+ ca->ident = git_path_check_ident(path, ccheck + 1);
+ ca->drv = git_path_check_convert(path, ccheck + 2);
+ ca->eol_attr = git_path_check_eol(path, ccheck + 3);
+ } else {
+ ca->drv = NULL;
+ ca->crlf_action = CRLF_GUESS;
+ ca->eol_attr = EOL_UNSET;
+ ca->ident = 0;
+ }
+}
+
int convert_to_git(const char *path, const char *src, size_t len,
struct strbuf *dst, enum safe_crlf checksafe)
{
- struct git_attr_check check[5];
- enum action action = CRLF_GUESS;
- enum eol eol_attr = EOL_UNSET;
- int ident = 0, ret = 0;
+ int ret = 0;
const char *filter = NULL;
+ struct conv_attrs ca;
- setup_convert_check(check);
- if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
- struct convert_driver *drv;
- action = git_path_check_crlf(path, check + 4);
- if (action == CRLF_GUESS)
- action = git_path_check_crlf(path, check + 0);
- ident = git_path_check_ident(path, check + 1);
- drv = git_path_check_convert(path, check + 2);
- eol_attr = git_path_check_eol(path, check + 3);
- if (drv && drv->clean)
- filter = drv->clean;
- }
+ convert_attrs(&ca, path);
+ if (ca.drv)
+ filter = ca.drv->clean;
ret |= apply_filter(path, src, len, dst, filter);
if (ret) {
src = dst->buf;
len = dst->len;
}
- action = determine_action(action, eol_attr);
- ret |= crlf_to_git(path, src, len, dst, action, checksafe);
+ ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+ ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe);
if (ret) {
src = dst->buf;
len = dst->len;
}
- return ret | ident_to_git(path, src, len, dst, ident);
+ return ret | ident_to_git(path, src, len, dst, ca.ident);
}
static int convert_to_working_tree_internal(const char *path, const char *src,
size_t len, struct strbuf *dst,
int normalizing)
{
- struct git_attr_check check[5];
- enum action action = CRLF_GUESS;
- enum eol eol_attr = EOL_UNSET;
- int ident = 0, ret = 0;
+ int ret = 0;
const char *filter = NULL;
+ struct conv_attrs ca;
- setup_convert_check(check);
- if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
- struct convert_driver *drv;
- action = git_path_check_crlf(path, check + 4);
- if (action == CRLF_GUESS)
- action = git_path_check_crlf(path, check + 0);
- ident = git_path_check_ident(path, check + 1);
- drv = git_path_check_convert(path, check + 2);
- eol_attr = git_path_check_eol(path, check + 3);
- if (drv && drv->smudge)
- filter = drv->smudge;
- }
+ convert_attrs(&ca, path);
+ if (ca.drv)
+ filter = ca.drv->smudge;
- ret |= ident_to_worktree(path, src, len, dst, ident);
+ ret |= ident_to_worktree(path, src, len, dst, ca.ident);
if (ret) {
src = dst->buf;
len = dst->len;
@@ -795,8 +789,8 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
* is a smudge filter. The filter might expect CRLFs.
*/
if (filter || !normalizing) {
- action = determine_action(action, eol_attr);
- ret |= crlf_to_worktree(path, src, len, dst, action);
+ ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+ ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action);
if (ret) {
src = dst->buf;
len = dst->len;
diff --git a/diff.c b/diff.c
index feced34..ba5f7aa 100644
--- a/diff.c
+++ b/diff.c
@@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
static int diff_no_prefix;
+static int diff_dirstat_permille_default = 30;
static struct diff_options default_diff_options;
static char diff_colors[][COLOR_MAXLEN] = {
@@ -66,6 +67,58 @@ static int parse_diff_color_slot(const char *var, int ofs)
return -1;
}
+static int parse_dirstat_params(struct diff_options *options, const char *params,
+ struct strbuf *errmsg)
+{
+ const char *p = params;
+ int p_len, ret = 0;
+
+ while (*p) {
+ p_len = strchrnul(p, ',') - p;
+ if (!memcmp(p, "changes", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "lines", p_len)) {
+ DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "files", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+ DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+ } else if (!memcmp(p, "noncumulative", p_len)) {
+ DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
+ } else if (!memcmp(p, "cumulative", p_len)) {
+ DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
+ } else if (isdigit(*p)) {
+ char *end;
+ int permille = strtoul(p, &end, 10) * 10;
+ if (*end == '.' && isdigit(*++end)) {
+ /* only use first digit */
+ permille += *end - '0';
+ /* .. and ignore any further digits */
+ while (isdigit(*++end))
+ ; /* nothing */
+ }
+ if (end - p == p_len)
+ options->dirstat_permille = permille;
+ else {
+ strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"),
+ p_len, p);
+ ret++;
+ }
+ } else {
+ strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"),
+ p_len, p);
+ ret++;
+ }
+
+ p += p_len;
+
+ if (*p)
+ p++; /* more parameters, swallow separator */
+ }
+ return ret;
+}
+
static int git_config_rename(const char *var, const char *value)
{
if (!value)
@@ -145,6 +198,17 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (!strcmp(var, "diff.dirstat")) {
+ struct strbuf errmsg = STRBUF_INIT;
+ default_diff_options.dirstat_permille = diff_dirstat_permille_default;
+ if (parse_dirstat_params(&default_diff_options, value, &errmsg))
+ warning(_("Found errors in 'diff.dirstat' config variable:\n%s"),
+ errmsg.buf);
+ strbuf_release(&errmsg);
+ diff_dirstat_permille_default = default_diff_options.dirstat_permille;
+ return 0;
+ }
+
if (!prefixcmp(var, "submodule."))
return parse_submodule_config_option(var, value);
@@ -1455,7 +1519,7 @@ struct dirstat_file {
struct dirstat_dir {
struct dirstat_file *files;
- int alloc, nr, percent, cumulative;
+ int alloc, nr, permille, cumulative;
};
static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
@@ -1502,12 +1566,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
* under this directory (sources == 1).
*/
if (baselen && sources != 1) {
- int permille = this_dir * 1000 / changed;
- if (permille) {
- int percent = permille / 10;
- if (percent >= dir->percent) {
+ if (this_dir) {
+ int permille = this_dir * 1000 / changed;
+ if (permille >= dir->permille) {
fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix,
- percent, permille % 10, baselen, base);
+ permille / 10, permille % 10, baselen, base);
if (!dir->cumulative)
return 0;
}
@@ -1533,7 +1596,7 @@ static void show_dirstat(struct diff_options *options)
dir.files = NULL;
dir.alloc = 0;
dir.nr = 0;
- dir.percent = options->dirstat_percent;
+ dir.permille = options->dirstat_permille;
dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
changed = 0;
@@ -1622,6 +1685,50 @@ found_damage:
gather_dirstat(options, &dir, changed, "", 0);
}
+static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options)
+{
+ int i;
+ unsigned long changed;
+ struct dirstat_dir dir;
+
+ if (data->nr == 0)
+ return;
+
+ dir.files = NULL;
+ dir.alloc = 0;
+ dir.nr = 0;
+ dir.permille = options->dirstat_permille;
+ dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
+
+ changed = 0;
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+ unsigned long damage = file->added + file->deleted;
+ if (file->is_binary)
+ /*
+ * binary files counts bytes, not lines. Must find some
+ * way to normalize binary bytes vs. textual lines.
+ * The following heuristic assumes that there are 64
+ * bytes per "line".
+ * This is stupid and ugly, but very cheap...
+ */
+ damage = (damage + 63) / 64;
+ ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
+ dir.files[dir.nr].name = file->name;
+ dir.files[dir.nr].changed = damage;
+ changed += damage;
+ dir.nr++;
+ }
+
+ /* This can happen even with many files, if everything was renames */
+ if (!changed)
+ return;
+
+ /* Show all directories with more than x% of the changes */
+ qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+ gather_dirstat(options, &dir, changed, "", 0);
+}
+
static void free_diffstat_info(struct diffstat_t *diffstat)
{
int i;
@@ -2891,7 +2998,7 @@ void diff_setup(struct diff_options *options)
options->line_termination = '\n';
options->break_opt = -1;
options->rename_limit = -1;
- options->dirstat_percent = 3;
+ options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3;
options->change = diff_change;
@@ -3149,6 +3256,21 @@ static int stat_opt(struct diff_options *options, const char **av)
return argcount;
}
+static int parse_dirstat_opt(struct diff_options *options, const char *params)
+{
+ struct strbuf errmsg = STRBUF_INIT;
+ if (parse_dirstat_params(options, params, &errmsg))
+ die(_("Failed to parse --dirstat/-X option parameter:\n%s"),
+ errmsg.buf);
+ strbuf_release(&errmsg);
+ /*
+ * The caller knows a dirstat-related option is given from the command
+ * line; allow it to say "return this_function();"
+ */
+ options->output_format |= DIFF_FORMAT_DIRSTAT;
+ return 1;
+}
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
@@ -3168,15 +3290,19 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->output_format |= DIFF_FORMAT_NUMSTAT;
else if (!strcmp(arg, "--shortstat"))
options->output_format |= DIFF_FORMAT_SHORTSTAT;
- else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- else if (!strcmp(arg, "--cumulative")) {
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
- } else if (opt_arg(arg, 0, "dirstat-by-file",
- &options->dirstat_percent)) {
- options->output_format |= DIFF_FORMAT_DIRSTAT;
- DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+ else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
+ return parse_dirstat_opt(options, "");
+ else if (!prefixcmp(arg, "-X"))
+ return parse_dirstat_opt(options, arg + 2);
+ else if (!prefixcmp(arg, "--dirstat="))
+ return parse_dirstat_opt(options, arg + 10);
+ else if (!strcmp(arg, "--cumulative"))
+ return parse_dirstat_opt(options, "cumulative");
+ else if (!strcmp(arg, "--dirstat-by-file"))
+ return parse_dirstat_opt(options, "files");
+ else if (!prefixcmp(arg, "--dirstat-by-file=")) {
+ parse_dirstat_opt(options, "files");
+ return parse_dirstat_opt(options, arg + 18);
}
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
@@ -4023,6 +4149,7 @@ void diff_flush(struct diff_options *options)
struct diff_queue_struct *q = &diff_queued_diff;
int i, output_format = options->output_format;
int separator = 0;
+ int dirstat_by_line = 0;
/*
* Order: raw, stat, summary, patch
@@ -4043,7 +4170,11 @@ void diff_flush(struct diff_options *options)
separator++;
}
- if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
+ if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE))
+ dirstat_by_line = 1;
+
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) ||
+ dirstat_by_line) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -4058,10 +4189,12 @@ void diff_flush(struct diff_options *options)
show_stats(&diffstat, options);
if (output_format & DIFF_FORMAT_SHORTSTAT)
show_shortstats(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIRSTAT)
+ show_dirstat_by_line(&diffstat, options);
free_diffstat_info(&diffstat);
separator++;
}
- if (output_format & DIFF_FORMAT_DIRSTAT)
+ if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line)
show_dirstat(options);
if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
diff --git a/diff.h b/diff.h
index d83e53e..adb40ba 100644
--- a/diff.h
+++ b/diff.h
@@ -78,6 +78,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
+#define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28)
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
@@ -114,7 +115,7 @@ struct diff_options {
int needed_rename_limit;
int degraded_cc_to_c;
int show_rename_progress;
- int dirstat_percent;
+ int dirstat_permille;
int setup;
int abbrev;
const char *prefix;
diff --git a/environment.c b/environment.c
index 40185bc..94d58fd 100644
--- a/environment.c
+++ b/environment.c
@@ -42,8 +42,8 @@ const char *editor_program;
const char *askpass_program;
const char *excludes_file;
enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
-int read_replace_refs = 1;
-enum eol eol = EOL_UNSET;
+int read_replace_refs = 1; /* NEEDSWORK: rename to use_replace_refs */
+enum eol core_eol = EOL_UNSET;
enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
diff --git a/fast-import.c b/fast-import.c
index 3e4e655..78d9786 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3135,11 +3135,11 @@ static int parse_one_feature(const char *feature, int from_stream)
option_export_marks(feature + 13);
} else if (!strcmp(feature, "cat-blob")) {
; /* Don't die - this feature is supported */
- } else if (!prefixcmp(feature, "relative-marks")) {
+ } else if (!strcmp(feature, "relative-marks")) {
relative_marks_paths = 1;
- } else if (!prefixcmp(feature, "no-relative-marks")) {
+ } else if (!strcmp(feature, "no-relative-marks")) {
relative_marks_paths = 0;
- } else if (!prefixcmp(feature, "force")) {
+ } else if (!strcmp(feature, "force")) {
force_update = 1;
} else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
; /* do nothing; we have the feature */
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index a329c5a..4f08fe7 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -705,7 +705,7 @@ sub add_untracked_cmd {
sub run_git_apply {
my $cmd = shift;
my $fh;
- open $fh, '| git ' . $cmd;
+ open $fh, '| git ' . $cmd . " --recount --allow-overlap";
print $fh @_;
return close $fh;
}
@@ -1050,7 +1050,7 @@ EOF
sub diff_applies {
my $fh;
- return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check',
+ return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
map { @{$_->{TEXT}} } @_);
}
@@ -1139,7 +1139,7 @@ EOF
sub apply_patch {
my $cmd = shift;
- my $ret = run_git_apply $cmd . ' --recount', @_;
+ my $ret = run_git_apply $cmd, @_;
if (!$ret) {
print STDERR @_;
}
@@ -1148,17 +1148,17 @@ sub apply_patch {
sub apply_patch_for_checkout_commit {
my $reverse = shift;
- my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_;
- my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_;
+ my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
+ my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
if ($applies_worktree && $applies_index) {
- run_git_apply 'apply '.$reverse.' --cached --recount', @_;
- run_git_apply 'apply '.$reverse.' --recount', @_;
+ run_git_apply 'apply '.$reverse.' --cached', @_;
+ run_git_apply 'apply '.$reverse, @_;
return 1;
} elsif (!$applies_index) {
print colored $error_color, "The selected hunks do not apply to the index!\n";
if (prompt_yesno "Apply them to the worktree anyway? ") {
- return run_git_apply 'apply '.$reverse.' --recount', @_;
+ return run_git_apply 'apply '.$reverse, @_;
} else {
print colored $error_color, "Nothing was applied.\n";
return 0;
@@ -1366,14 +1366,13 @@ sub patch_update_file {
next;
}
elsif ($line =~ /^q/i) {
- while ($ix < $num) {
- if (!defined $hunk[$ix]{USE}) {
- $hunk[$ix]{USE} = 0;
+ for ($i = 0; $i < $num; $i++) {
+ if (!defined $hunk[$i]{USE}) {
+ $hunk[$i]{USE} = 0;
}
- $ix++;
}
$quit = 1;
- next;
+ last;
}
elsif ($line =~ m|^/(.*)|) {
my $regex = $1;
diff --git a/git-svn.perl b/git-svn.perl
index 0fd2fd2..7849cfc 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -5765,7 +5765,7 @@ sub cmd_show_log {
my (@k, $c, $d, $stat);
my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
while (<$log>) {
- if (/^${esc_color}commit (- )?($::sha1_short)/o) {
+ if (/^${esc_color}commit (?:- )?($::sha1_short)/o) {
my $cmt = $1;
if ($c && cmt_showable($c) && $c->{r} != $r_last) {
$r_last = $c->{r};
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index f8db40a..66eadb4 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -412,20 +412,23 @@ our %feature = (
'override' => 0,
'default' => []},
- # Allow gitweb scan project content tags described in ctags/
- # of project repository, and display the popular Web 2.0-ish
- # "tag cloud" near the project list. Note that this is something
- # COMPLETELY different from the normal Git tags.
+ # Allow gitweb scan project content tags of project repository,
+ # and display the popular Web 2.0-ish "tag cloud" near the projects
+ # list. Note that this is something COMPLETELY different from the
+ # normal Git tags.
# gitweb by itself can show existing tags, but it does not handle
- # tagging itself; you need an external application for that.
- # For an example script, check Girocco's cgi/tagproj.cgi.
+ # tagging itself; you need to do it externally, outside gitweb.
+ # The format is described in git_get_project_ctags() subroutine.
# You may want to install the HTML::TagCloud Perl module to get
# a pretty tag cloud instead of just a list of tags.
# To enable system wide have in $GITWEB_CONFIG
- # $feature{'ctags'}{'default'} = ['path_to_tag_script'];
+ # $feature{'ctags'}{'default'} = [1];
# Project specific override is not supported.
+
+ # In the future whether ctags editing is enabled might depend
+ # on the value, but using 1 should always mean no editing of ctags.
'ctags' => {
'override' => 0,
'default' => [0]},
@@ -703,6 +706,7 @@ our @cgi_param_mapping = (
snapshot_format => "sf",
extra_options => "opt",
search_use_regexp => "sr",
+ ctag => "by_tag",
# this must be last entry (for manipulation from JavaScript)
javascript => "js"
);
@@ -2572,23 +2576,66 @@ sub git_get_project_description {
return $descr;
}
+# supported formats:
+# * $GIT_DIR/ctags/<tagname> file (in 'ctags' subdirectory)
+# - if its contents is a number, use it as tag weight,
+# - otherwise add a tag with weight 1
+# * $GIT_DIR/ctags file, each line is a tag (with weight 1)
+# the same value multiple times increases tag weight
+# * `gitweb.ctag' multi-valued repo config variable
sub git_get_project_ctags {
- my $path = shift;
+ my $project = shift;
my $ctags = {};
- $git_dir = "$projectroot/$path";
- opendir my $dh, "$git_dir/ctags"
- or return $ctags;
- foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
- open my $ct, '<', $_ or next;
- my $val = <$ct>;
- chomp $val;
- close $ct;
- my $ctag = $_; $ctag =~ s#.*/##;
- $ctags->{$ctag} = $val;
+ $git_dir = "$projectroot/$project";
+ if (opendir my $dh, "$git_dir/ctags") {
+ my @files = grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh);
+ foreach my $tagfile (@files) {
+ open my $ct, '<', $tagfile
+ or next;
+ my $val = <$ct>;
+ chomp $val if $val;
+ close $ct;
+
+ (my $ctag = $tagfile) =~ s#.*/##;
+ if ($val =~ /\d+/) {
+ $ctags->{$ctag} = $val;
+ } else {
+ $ctags->{$ctag} = 1;
+ }
+ }
+ closedir $dh;
+
+ } elsif (open my $fh, '<', "$git_dir/ctags") {
+ while (my $line = <$fh>) {
+ chomp $line;
+ $ctags->{$line}++ if $line;
+ }
+ close $fh;
+
+ } else {
+ my $taglist = config_to_multi(git_get_project_config('ctag'));
+ foreach my $tag (@$taglist) {
+ $ctags->{$tag}++;
+ }
}
- closedir $dh;
- $ctags;
+
+ return $ctags;
+}
+
+# return hash, where keys are content tags ('ctags'),
+# and values are sum of weights of given tag in every project
+sub git_gather_all_ctags {
+ my $projects = shift;
+ my $ctags = {};
+
+ foreach my $p (@$projects) {
+ foreach my $ct (keys %{$p->{'ctags'}}) {
+ $ctags->{$ct} += $p->{'ctags'}->{$ct};
+ }
+ }
+
+ return $ctags;
}
sub git_populate_project_tagcloud {
@@ -2606,33 +2653,49 @@ sub git_populate_project_tagcloud {
}
my $cloud;
+ my $matched = $cgi->param('by_tag');
if (eval { require HTML::TagCloud; 1; }) {
$cloud = HTML::TagCloud->new;
- foreach (sort keys %ctags_lc) {
+ foreach my $ctag (sort keys %ctags_lc) {
# Pad the title with spaces so that the cloud looks
# less crammed.
- my $title = $ctags_lc{$_}->{topname};
+ my $title = esc_html($ctags_lc{$ctag}->{topname});
$title =~ s/ /&nbsp;/g;
$title =~ s/^/&nbsp;/g;
$title =~ s/$/&nbsp;/g;
- $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
+ if (defined $matched && $matched eq $ctag) {
+ $title = qq(<span class="match">$title</span>);
+ }
+ $cloud->add($title, href(project=>undef, ctag=>$ctag),
+ $ctags_lc{$ctag}->{count});
}
} else {
- $cloud = \%ctags_lc;
+ $cloud = {};
+ foreach my $ctag (keys %ctags_lc) {
+ my $title = esc_html($ctags_lc{$ctag}->{topname}, -nbsp=>1);
+ if (defined $matched && $matched eq $ctag) {
+ $title = qq(<span class="match">$title</span>);
+ }
+ $cloud->{$ctag}{count} = $ctags_lc{$ctag}->{count};
+ $cloud->{$ctag}{ctag} =
+ $cgi->a({-href=>href(project=>undef, ctag=>$ctag)}, $title);
+ }
}
- $cloud;
+ return $cloud;
}
sub git_show_project_tagcloud {
my ($cloud, $count) = @_;
- print STDERR ref($cloud)."..\n";
if (ref $cloud eq 'HTML::TagCloud') {
return $cloud->html_and_css($count);
} else {
- my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
- return '<p align="center">' . join (', ', map {
- $cgi->a({-href=>"$home_link?by_tag=$_"}, $cloud->{$_}->{topname})
- } splice(@tags, 0, $count)) . '</p>';
+ my @tags = sort { $cloud->{$a}->{'count'} <=> $cloud->{$b}->{'count'} } keys %$cloud;
+ return
+ '<div id="htmltagcloud"'.($project ? '' : ' align="center"').'>' .
+ join (', ', map {
+ $cloud->{$_}->{'ctag'}
+ } splice(@tags, 0, $count)) .
+ '</div>';
}
}
@@ -2651,21 +2714,23 @@ sub git_get_project_url_list {
}
sub git_get_projects_list {
- my ($filter) = @_;
+ my $filter = shift || '';
my @list;
- $filter ||= '';
$filter =~ s/\.git$//;
- my $check_forks = gitweb_check_feature('forks');
-
if (-d $projects_list) {
# search in directory
- my $dir = $projects_list . ($filter ? "/$filter" : '');
+ my $dir = $projects_list;
# remove the trailing "/"
$dir =~ s!/+$!!;
- my $pfxlen = length("$dir");
- my $pfxdepth = ($dir =~ tr!/!!);
+ my $pfxlen = length("$projects_list");
+ my $pfxdepth = ($projects_list =~ tr!/!!);
+ # when filtering, search only given subdirectory
+ if ($filter) {
+ $dir .= "/$filter";
+ $dir =~ s!/+$!!;
+ }
File::Find::find({
follow_fast => 1, # follow symbolic links
@@ -2680,14 +2745,14 @@ sub git_get_projects_list {
# only directories can be git repositories
return unless (-d $_);
# don't traverse too deep (Find is super slow on os x)
+ # $project_maxdepth excludes depth of $projectroot
if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
$File::Find::prune = 1;
return;
}
- my $subdir = substr($File::Find::name, $pfxlen + 1);
+ my $path = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- my $path = ($filter ? "$filter/" : '') . $subdir;
if (check_export_ok("$projectroot/$path")) {
push @list, { path => $path };
$File::Find::prune = 1;
@@ -2700,7 +2765,6 @@ sub git_get_projects_list {
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
- my %paths;
open my $fd, '<', $projects_list or return;
PROJECT:
while (my $line = <$fd>) {
@@ -2711,32 +2775,9 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if ($filter ne '') {
- # looking for forks;
- my $pfx = substr($path, 0, length($filter));
- if ($pfx ne $filter) {
- next PROJECT;
- }
- my $sfx = substr($path, length($filter));
- if ($sfx !~ /^\/.*\.git$/) {
- next PROJECT;
- }
- } elsif ($check_forks) {
- PATH:
- foreach my $filter (keys %paths) {
- # looking for forks;
- my $pfx = substr($path, 0, length($filter));
- if ($pfx ne $filter) {
- next PATH;
- }
- my $sfx = substr($path, length($filter));
- if ($sfx !~ /^\/.*\.git$/) {
- next PATH;
- }
- # is a fork, don't include it in
- # the list
- next PROJECT;
- }
+ # if $filter is rpovided, check if $path begins with $filter
+ if ($filter && $path !~ m!^\Q$filter\E/!) {
+ next;
}
if (check_export_ok("$projectroot/$path")) {
my $pr = {
@@ -2744,8 +2785,6 @@ sub git_get_projects_list {
owner => to_utf8($owner),
};
push @list, $pr;
- (my $forks_path = $path) =~ s/\.git$//;
- $paths{$forks_path}++;
}
}
close $fd;
@@ -2753,6 +2792,98 @@ sub git_get_projects_list {
return @list;
}
+# written with help of Tree::Trie module (Perl Artistic License, GPL compatibile)
+# as side effects it sets 'forks' field to list of forks for forked projects
+sub filter_forks_from_projects_list {
+ my $projects = shift;
+
+ my %trie; # prefix tree of directories (path components)
+ # generate trie out of those directories that might contain forks
+ foreach my $pr (@$projects) {
+ my $path = $pr->{'path'};
+ $path =~ s/\.git$//; # forks of 'repo.git' are in 'repo/' directory
+ next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
+ next unless ($path); # skip '.git' repository: tests, git-instaweb
+ next unless (-d $path); # containing directory exists
+ $pr->{'forks'} = []; # there can be 0 or more forks of project
+
+ # add to trie
+ my @dirs = split('/', $path);
+ # walk the trie, until either runs out of components or out of trie
+ my $ref = \%trie;
+ while (scalar @dirs &&
+ exists($ref->{$dirs[0]})) {
+ $ref = $ref->{shift @dirs};
+ }
+ # create rest of trie structure from rest of components
+ foreach my $dir (@dirs) {
+ $ref = $ref->{$dir} = {};
+ }
+ # create end marker, store $pr as a data
+ $ref->{''} = $pr if (!exists $ref->{''});
+ }
+
+ # filter out forks, by finding shortest prefix match for paths
+ my @filtered;
+ PROJECT:
+ foreach my $pr (@$projects) {
+ # trie lookup
+ my $ref = \%trie;
+ DIR:
+ foreach my $dir (split('/', $pr->{'path'})) {
+ if (exists $ref->{''}) {
+ # found [shortest] prefix, is a fork - skip it
+ push @{$ref->{''}{'forks'}}, $pr;
+ next PROJECT;
+ }
+ if (!exists $ref->{$dir}) {
+ # not in trie, cannot have prefix, not a fork
+ push @filtered, $pr;
+ next PROJECT;
+ }
+ # If the dir is there, we just walk one step down the trie.
+ $ref = $ref->{$dir};
+ }
+ # we ran out of trie
+ # (shouldn't happen: it's either no match, or end marker)
+ push @filtered, $pr;
+ }
+
+ return @filtered;
+}
+
+# note: fill_project_list_info must be run first,
+# for 'descr_long' and 'ctags' to be filled
+sub search_projects_list {
+ my ($projlist, %opts) = @_;
+ my $tagfilter = $opts{'tagfilter'};
+ my $searchtext = $opts{'searchtext'};
+
+ return @$projlist
+ unless ($tagfilter || $searchtext);
+
+ my @projects;
+ PROJECT:
+ foreach my $pr (@$projlist) {
+
+ if ($tagfilter) {
+ next unless ref($pr->{'ctags'}) eq 'HASH';
+ next unless
+ grep { lc($_) eq lc($tagfilter) } keys %{$pr->{'ctags'}};
+ }
+
+ if ($searchtext) {
+ next unless
+ $pr->{'path'} =~ /$searchtext/ ||
+ $pr->{'descr_long'} =~ /$searchtext/;
+ }
+
+ push @projects, $pr;
+ }
+
+ return @projects;
+}
+
our $gitweb_project_owner = undef;
sub git_get_project_list_from_file {
@@ -4742,7 +4873,7 @@ sub git_patchset_body {
# project in the list, removing invalid projects from returned list
# NOTE: modifies $projlist, but does not remove entries from it
sub fill_project_list_info {
- my ($projlist, $check_forks) = @_;
+ my $projlist = shift;
my @projects;
my $show_ctags = gitweb_check_feature('ctags');
@@ -4762,23 +4893,36 @@ sub fill_project_list_info {
if (!defined $pr->{'owner'}) {
$pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
}
- if ($check_forks) {
- my $pname = $pr->{'path'};
- if (($pname =~ s/\.git$//) &&
- ($pname !~ /\/$/) &&
- (-d "$projectroot/$pname")) {
- $pr->{'forks'} = "-d $projectroot/$pname";
- } else {
- $pr->{'forks'} = 0;
- }
+ if ($show_ctags) {
+ $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
}
- $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
push @projects, $pr;
}
return @projects;
}
+sub sort_projects_list {
+ my ($projlist, $order) = @_;
+ my @projects;
+
+ my %order_info = (
+ project => { key => 'path', type => 'str' },
+ descr => { key => 'descr_long', type => 'str' },
+ owner => { key => 'owner', type => 'str' },
+ age => { key => 'age', type => 'num' }
+ );
+ my $oi = $order_info{$order};
+ return @$projlist unless defined $oi;
+ if ($oi->{'type'} eq 'str') {
+ @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist;
+ } else {
+ @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist;
+ }
+
+ return @projects;
+}
+
# print 'sort by' <th> element, generating 'sort by $name' replay link
# if that order is not selected
sub print_sort_th {
@@ -4805,36 +4949,42 @@ sub format_sort_th {
sub git_project_list_body {
# actually uses global variable $project
my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+ my @projects = @$projlist;
my $check_forks = gitweb_check_feature('forks');
- my @projects = fill_project_list_info($projlist, $check_forks);
+ my $show_ctags = gitweb_check_feature('ctags');
+ my $tagfilter = $show_ctags ? $cgi->param('by_tag') : undef;
+ $check_forks = undef
+ if ($tagfilter || $searchtext);
+
+ # filtering out forks before filling info allows to do less work
+ @projects = filter_forks_from_projects_list(\@projects)
+ if ($check_forks);
+ @projects = fill_project_list_info(\@projects);
+ # searching projects require filling to be run before it
+ @projects = search_projects_list(\@projects,
+ 'searchtext' => $searchtext,
+ 'tagfilter' => $tagfilter)
+ if ($tagfilter || $searchtext);
$order ||= $default_projects_order;
$from = 0 unless defined $from;
$to = $#projects if (!defined $to || $#projects < $to);
- my %order_info = (
- project => { key => 'path', type => 'str' },
- descr => { key => 'descr_long', type => 'str' },
- owner => { key => 'owner', type => 'str' },
- age => { key => 'age', type => 'num' }
- );
- my $oi = $order_info{$order};
- if ($oi->{'type'} eq 'str') {
- @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;
- } else {
- @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;
+ # short circuit
+ if ($from > $to) {
+ print "<center>\n".
+ "<b>No such projects found</b><br />\n".
+ "Click ".$cgi->a({-href=>href(project=>undef)},"here")." to view all projects<br />\n".
+ "</center>\n<br />\n";
+ return;
}
- my $show_ctags = gitweb_check_feature('ctags');
+ @projects = sort_projects_list(\@projects, $order);
+
if ($show_ctags) {
- my %ctags;
- foreach my $p (@projects) {
- foreach my $ct (keys %{$p->{'ctags'}}) {
- $ctags{$ct} += $p->{'ctags'}->{$ct};
- }
- }
- my $cloud = git_populate_project_tagcloud(\%ctags);
+ my $ctags = git_gather_all_ctags(\@projects);
+ my $cloud = git_populate_project_tagcloud($ctags);
print git_show_project_tagcloud($cloud, 64);
}
@@ -4852,32 +5002,26 @@ sub git_project_list_body {
"</tr>\n";
}
my $alternate = 1;
- my $tagfilter = $cgi->param('by_tag');
for (my $i = $from; $i <= $to; $i++) {
my $pr = $projects[$i];
- next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};
- next if $searchtext and not $pr->{'path'} =~ /$searchtext/
- and not $pr->{'descr_long'} =~ /$searchtext/;
- # Weed out forks or non-matching entries of search
- if ($check_forks) {
- my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#;
- $forkbase="^$forkbase" if $forkbase;
- next if not $searchtext and not $tagfilter and $show_ctags
- and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe
- }
-
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
+
if ($check_forks) {
print "<td>";
if ($pr->{'forks'}) {
- print "<!-- $pr->{'forks'} -->\n";
- print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+ my $nforks = scalar @{$pr->{'forks'}};
+ if ($nforks > 0) {
+ print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks"),
+ -title => "$nforks forks"}, "+");
+ } else {
+ print $cgi->span({-title => "$nforks forks"}, "+");
+ }
}
print "</td>\n";
}
@@ -5357,7 +5501,10 @@ sub git_forks {
}
sub git_project_index {
- my @projects = git_get_projects_list($project);
+ my @projects = git_get_projects_list();
+ if (!@projects) {
+ die_error(404, "No projects found");
+ }
print $cgi->header(
-type => 'text/plain',
@@ -5399,7 +5546,11 @@ sub git_summary {
my $check_forks = gitweb_check_feature('forks');
if ($check_forks) {
+ # find forks of a project
@forklist = git_get_projects_list($project);
+ # filter out forks of forks
+ @forklist = filter_forks_from_projects_list(\@forklist)
+ if (@forklist);
}
git_header_html();
@@ -5428,13 +5579,14 @@ sub git_summary {
my $show_ctags = gitweb_check_feature('ctags');
if ($show_ctags) {
my $ctags = git_get_project_ctags($project);
- my $cloud = git_populate_project_tagcloud($ctags);
- print "<tr id=\"metadata_ctags\"><td>Content tags:<br />";
- print "</td>\n<td>" unless %$ctags;
- print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>";
- print "</td>\n<td>" if %$ctags;
- print git_show_project_tagcloud($cloud, 48);
- print "</td></tr>";
+ if (%$ctags) {
+ # without ability to add tags, don't show if there are none
+ my $cloud = git_populate_project_tagcloud($ctags);
+ print "<tr id=\"metadata_ctags\">" .
+ "<td>content tags</td>" .
+ "<td>".git_show_project_tagcloud($cloud, 48)."</td>" .
+ "</tr>\n";
+ }
}
print "</table>\n";
@@ -7319,6 +7471,9 @@ sub git_atom {
sub git_opml {
my @list = git_get_projects_list();
+ if (!@list) {
+ die_error(404, "No projects found");
+ }
print $cgi->header(
-type => 'text/xml',
diff --git a/http-push.c b/http-push.c
index d18346c..28bfe76 100644
--- a/http-push.c
+++ b/http-push.c
@@ -169,7 +169,7 @@ enum dav_header_flag {
DAV_HEADER_TIMEOUT = (1u << 2)
};
-static char *xml_entities(char *s)
+static char *xml_entities(const char *s)
{
struct strbuf buf = STRBUF_INIT;
while (*s) {
@@ -197,6 +197,34 @@ static char *xml_entities(char *s)
return strbuf_detach(&buf, NULL);
}
+static void curl_setup_http_get(CURL *curl, const char *url,
+ const char *custom_req)
+{
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+}
+
+static void curl_setup_http(CURL *curl, const char *url,
+ const char *custom_req, struct buffer *buffer,
+ curl_write_callback write_fn)
+{
+ curl_easy_setopt(curl, CURLOPT_PUT, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_INFILE, buffer);
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(curl, CURLOPT_IOCTLDATA, &buffer);
+#endif
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn);
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
+}
+
static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
{
struct strbuf buf = STRBUF_INIT;
@@ -272,11 +300,8 @@ static void start_mkcol(struct transfer_request *request)
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+ curl_setup_http_get(slot->curl, request->url, DAV_MKCOL);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
if (start_active_slot(slot)) {
request->slot = slot;
@@ -395,19 +420,8 @@ static void start_put(struct transfer_request *request)
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+ curl_setup_http(slot->curl, request->url, DAV_PUT,
+ &request->buffer, fwrite_null);
if (start_active_slot(slot)) {
request->slot = slot;
@@ -427,13 +441,10 @@ static void start_move(struct transfer_request *request)
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
+ curl_setup_http_get(slot->curl, request->url, DAV_MOVE);
dav_headers = curl_slist_append(dav_headers, request->dest);
dav_headers = curl_slist_append(dav_headers, "Overwrite: T");
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
if (start_active_slot(slot)) {
request->slot = slot;
@@ -458,10 +469,7 @@ static int refresh_lock(struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+ curl_setup_http_get(slot->curl, lock->url, DAV_LOCK);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
if (start_active_slot(slot)) {
@@ -797,7 +805,7 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
}
}
-static void one_remote_ref(char *refname);
+static void one_remote_ref(const char *refname);
static void
xml_start_tag(void *userData, const char *name, const char **atts)
@@ -876,10 +884,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
ep[1] = '\0';
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_setup_http_get(slot->curl, url, DAV_MKCOL);
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result != CURLE_OK &&
@@ -909,19 +914,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+ curl_setup_http(slot->curl, url, DAV_LOCK, &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
lock = xcalloc(1, sizeof(*lock));
lock->timeout = -1;
@@ -987,9 +982,7 @@ static int unlock_remote(struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
+ curl_setup_http_get(slot->curl, lock->url, DAV_UNLOCK);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
if (start_active_slot(slot)) {
@@ -1167,19 +1160,10 @@ static void remote_ls(const char *path, int flags,
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+ curl_setup_http(slot->curl, url, DAV_PROPFIND,
+ &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1250,19 +1234,10 @@ static int locking_available(void)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+ curl_setup_http(slot->curl, repo->url, DAV_PROPFIND,
+ &out_buffer, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1436,19 +1411,9 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_setup_http(slot->curl, lock->url, DAV_PUT,
+ &out_buffer, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1471,7 +1436,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
static struct ref *remote_refs;
-static void one_remote_ref(char *refname)
+static void one_remote_ref(const char *refname)
{
struct ref *ref;
struct object *obj;
@@ -1572,19 +1537,9 @@ static void update_remote_info_refs(struct remote_lock *lock)
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer);
-#endif
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_setup_http(slot->curl, lock->url, DAV_PUT,
+ &buffer, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
- curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
if (start_active_slot(slot)) {
run_active_slot(slot);
@@ -1660,7 +1615,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha
return (merge_bases && !merge_bases->next && merge_bases->item == branch);
}
-static int delete_remote_branch(char *pattern, int force)
+static int delete_remote_branch(const char *pattern, int force)
{
struct ref *refs = remote_refs;
struct ref *remote_ref = NULL;
@@ -1742,10 +1697,7 @@ static int delete_remote_branch(char *pattern, int force)
sprintf(url, "%s%s", repo->url, remote_ref->name);
slot = get_active_slot();
slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);
+ curl_setup_http_get(slot->curl, url, DAV_DELETE);
if (start_active_slot(slot)) {
run_active_slot(slot);
free(url);
diff --git a/http-walker.c b/http-walker.c
index 9bc8114..51a906e 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -185,7 +185,7 @@ static void process_alternates_response(void *callback_data)
struct active_request_slot *slot = alt_req->slot;
struct alt_base *tail = cdata->alt;
const char *base = alt_req->base;
- static const char null_byte = '\0';
+ const char null_byte = '\0';
char *data;
int i = 0;
@@ -218,7 +218,7 @@ static void process_alternates_response(void *callback_data)
}
}
- fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
+ fwrite_buffer((char *)&null_byte, 1, 1, alt_req->buffer);
alt_req->buffer->len--;
data = alt_req->buffer->buf;
diff --git a/http.c b/http.c
index b27bb57..b2ae8de 100644
--- a/http.c
+++ b/http.c
@@ -60,7 +60,7 @@ static struct curl_slist *no_pragma_header;
static struct active_request_slot *active_queue_head;
-size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
+size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
struct buffer *buffer = buffer_;
@@ -92,7 +92,7 @@ curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
}
#endif
-size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
+size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
struct strbuf *buffer = buffer_;
@@ -102,7 +102,7 @@ size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer
return size;
}
-size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
+size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
{
data_received++;
return eltsize * nmemb;
@@ -1167,7 +1167,7 @@ abort:
}
/* Helpers for fetching objects (loose) */
-static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
void *data)
{
unsigned char expn[4096];
@@ -1184,7 +1184,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
} while (posn < size);
freq->stream.avail_in = size;
- freq->stream.next_in = ptr;
+ freq->stream.next_in = (void *)ptr;
do {
freq->stream.next_out = expn;
freq->stream.avail_out = sizeof(expn);
@@ -1203,7 +1203,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
char *filename;
char prevfile[PATH_MAX];
int prevlocal;
- unsigned char prev_buf[PREV_BUF_SIZE];
+ char prev_buf[PREV_BUF_SIZE];
ssize_t prev_read = 0;
long prev_posn = 0;
char range[RANGE_HEADER_SIZE];
diff --git a/http.h b/http.h
index e9ed3c2..19b7134 100644
--- a/http.h
+++ b/http.h
@@ -66,9 +66,9 @@ struct buffer {
};
/* Curl request read/write callbacks */
-extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
-extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
-extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+extern size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+extern size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+extern size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
#ifndef NO_CURL_IOCTL
extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
#endif
diff --git a/ident.c b/ident.c
index 1c4adb0..8e56b5e 100644
--- a/ident.c
+++ b/ident.c
@@ -34,6 +34,7 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz)
*dst++ = toupper(*w->pw_name);
memcpy(dst, w->pw_name + 1, nlen - 1);
dst += nlen - 1;
+ len += nlen;
}
}
if (len < sz)
diff --git a/merge-recursive.c b/merge-recursive.c
index ecb1806..db9ba19 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -341,11 +341,10 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
* make room for the corresponding directory. Such paths will
* later be processed in process_df_entry() at the end. If
* the corresponding directory ends up being removed by the
- * merge, then the file will be reinstated at that time
- * (albeit with a different timestamp!); otherwise, if the
- * file is not supposed to be removed by the merge, the
- * contents of the file will be placed in another unique
- * filename.
+ * merge, then the file will be reinstated at that time;
+ * otherwise, if the file is not supposed to be removed by the
+ * merge, the contents of the file will be placed in another
+ * unique filename.
*
* NOTE: This function relies on the fact that entries for a
* D/F conflict will appear adjacent in the index, with the
@@ -356,13 +355,6 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
int last_len = 0;
int i;
- /*
- * Do not do any of this crazyness during the recursive; we don't
- * even write anything to the working tree!
- */
- if (o->call_depth)
- return;
-
for (i = 0; i < entries->nr; i++) {
const char *path = entries->items[i].string;
int len = strlen(path);
@@ -1265,13 +1257,9 @@ static int merge_content(struct merge_options *o,
}
if (mfi.clean && !df_conflict_remains &&
- sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode &&
- !o->call_depth && !lstat(path, &st)) {
+ sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
output(o, 3, "Skipped %s (merged same as existing)", path);
- add_cacheinfo(mfi.mode, mfi.sha, path,
- 0 /*stage*/, 1 /*refresh*/, 0 /*options*/);
- return mfi.clean;
- } else
+ else
output(o, 2, "Auto-merging %s", path);
if (!mfi.clean) {
@@ -1721,15 +1709,15 @@ int merge_recursive_generic(struct merge_options *o,
static int merge_recursive_config(const char *var, const char *value, void *cb)
{
struct merge_options *o = cb;
- if (!strcasecmp(var, "merge.verbosity")) {
+ if (!strcmp(var, "merge.verbosity")) {
o->verbosity = git_config_int(var, value);
return 0;
}
- if (!strcasecmp(var, "diff.renamelimit")) {
+ if (!strcmp(var, "diff.renamelimit")) {
o->diff_rename_limit = git_config_int(var, value);
return 0;
}
- if (!strcasecmp(var, "merge.renamelimit")) {
+ if (!strcmp(var, "merge.renamelimit")) {
o->merge_rename_limit = git_config_int(var, value);
return 0;
}
diff --git a/object.c b/object.c
index 7e1f2bb..31976b5 100644
--- a/object.c
+++ b/object.c
@@ -188,8 +188,8 @@ struct object *parse_object(const unsigned char *sha1)
unsigned long size;
enum object_type type;
int eaten;
- const unsigned char *repl;
- void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl);
+ const unsigned char *repl = lookup_replace_object(sha1);
+ void *buffer = read_sha1_file(sha1, &type, &size);
if (buffer) {
struct object *obj;
diff --git a/remote-curl.c b/remote-curl.c
index 775d614..17d8a9b 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -347,7 +347,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
}
#endif
-static size_t rpc_in(const void *ptr, size_t eltsize,
+static size_t rpc_in(char *ptr, size_t eltsize,
size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
diff --git a/replace_object.c b/replace_object.c
index 7c6c754..d0b1548 100644
--- a/replace_object.c
+++ b/replace_object.c
@@ -85,12 +85,14 @@ static void prepare_replace_object(void)
for_each_replace_ref(register_replace_ref, NULL);
replace_object_prepared = 1;
+ if (!replace_object_nr)
+ read_replace_refs = 0;
}
/* We allow "recursive" replacement. Only within reason, though */
#define MAXREPLACEDEPTH 5
-const unsigned char *lookup_replace_object(const unsigned char *sha1)
+const unsigned char *do_lookup_replace_object(const unsigned char *sha1)
{
int pos, depth = MAXREPLACEDEPTH;
const unsigned char *cur = sha1;
diff --git a/rerere.c b/rerere.c
index 22dfc84..dee2cb1 100644
--- a/rerere.c
+++ b/rerere.c
@@ -671,3 +671,87 @@ int rerere_forget(const char **pathspec)
}
return write_rr(&merge_rr, fd);
}
+
+static time_t rerere_created_at(const char *name)
+{
+ struct stat st;
+ return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static time_t rerere_last_used_at(const char *name)
+{
+ struct stat st;
+ return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static void unlink_rr_item(const char *name)
+{
+ unlink(rerere_path(name, "thisimage"));
+ unlink(rerere_path(name, "preimage"));
+ unlink(rerere_path(name, "postimage"));
+ rmdir(git_path("rr-cache/%s", name));
+}
+
+struct rerere_gc_config_cb {
+ int cutoff_noresolve;
+ int cutoff_resolve;
+};
+
+static int git_rerere_gc_config(const char *var, const char *value, void *cb)
+{
+ struct rerere_gc_config_cb *cf = cb;
+
+ if (!strcmp(var, "gc.rerereresolved"))
+ cf->cutoff_resolve = git_config_int(var, value);
+ else if (!strcmp(var, "gc.rerereunresolved"))
+ cf->cutoff_noresolve = git_config_int(var, value);
+ else
+ return git_default_config(var, value, cb);
+ return 0;
+}
+
+void rerere_gc(struct string_list *rr)
+{
+ struct string_list to_remove = STRING_LIST_INIT_DUP;
+ DIR *dir;
+ struct dirent *e;
+ int i, cutoff;
+ time_t now = time(NULL), then;
+ struct rerere_gc_config_cb cf = { 15, 60 };
+
+ git_config(git_rerere_gc_config, &cf);
+ dir = opendir(git_path("rr-cache"));
+ if (!dir)
+ die_errno("unable to open rr-cache directory");
+ while ((e = readdir(dir))) {
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
+
+ then = rerere_last_used_at(e->d_name);
+ if (then) {
+ cutoff = cf.cutoff_resolve;
+ } else {
+ then = rerere_created_at(e->d_name);
+ if (!then)
+ continue;
+ cutoff = cf.cutoff_noresolve;
+ }
+ if (then < now - cutoff * 86400)
+ string_list_append(&to_remove, e->d_name);
+ }
+ for (i = 0; i < to_remove.nr; i++)
+ unlink_rr_item(to_remove.items[i].string);
+ string_list_clear(&to_remove, 0);
+}
+
+void rerere_clear(struct string_list *merge_rr)
+{
+ int i;
+
+ for (i = 0; i < merge_rr->nr; i++) {
+ const char *name = (const char *)merge_rr->items[i].util;
+ if (!has_rerere_resolution(name))
+ unlink_rr_item(name);
+ }
+ unlink_or_warn(git_path("MERGE_RR"));
+}
diff --git a/rerere.h b/rerere.h
index 595f49f..fcd8bc1 100644
--- a/rerere.h
+++ b/rerere.h
@@ -19,6 +19,8 @@ extern const char *rerere_path(const char *hex, const char *file);
extern int has_rerere_resolution(const char *hex);
extern int rerere_forget(const char **);
extern int rerere_remaining(struct string_list *);
+extern void rerere_clear(struct string_list *);
+extern void rerere_gc(struct string_list *);
#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
"update the index with reused conflict resolution if possible")
diff --git a/revision.c b/revision.c
index 541f09e..cf0b001 100644
--- a/revision.c
+++ b/revision.c
@@ -1098,35 +1098,34 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
return 0;
}
-static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, const char ***prune_data)
-{
- const char **prune = *prune_data;
- int prune_nr;
- int prune_alloc;
+struct cmdline_pathspec {
+ int alloc;
+ int nr;
+ const char **path;
+};
- /* count existing ones */
- if (!prune)
- prune_nr = 0;
- else
- for (prune_nr = 0; prune[prune_nr]; prune_nr++)
- ;
- prune_alloc = prune_nr; /* not really, but we do not know */
+static void append_prune_data(struct cmdline_pathspec *prune, const char **av)
+{
+ while (*av) {
+ ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+ prune->path[prune->nr++] = *(av++);
+ }
+}
+static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb,
+ struct cmdline_pathspec *prune)
+{
while (strbuf_getwholeline(sb, stdin, '\n') != EOF) {
int len = sb->len;
if (len && sb->buf[len - 1] == '\n')
sb->buf[--len] = '\0';
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr++] = xstrdup(sb->buf);
- }
- if (prune) {
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr] = NULL;
+ ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
+ prune->path[prune->nr++] = xstrdup(sb->buf);
}
- *prune_data = prune;
}
-static void read_revisions_from_stdin(struct rev_info *revs, const char ***prune)
+static void read_revisions_from_stdin(struct rev_info *revs,
+ struct cmdline_pathspec *prune)
{
struct strbuf sb;
int seen_dashdash = 0;
@@ -1180,7 +1179,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
!strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
!strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
!strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
- !strcmp(arg, "--bisect"))
+ !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") ||
+ !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") ||
+ !prefixcmp(arg, "--remotes="))
{
unkv[(*unkc)++] = arg;
return 1;
@@ -1507,32 +1508,67 @@ static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void
return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data);
}
-static void append_prune_data(const char ***prune_data, const char **av)
+static int handle_revision_pseudo_opt(const char *submodule,
+ struct rev_info *revs,
+ int argc, const char **argv, int *flags)
{
- const char **prune = *prune_data;
- int prune_nr;
- int prune_alloc;
+ const char *arg = argv[0];
+ const char *optarg;
+ int argcount;
- if (!prune) {
- *prune_data = av;
- return;
+ /*
+ * NOTE!
+ *
+ * Commands like "git shortlog" will not accept the options below
+ * unless parse_revision_opt queues them (as opposed to erroring
+ * out).
+ *
+ * When implementing your new pseudo-option, remember to
+ * register it in the list at the top of handle_revision_opt.
+ */
+ if (!strcmp(arg, "--all")) {
+ handle_refs(submodule, revs, *flags, for_each_ref_submodule);
+ handle_refs(submodule, revs, *flags, head_ref_submodule);
+ } else if (!strcmp(arg, "--branches")) {
+ handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
+ } else if (!strcmp(arg, "--bisect")) {
+ handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
+ handle_refs(submodule, revs, *flags ^ UNINTERESTING, for_each_good_bisect_ref);
+ revs->bisect = 1;
+ } else if (!strcmp(arg, "--tags")) {
+ handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
+ } else if (!strcmp(arg, "--remotes")) {
+ handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule);
+ } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref(handle_one_ref, optarg, &cb);
+ return argcount;
+ } else if (!prefixcmp(arg, "--branches=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
+ } else if (!prefixcmp(arg, "--tags=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
+ } else if (!prefixcmp(arg, "--remotes=")) {
+ struct all_refs_cb cb;
+ init_all_refs_cb(&cb, revs, *flags);
+ for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
+ } else if (!strcmp(arg, "--reflog")) {
+ handle_reflog(revs, *flags);
+ } else if (!strcmp(arg, "--not")) {
+ *flags ^= UNINTERESTING;
+ } else if (!strcmp(arg, "--no-walk")) {
+ revs->no_walk = 1;
+ } else if (!strcmp(arg, "--do-walk")) {
+ revs->no_walk = 0;
+ } else {
+ return 0;
}
- /* count existing ones */
- for (prune_nr = 0; prune[prune_nr]; prune_nr++)
- ;
- prune_alloc = prune_nr; /* not really, but we do not know */
-
- while (*av) {
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr++] = *av;
- av++;
- }
- if (prune) {
- ALLOC_GROW(prune, prune_nr+1, prune_alloc);
- prune[prune_nr] = NULL;
- }
- *prune_data = prune;
+ return 1;
}
/*
@@ -1545,11 +1581,10 @@ static void append_prune_data(const char ***prune_data, const char **av)
int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
{
int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
- const char **prune_data = NULL;
+ struct cmdline_pathspec prune_data;
const char *submodule = NULL;
- const char *optarg;
- int argcount;
+ memset(&prune_data, 0, sizeof(prune_data));
if (opt)
submodule = opt->submodule;
@@ -1562,7 +1597,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
argv[i] = NULL;
argc = i;
if (argv[i + 1])
- prune_data = argv + i + 1;
+ append_prune_data(&prune_data, argv + i + 1);
seen_dashdash = 1;
break;
}
@@ -1575,70 +1610,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
if (*arg == '-') {
int opts;
- if (!strcmp(arg, "--all")) {
- handle_refs(submodule, revs, flags, for_each_ref_submodule);
- handle_refs(submodule, revs, flags, head_ref_submodule);
- continue;
- }
- if (!strcmp(arg, "--branches")) {
- handle_refs(submodule, revs, flags, for_each_branch_ref_submodule);
- continue;
- }
- if (!strcmp(arg, "--bisect")) {
- handle_refs(submodule, revs, flags, for_each_bad_bisect_ref);
- handle_refs(submodule, revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);
- revs->bisect = 1;
- continue;
- }
- if (!strcmp(arg, "--tags")) {
- handle_refs(submodule, revs, flags, for_each_tag_ref_submodule);
- continue;
- }
- if (!strcmp(arg, "--remotes")) {
- handle_refs(submodule, revs, flags, for_each_remote_ref_submodule);
- continue;
- }
- if ((argcount = parse_long_opt("glob", argv + i, &optarg))) {
- struct all_refs_cb cb;
- i += argcount - 1;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref(handle_one_ref, optarg, &cb);
- continue;
- }
- if (!prefixcmp(arg, "--branches=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
- continue;
- }
- if (!prefixcmp(arg, "--tags=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
- continue;
- }
- if (!prefixcmp(arg, "--remotes=")) {
- struct all_refs_cb cb;
- init_all_refs_cb(&cb, revs, flags);
- for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
- continue;
- }
- if (!strcmp(arg, "--reflog")) {
- handle_reflog(revs, flags);
- continue;
- }
- if (!strcmp(arg, "--not")) {
- flags ^= UNINTERESTING;
- continue;
- }
- if (!strcmp(arg, "--no-walk")) {
- revs->no_walk = 1;
- continue;
- }
- if (!strcmp(arg, "--do-walk")) {
- revs->no_walk = 0;
+ opts = handle_revision_pseudo_opt(submodule,
+ revs, argc - i, argv + i,
+ &flags);
+ if (opts > 0) {
+ i += opts - 1;
continue;
}
+
if (!strcmp(arg, "--stdin")) {
if (revs->disable_stdin) {
argv[left++] = arg;
@@ -1681,8 +1660,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
got_rev_arg = 1;
}
- if (prune_data)
- init_pathspec(&revs->prune_data, get_pathspec(revs->prefix, prune_data));
+ if (prune_data.nr) {
+ ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
+ prune_data.path[prune_data.nr++] = NULL;
+ init_pathspec(&revs->prune_data,
+ get_pathspec(revs->prefix, prune_data.path));
+ }
if (revs->def == NULL)
revs->def = opt ? opt->def : NULL;
diff --git a/sha1_file.c b/sha1_file.c
index 889fe71..357f9ab 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -31,8 +31,6 @@ static inline uintmax_t sz_fmt(size_t s) { return s; }
const unsigned char null_sha1[20];
-static int git_open_noatime(const char *name, struct packed_git *p);
-
/*
* This is meant to hold a *small* number of objects that you would
* want read_sha1_file() to be able to return, but yet you do not want
@@ -227,6 +225,7 @@ struct alternate_object_database *alt_odb_list;
static struct alternate_object_database **alt_odb_tail;
static void read_info_alternates(const char * alternates, int depth);
+static int git_open_noatime(const char *name);
/*
* Prepare alternate object database registry.
@@ -360,7 +359,7 @@ static void read_info_alternates(const char * relative_base, int depth)
int fd;
sprintf(path, "%s/%s", relative_base, alt_file_name);
- fd = git_open_noatime(path, NULL);
+ fd = git_open_noatime(path);
if (fd < 0)
return;
if (fstat(fd, &st) || (st.st_size == 0)) {
@@ -475,7 +474,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
struct pack_idx_header *hdr;
size_t idx_size;
uint32_t version, nr, i, *index;
- int fd = git_open_noatime(path, p);
+ int fd = git_open_noatime(path);
struct stat st;
if (fd < 0)
@@ -757,7 +756,7 @@ static int open_packed_git_1(struct packed_git *p)
while (pack_max_fds <= pack_open_fds && unuse_one_window(NULL, -1))
; /* nothing */
- p->pack_fd = git_open_noatime(p->pack_name, p);
+ p->pack_fd = git_open_noatime(p->pack_name);
if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
return -1;
pack_open_fds++;
@@ -1145,7 +1144,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz
return hashcmp(sha1, real_sha1) ? -1 : 0;
}
-static int git_open_noatime(const char *name, struct packed_git *p)
+static int git_open_noatime(const char *name)
{
static int sha1_file_open_flag = O_NOATIME;
@@ -1170,7 +1169,7 @@ static int open_sha1_file(const unsigned char *sha1)
char *name = sha1_file_name(sha1);
struct alternate_object_database *alt;
- fd = git_open_noatime(name, NULL);
+ fd = git_open_noatime(name);
if (fd >= 0)
return fd;
@@ -1179,7 +1178,7 @@ static int open_sha1_file(const unsigned char *sha1)
for (alt = alt_odb_list; alt; alt = alt->next) {
name = alt->name;
fill_sha1_path(name, sha1);
- fd = git_open_noatime(alt->base, NULL);
+ fd = git_open_noatime(alt->base);
if (fd >= 0)
return fd;
}
@@ -1308,7 +1307,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
/*
* The above condition must be (bytes <= size), not
* (bytes < size). In other words, even though we
- * expect no more output and set avail_out to zer0,
+ * expect no more output and set avail_out to zero,
* the input zlib stream may have bytes that express
* "this concludes the stream", and we *do* want to
* eat that input.
@@ -2206,23 +2205,21 @@ static void *read_object(const unsigned char *sha1, enum object_type *type,
* deal with them should arrange to call read_object() and give error
* messages themselves.
*/
-void *read_sha1_file_repl(const unsigned char *sha1,
- enum object_type *type,
- unsigned long *size,
- const unsigned char **replacement)
+void *read_sha1_file_extended(const unsigned char *sha1,
+ enum object_type *type,
+ unsigned long *size,
+ unsigned flag)
{
- const unsigned char *repl = lookup_replace_object(sha1);
void *data;
char *path;
const struct packed_git *p;
+ const unsigned char *repl = (flag & READ_SHA1_FILE_REPLACE)
+ ? lookup_replace_object(sha1) : sha1;
errno = 0;
data = read_object(repl, type, size);
- if (data) {
- if (replacement)
- *replacement = repl;
+ if (data)
return data;
- }
if (errno && errno != ENOENT)
die_errno("failed to read object %s", sha1_to_hex(sha1));
diff --git a/submodule.c b/submodule.c
index 5294cef..b6dec70 100644
--- a/submodule.c
+++ b/submodule.c
@@ -14,6 +14,15 @@ static struct string_list config_fetch_recurse_submodules_for_name;
static struct string_list config_ignore_for_name;
static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
static struct string_list changed_submodule_paths;
+/*
+ * The following flag is set if the .gitmodules file is unmerged. We then
+ * disable recursion for all submodules where .git/config doesn't have a
+ * matching config entry because we can't guess what might be configured in
+ * .gitmodules unless the user resolves the conflict. When a command line
+ * option is given (which always overrides configuration) this flag will be
+ * ignored.
+ */
+static int gitmodules_is_unmerged;
static int add_submodule_odb(const char *path)
{
@@ -63,6 +72,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
if (ignore_option)
handle_ignore_submodules_arg(diffopt, ignore_option->util);
+ else if (gitmodules_is_unmerged)
+ DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
}
}
@@ -82,9 +93,24 @@ void gitmodules_config(void)
const char *work_tree = get_git_work_tree();
if (work_tree) {
struct strbuf gitmodules_path = STRBUF_INIT;
+ int pos;
strbuf_addstr(&gitmodules_path, work_tree);
strbuf_addstr(&gitmodules_path, "/.gitmodules");
- git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+ if (read_cache() < 0)
+ die("index file corrupt");
+ pos = cache_name_pos(".gitmodules", 11);
+ if (pos < 0) { /* .gitmodules not found or isn't merged */
+ pos = -1 - pos;
+ if (active_nr > pos) { /* there is a .gitmodules */
+ const struct cache_entry *ce = active_cache[pos];
+ if (ce_namelen(ce) == 11 &&
+ !memcmp(ce->name, ".gitmodules", 11))
+ gitmodules_is_unmerged = 1;
+ }
+ }
+
+ if (!gitmodules_is_unmerged)
+ git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
strbuf_release(&gitmodules_path);
}
}
@@ -434,7 +460,8 @@ int fetch_populated_submodules(int num_options, const char **options,
default_argv = "on-demand";
}
} else {
- if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF)
+ if ((config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF) ||
+ gitmodules_is_unmerged)
continue;
if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) {
if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index de84e35..20a50eb 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -17,19 +17,21 @@ test_expect_success 'setup' '
cat >expected <<-\EOF &&
100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added
+ 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/addedtoo
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added
EOF
cat >expected.swt <<-\EOF &&
H init.t
H sub/added
+ H sub/addedtoo
H subsub/added
EOF
test_commit init &&
echo modified >>init.t &&
mkdir sub subsub &&
- touch sub/added subsub/added &&
- git add init.t sub/added subsub/added &&
+ touch sub/added sub/addedtoo subsub/added &&
+ git add init.t sub/added sub/addedtoo subsub/added &&
git commit -m "modified and added" &&
git tag top &&
git rm sub/added &&
@@ -83,6 +85,7 @@ test_expect_success 'match directories with trailing slash' '
cat >expected.swt-noinit <<-\EOF &&
S init.t
H sub/added
+ H sub/addedtoo
S subsub/added
EOF
@@ -95,7 +98,7 @@ test_expect_success 'match directories with trailing slash' '
'
test_expect_success 'match directories without trailing slash' '
- echo sub >>.git/info/sparse-checkout &&
+ echo sub >.git/info/sparse-checkout &&
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-noinit result &&
@@ -103,8 +106,49 @@ test_expect_success 'match directories without trailing slash' '
test -f sub/added
'
+test_expect_success 'match directories with negated patterns' '
+ cat >expected.swt-negation <<\EOF &&
+S init.t
+S sub/added
+H sub/addedtoo
+S subsub/added
+EOF
+
+ cat >.git/info/sparse-checkout <<\EOF &&
+sub
+!sub/added
+EOF
+ git read-tree -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-negation result &&
+ test ! -f init.t &&
+ test ! -f sub/added &&
+ test -f sub/addedtoo
+'
+
+test_expect_success 'match directories with negated patterns (2)' '
+ cat >expected.swt-negation2 <<\EOF &&
+H init.t
+H sub/added
+S sub/addedtoo
+H subsub/added
+EOF
+
+ cat >.git/info/sparse-checkout <<\EOF &&
+/*
+!sub
+sub/added
+EOF
+ git read-tree -m -u HEAD &&
+ git ls-files -t >result &&
+ test_cmp expected.swt-negation2 result &&
+ test -f init.t &&
+ test -f sub/added &&
+ test ! -f sub/addedtoo
+'
+
test_expect_success 'match directory pattern' '
- echo "s?b" >>.git/info/sparse-checkout &&
+ echo "s?b" >.git/info/sparse-checkout &&
git read-tree -m -u HEAD &&
git ls-files -t >result &&
test_cmp expected.swt-noinit result &&
@@ -116,6 +160,7 @@ test_expect_success 'checkout area changes' '
cat >expected.swt-nosub <<-\EOF &&
H init.t
S sub/added
+ S sub/addedtoo
S subsub/added
EOF
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index 4a6396f..0843a1c 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -8,8 +8,11 @@ exec </dev/null
test_did_you_mean ()
{
- printf "fatal: Path '$2$3' $4, but not ${5:-'$3'}.\n" >expected &&
- printf "Did you mean '$1:$2$3'${2:+ aka '$1:./$3'}?\n" >>expected &&
+ sq="'" &&
+ cat >expected <<-EOF &&
+ fatal: Path '$2$3' $4, but not ${5:-$sq$3$sq}.
+ Did you mean '$1:$2$3'${2:+ aka $sq$1:./$3$sq}?
+ EOF
test_cmp expected error
}
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index 2d7d311..4cdebda 100755
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
@@ -149,31 +149,21 @@ test_expect_success 'add -u resolves unmerged paths' '
echo 3 >path1 &&
echo 2 >path3 &&
echo 2 >path5 &&
- git add -u &&
- git ls-files -s path1 path2 path3 path4 path5 path6 >actual &&
- {
- echo "100644 $three 0 path1"
- echo "100644 $one 1 path3"
- echo "100644 $one 1 path4"
- echo "100644 $one 3 path5"
- echo "100644 $one 3 path6"
- } >expect &&
- test_cmp expect actual &&
- # Bonus tests. Explicit resolving
- git add path3 path5 &&
+ # Explicit resolving by adding removed paths should fail
test_must_fail git add path4 &&
test_must_fail git add path6 &&
- git rm path4 &&
- git rm path6 &&
- git ls-files -s "path?" >actual &&
+ # "add -u" should notice removals no matter what stages
+ # the index entries are in.
+ git add -u &&
+ git ls-files -s path1 path2 path3 path4 path5 path6 >actual &&
{
echo "100644 $three 0 path1"
echo "100644 $two 0 path3"
echo "100644 $two 0 path5"
- } >expect
-
+ } >expect &&
+ test_cmp expect actual
'
test_expect_success '"add -u non-existent" should fail' '
diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh
index f2b2a52..c286854 100755
--- a/t/t3102-ls-tree-wildcards.sh
+++ b/t/t3102-ls-tree-wildcards.sh
@@ -1,21 +1,21 @@
#!/bin/sh
-test_description='ls-tree with(out) wildcards'
+test_description='ls-tree with(out) globs'
. ./test-lib.sh
test_expect_success 'setup' '
- mkdir a aa "a*" &&
- touch a/one aa/two "a*/three" &&
- git add a/one aa/two "a*/three" &&
+ mkdir a aa "a[a]" &&
+ touch a/one aa/two "a[a]/three" &&
+ git add a/one aa/two "a[a]/three" &&
git commit -m test
'
-test_expect_success 'ls-tree a* matches literally' '
+test_expect_success 'ls-tree a[a] matches literally' '
cat >expected <<EOF &&
-100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a*/three
+100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three
EOF
- git ls-tree -r HEAD "a*" >actual &&
+ git ls-tree -r HEAD "a[a]" >actual &&
test_cmp expected actual
'
diff --git a/t/t3503-cherry-pick-root.sh b/t/t3503-cherry-pick-root.sh
index b0faa29..9aefe3a 100755
--- a/t/t3503-cherry-pick-root.sh
+++ b/t/t3503-cherry-pick-root.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='test cherry-picking a root commit'
+test_description='test cherry-picking (and reverting) a root commit'
. ./test-lib.sh
@@ -23,7 +23,30 @@ test_expect_success setup '
test_expect_success 'cherry-pick a root commit' '
git cherry-pick master &&
- test first = $(cat file1)
+ echo first >expect &&
+ test_cmp expect file1
+
+'
+
+test_expect_success 'revert a root commit' '
+
+ git revert master &&
+ test_path_is_missing file1
+
+'
+
+test_expect_success 'cherry-pick a root commit with an external strategy' '
+
+ git cherry-pick --strategy=resolve master &&
+ echo first >expect &&
+ test_cmp expect file1
+
+'
+
+test_expect_success 'revert a root commit with an external strategy' '
+
+ git revert --strategy=resolve master &&
+ test_path_is_missing file1
'
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index fdcbe2e..9e236f9 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -294,4 +294,40 @@ test_expect_success PERL 'deleting an empty file' '
test_cmp expected diff
'
+test_expect_success PERL 'split hunk setup' '
+ git reset --hard &&
+ for i in 10 20 30 40 50 60
+ do
+ echo $i
+ done >test &&
+ git add test &&
+ test_tick &&
+ git commit -m test &&
+
+ for i in 10 15 20 21 22 23 24 30 40 50 60
+ do
+ echo $i
+ done >test
+'
+
+test_expect_success PERL 'split hunk "add -p (edit)"' '
+ # Split, say Edit and do nothing. Then:
+ #
+ # 1. Broken version results in a patch that does not apply and
+ # only takes [y/n] (edit again) so the first q is discarded
+ # and then n attempts to discard the edit. Repeat q enough
+ # times to get out.
+ #
+ # 2. Correct version applies the (not)edited version, and asks
+ # about the next hunk, against wich we say q and program
+ # exits.
+ for a in s e q n q q
+ do
+ echo $a
+ done |
+ EDITOR=: git add -p &&
+ git diff >actual &&
+ ! grep "^+15" actual
+'
+
test_done
diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh
new file mode 100755
index 0000000..29e80a5
--- /dev/null
+++ b/t/t4047-diff-dirstat.sh
@@ -0,0 +1,979 @@
+#!/bin/sh
+
+test_description='diff --dirstat tests'
+. ./test-lib.sh
+
+# set up two commits where the second commit has these files
+# (10 lines in each file):
+#
+# unchanged/text (unchanged from 1st commit)
+# changed/text (changed 1st line)
+# rearranged/text (swapped 1st and 2nd line)
+# dst/copy/unchanged/text (copied from src/copy/unchanged/text, unchanged)
+# dst/copy/changed/text (copied from src/copy/changed/text, changed)
+# dst/copy/rearranged/text (copied from src/copy/rearranged/text, rearranged)
+# dst/move/unchanged/text (moved from src/move/unchanged/text, unchanged)
+# dst/move/changed/text (moved from src/move/changed/text, changed)
+# dst/move/rearranged/text (moved from src/move/rearranged/text, rearranged)
+
+test_expect_success 'setup' '
+ mkdir unchanged &&
+ mkdir changed &&
+ mkdir rearranged &&
+ mkdir src &&
+ mkdir src/copy &&
+ mkdir src/copy/unchanged &&
+ mkdir src/copy/changed &&
+ mkdir src/copy/rearranged &&
+ mkdir src/move &&
+ mkdir src/move/unchanged &&
+ mkdir src/move/changed &&
+ mkdir src/move/rearranged &&
+ cat <<EOF >unchanged/text &&
+unchanged line #0
+unchanged line #1
+unchanged line #2
+unchanged line #3
+unchanged line #4
+unchanged line #5
+unchanged line #6
+unchanged line #7
+unchanged line #8
+unchanged line #9
+EOF
+ cat <<EOF >changed/text &&
+changed line #0
+changed line #1
+changed line #2
+changed line #3
+changed line #4
+changed line #5
+changed line #6
+changed line #7
+changed line #8
+changed line #9
+EOF
+ cat <<EOF >rearranged/text &&
+rearranged line #0
+rearranged line #1
+rearranged line #2
+rearranged line #3
+rearranged line #4
+rearranged line #5
+rearranged line #6
+rearranged line #7
+rearranged line #8
+rearranged line #9
+EOF
+ cat <<EOF >src/copy/unchanged/text &&
+copy unchanged line #0
+copy unchanged line #1
+copy unchanged line #2
+copy unchanged line #3
+copy unchanged line #4
+copy unchanged line #5
+copy unchanged line #6
+copy unchanged line #7
+copy unchanged line #8
+copy unchanged line #9
+EOF
+ cat <<EOF >src/copy/changed/text &&
+copy changed line #0
+copy changed line #1
+copy changed line #2
+copy changed line #3
+copy changed line #4
+copy changed line #5
+copy changed line #6
+copy changed line #7
+copy changed line #8
+copy changed line #9
+EOF
+ cat <<EOF >src/copy/rearranged/text &&
+copy rearranged line #0
+copy rearranged line #1
+copy rearranged line #2
+copy rearranged line #3
+copy rearranged line #4
+copy rearranged line #5
+copy rearranged line #6
+copy rearranged line #7
+copy rearranged line #8
+copy rearranged line #9
+EOF
+ cat <<EOF >src/move/unchanged/text &&
+move unchanged line #0
+move unchanged line #1
+move unchanged line #2
+move unchanged line #3
+move unchanged line #4
+move unchanged line #5
+move unchanged line #6
+move unchanged line #7
+move unchanged line #8
+move unchanged line #9
+EOF
+ cat <<EOF >src/move/changed/text &&
+move changed line #0
+move changed line #1
+move changed line #2
+move changed line #3
+move changed line #4
+move changed line #5
+move changed line #6
+move changed line #7
+move changed line #8
+move changed line #9
+EOF
+ cat <<EOF >src/move/rearranged/text &&
+move rearranged line #0
+move rearranged line #1
+move rearranged line #2
+move rearranged line #3
+move rearranged line #4
+move rearranged line #5
+move rearranged line #6
+move rearranged line #7
+move rearranged line #8
+move rearranged line #9
+EOF
+ git add . &&
+ git commit -m "initial" &&
+ mkdir dst &&
+ mkdir dst/copy &&
+ mkdir dst/copy/unchanged &&
+ mkdir dst/copy/changed &&
+ mkdir dst/copy/rearranged &&
+ mkdir dst/move &&
+ mkdir dst/move/unchanged &&
+ mkdir dst/move/changed &&
+ mkdir dst/move/rearranged &&
+ cat <<EOF >changed/text &&
+CHANGED XXXXXXX line #0
+changed line #1
+changed line #2
+changed line #3
+changed line #4
+changed line #5
+changed line #6
+changed line #7
+changed line #8
+changed line #9
+EOF
+ cat <<EOF >rearranged/text &&
+rearranged line #1
+rearranged line #0
+rearranged line #2
+rearranged line #3
+rearranged line #4
+rearranged line #5
+rearranged line #6
+rearranged line #7
+rearranged line #8
+rearranged line #9
+EOF
+ cat <<EOF >dst/copy/unchanged/text &&
+copy unchanged line #0
+copy unchanged line #1
+copy unchanged line #2
+copy unchanged line #3
+copy unchanged line #4
+copy unchanged line #5
+copy unchanged line #6
+copy unchanged line #7
+copy unchanged line #8
+copy unchanged line #9
+EOF
+ cat <<EOF >dst/copy/changed/text &&
+copy XXXCHANGED line #0
+copy changed line #1
+copy changed line #2
+copy changed line #3
+copy changed line #4
+copy changed line #5
+copy changed line #6
+copy changed line #7
+copy changed line #8
+copy changed line #9
+EOF
+ cat <<EOF >dst/copy/rearranged/text &&
+copy rearranged line #1
+copy rearranged line #0
+copy rearranged line #2
+copy rearranged line #3
+copy rearranged line #4
+copy rearranged line #5
+copy rearranged line #6
+copy rearranged line #7
+copy rearranged line #8
+copy rearranged line #9
+EOF
+ cat <<EOF >dst/move/unchanged/text &&
+move unchanged line #0
+move unchanged line #1
+move unchanged line #2
+move unchanged line #3
+move unchanged line #4
+move unchanged line #5
+move unchanged line #6
+move unchanged line #7
+move unchanged line #8
+move unchanged line #9
+EOF
+ cat <<EOF >dst/move/changed/text &&
+move XXXCHANGED line #0
+move changed line #1
+move changed line #2
+move changed line #3
+move changed line #4
+move changed line #5
+move changed line #6
+move changed line #7
+move changed line #8
+move changed line #9
+EOF
+ cat <<EOF >dst/move/rearranged/text &&
+move rearranged line #1
+move rearranged line #0
+move rearranged line #2
+move rearranged line #3
+move rearranged line #4
+move rearranged line #5
+move rearranged line #6
+move rearranged line #7
+move rearranged line #8
+move rearranged line #9
+EOF
+ git add . &&
+ git rm -r src/move/unchanged &&
+ git rm -r src/move/changed &&
+ git rm -r src/move/rearranged &&
+ git commit -m "changes"
+'
+
+cat <<EOF >expect_diff_stat
+ changed/text | 2 +-
+ dst/copy/changed/text | 10 ++++++++++
+ dst/copy/rearranged/text | 10 ++++++++++
+ dst/copy/unchanged/text | 10 ++++++++++
+ dst/move/changed/text | 10 ++++++++++
+ dst/move/rearranged/text | 10 ++++++++++
+ dst/move/unchanged/text | 10 ++++++++++
+ rearranged/text | 2 +-
+ src/move/changed/text | 10 ----------
+ src/move/rearranged/text | 10 ----------
+ src/move/unchanged/text | 10 ----------
+ 11 files changed, 62 insertions(+), 32 deletions(-)
+EOF
+
+cat <<EOF >expect_diff_stat_M
+ changed/text | 2 +-
+ dst/copy/changed/text | 10 ++++++++++
+ dst/copy/rearranged/text | 10 ++++++++++
+ dst/copy/unchanged/text | 10 ++++++++++
+ {src => dst}/move/changed/text | 2 +-
+ {src => dst}/move/rearranged/text | 2 +-
+ {src => dst}/move/unchanged/text | 0
+ rearranged/text | 2 +-
+ 8 files changed, 34 insertions(+), 4 deletions(-)
+EOF
+
+cat <<EOF >expect_diff_stat_CC
+ changed/text | 2 +-
+ {src => dst}/copy/changed/text | 2 +-
+ {src => dst}/copy/rearranged/text | 2 +-
+ {src => dst}/copy/unchanged/text | 0
+ {src => dst}/move/changed/text | 2 +-
+ {src => dst}/move/rearranged/text | 2 +-
+ {src => dst}/move/unchanged/text | 0
+ rearranged/text | 2 +-
+ 8 files changed, 6 insertions(+), 6 deletions(-)
+EOF
+
+test_expect_success 'sanity check setup (--stat)' '
+ git diff --stat HEAD^..HEAD >actual_diff_stat &&
+ test_cmp expect_diff_stat actual_diff_stat &&
+ git diff --stat -M HEAD^..HEAD >actual_diff_stat_M &&
+ test_cmp expect_diff_stat_M actual_diff_stat_M &&
+ git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
+ test_cmp expect_diff_stat_CC actual_diff_stat_CC
+'
+
+# changed/text and rearranged/text falls below default 3% threshold
+cat <<EOF >expect_diff_dirstat
+ 10.8% dst/copy/changed/
+ 10.8% dst/copy/rearranged/
+ 10.8% dst/copy/unchanged/
+ 10.8% dst/move/changed/
+ 10.8% dst/move/rearranged/
+ 10.8% dst/move/unchanged/
+ 10.8% src/move/changed/
+ 10.8% src/move/rearranged/
+ 10.8% src/move/unchanged/
+EOF
+
+# rearranged/text falls below default 3% threshold
+cat <<EOF >expect_diff_dirstat_M
+ 5.8% changed/
+ 29.3% dst/copy/changed/
+ 29.3% dst/copy/rearranged/
+ 29.3% dst/copy/unchanged/
+ 5.8% dst/move/changed/
+EOF
+
+# rearranged/text falls below default 3% threshold
+cat <<EOF >expect_diff_dirstat_CC
+ 32.6% changed/
+ 32.6% dst/copy/changed/
+ 32.6% dst/move/changed/
+EOF
+
+test_expect_success 'various ways to misspell --dirstat' '
+ test_must_fail git show --dirstat10 &&
+ test_must_fail git show --dirstat10,files &&
+ test_must_fail git show -X=20 &&
+ test_must_fail git show -X=20,cumulative
+'
+
+test_expect_success 'vanilla --dirstat' '
+ git diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'vanilla -X' '
+ git diff -X HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -X -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -X -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'explicit defaults: --dirstat=changes,noncumulative,3' '
+ git diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'explicit defaults: -Xchanges,noncumulative,3' '
+ git diff -Xchanges,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -Xchanges,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -Xchanges,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'later options override earlier options:' '
+ git diff --dirstat=files,10,cumulative,changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,10,cumulative,changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+ git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files --dirstat=10 --dirstat=cumulative --dirstat=changes --dirstat=noncumulative -X3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'non-defaults in config overridden by explicit defaults on command line' '
+ git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=files,cumulative,50 diff --dirstat=changes,noncumulative,3 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 2.1% changed/
+ 10.8% dst/copy/changed/
+ 10.8% dst/copy/rearranged/
+ 10.8% dst/copy/unchanged/
+ 10.8% dst/move/changed/
+ 10.8% dst/move/rearranged/
+ 10.8% dst/move/unchanged/
+ 0.0% rearranged/
+ 10.8% src/move/changed/
+ 10.8% src/move/rearranged/
+ 10.8% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.8% changed/
+ 29.3% dst/copy/changed/
+ 29.3% dst/copy/rearranged/
+ 29.3% dst/copy/unchanged/
+ 5.8% dst/move/changed/
+ 0.1% dst/move/rearranged/
+ 0.1% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 32.6% changed/
+ 32.6% dst/copy/changed/
+ 0.6% dst/copy/rearranged/
+ 32.6% dst/move/changed/
+ 0.6% dst/move/rearranged/
+ 0.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=0' '
+ git diff --dirstat=0 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=0 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '-X0' '
+ git diff -X0 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -X0 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -X0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0' '
+ git -c diff.dirstat=0 diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0 diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0 diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 2.1% changed/
+ 10.8% dst/copy/changed/
+ 10.8% dst/copy/rearranged/
+ 10.8% dst/copy/unchanged/
+ 32.5% dst/copy/
+ 10.8% dst/move/changed/
+ 10.8% dst/move/rearranged/
+ 10.8% dst/move/unchanged/
+ 32.5% dst/move/
+ 65.1% dst/
+ 0.0% rearranged/
+ 10.8% src/move/changed/
+ 10.8% src/move/rearranged/
+ 10.8% src/move/unchanged/
+ 32.5% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.8% changed/
+ 29.3% dst/copy/changed/
+ 29.3% dst/copy/rearranged/
+ 29.3% dst/copy/unchanged/
+ 88.0% dst/copy/
+ 5.8% dst/move/changed/
+ 0.1% dst/move/rearranged/
+ 5.9% dst/move/
+ 94.0% dst/
+ 0.1% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 32.6% changed/
+ 32.6% dst/copy/changed/
+ 0.6% dst/copy/rearranged/
+ 33.3% dst/copy/
+ 32.6% dst/move/changed/
+ 0.6% dst/move/rearranged/
+ 33.3% dst/move/
+ 66.6% dst/
+ 0.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=0 --cumulative' '
+ git diff --dirstat=0 --cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=0 --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=0 --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=0,cumulative' '
+ git diff --dirstat=0,cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '-X0,cumulative' '
+ git diff -X0,cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff -X0,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff -X0,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0,cumulative' '
+ git -c diff.dirstat=0,cumulative diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0,cumulative diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0,cumulative diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0 & --dirstat=cumulative' '
+ git -c diff.dirstat=0 diff --dirstat=cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0 diff --dirstat=cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0 diff --dirstat=cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 9.0% changed/
+ 9.0% dst/copy/changed/
+ 9.0% dst/copy/rearranged/
+ 9.0% dst/copy/unchanged/
+ 9.0% dst/move/changed/
+ 9.0% dst/move/rearranged/
+ 9.0% dst/move/unchanged/
+ 9.0% rearranged/
+ 9.0% src/move/changed/
+ 9.0% src/move/rearranged/
+ 9.0% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat-by-file' '
+ git diff --dirstat-by-file HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat-by-file -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat-by-file -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files' '
+ git diff --dirstat=files HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=files' '
+ git -c diff.dirstat=files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 27.2% dst/copy/
+ 27.2% dst/move/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat-by-file=10' '
+ git diff --dirstat-by-file=10 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat-by-file=10 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat-by-file=10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,10' '
+ git diff --dirstat=files,10 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,10 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=10,files' '
+ git -c diff.dirstat=10,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=10,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=10,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 9.0% changed/
+ 9.0% dst/copy/changed/
+ 9.0% dst/copy/rearranged/
+ 9.0% dst/copy/unchanged/
+ 27.2% dst/copy/
+ 9.0% dst/move/changed/
+ 9.0% dst/move/rearranged/
+ 9.0% dst/move/unchanged/
+ 27.2% dst/move/
+ 54.5% dst/
+ 9.0% rearranged/
+ 9.0% src/move/changed/
+ 9.0% src/move/rearranged/
+ 9.0% src/move/unchanged/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 42.8% dst/copy/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 28.5% dst/move/
+ 71.4% dst/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 33.3% dst/copy/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 33.3% dst/move/
+ 66.6% dst/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat-by-file --cumulative' '
+ git diff --dirstat-by-file --cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat-by-file --cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat-by-file --cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,cumulative' '
+ git diff --dirstat=files,cumulative HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=cumulative,files' '
+ git -c diff.dirstat=cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 27.2% dst/copy/
+ 27.2% dst/move/
+ 54.5% dst/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 14.2% changed/
+ 14.2% dst/copy/changed/
+ 14.2% dst/copy/rearranged/
+ 14.2% dst/copy/unchanged/
+ 42.8% dst/copy/
+ 14.2% dst/move/changed/
+ 14.2% dst/move/rearranged/
+ 28.5% dst/move/
+ 71.4% dst/
+ 14.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 33.3% dst/copy/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 33.3% dst/move/
+ 66.6% dst/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=files,cumulative,10' '
+ git diff --dirstat=files,cumulative,10 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,10 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,10 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=10,cumulative,files' '
+ git -c diff.dirstat=10,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=10,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=10,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 27.2% dst/copy/
+ 27.2% dst/move/
+ 54.5% dst/
+ 27.2% src/move/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 42.8% dst/copy/
+ 28.5% dst/move/
+ 71.4% dst/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 33.3% dst/copy/
+ 33.3% dst/move/
+ 66.6% dst/
+EOF
+
+test_expect_success '--dirstat=files,cumulative,16.7' '
+ git diff --dirstat=files,cumulative,16.7 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,16.7 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,16.7 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=16.7,cumulative,files' '
+ git -c diff.dirstat=16.7,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=16.7,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=16.7,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=16.70,cumulative,files' '
+ git -c diff.dirstat=16.70,cumulative,files diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=16.70,cumulative,files diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=16.70,cumulative,files diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,cumulative,27.2' '
+ git diff --dirstat=files,cumulative,27.2 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,27.2 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,27.2 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=files,cumulative,27.09' '
+ git diff --dirstat=files,cumulative,27.09 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=files,cumulative,27.09 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=files,cumulative,27.09 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 10.6% dst/copy/changed/
+ 10.6% dst/copy/rearranged/
+ 10.6% dst/copy/unchanged/
+ 10.6% dst/move/changed/
+ 10.6% dst/move/rearranged/
+ 10.6% dst/move/unchanged/
+ 10.6% src/move/changed/
+ 10.6% src/move/rearranged/
+ 10.6% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.2% changed/
+ 26.3% dst/copy/changed/
+ 26.3% dst/copy/rearranged/
+ 26.3% dst/copy/unchanged/
+ 5.2% dst/move/changed/
+ 5.2% dst/move/rearranged/
+ 5.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=lines' '
+ git diff --dirstat=lines HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=lines -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=lines -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=lines' '
+ git -c diff.dirstat=lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+cat <<EOF >expect_diff_dirstat
+ 2.1% changed/
+ 10.6% dst/copy/changed/
+ 10.6% dst/copy/rearranged/
+ 10.6% dst/copy/unchanged/
+ 10.6% dst/move/changed/
+ 10.6% dst/move/rearranged/
+ 10.6% dst/move/unchanged/
+ 2.1% rearranged/
+ 10.6% src/move/changed/
+ 10.6% src/move/rearranged/
+ 10.6% src/move/unchanged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_M
+ 5.2% changed/
+ 26.3% dst/copy/changed/
+ 26.3% dst/copy/rearranged/
+ 26.3% dst/copy/unchanged/
+ 5.2% dst/move/changed/
+ 5.2% dst/move/rearranged/
+ 5.2% rearranged/
+EOF
+
+cat <<EOF >expect_diff_dirstat_CC
+ 16.6% changed/
+ 16.6% dst/copy/changed/
+ 16.6% dst/copy/rearranged/
+ 16.6% dst/move/changed/
+ 16.6% dst/move/rearranged/
+ 16.6% rearranged/
+EOF
+
+test_expect_success '--dirstat=lines,0' '
+ git diff --dirstat=lines,0 HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git diff --dirstat=lines,0 -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git diff --dirstat=lines,0 -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success 'diff.dirstat=0,lines' '
+ git -c diff.dirstat=0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ git -c diff.dirstat=0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ git -c diff.dirstat=0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC
+'
+
+test_expect_success '--dirstat=future_param,lines,0 should fail loudly' '
+ test_must_fail git diff --dirstat=future_param,lines,0 HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp /dev/null actual_diff_dirstat &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "\--dirstat" actual_error
+'
+
+test_expect_success '--dirstat=dummy1,cumulative,2dummy should report both unrecognized parameters' '
+ test_must_fail git diff --dirstat=dummy1,cumulative,2dummy HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp /dev/null actual_diff_dirstat &&
+ test_i18ngrep -q "dummy1" actual_error &&
+ test_i18ngrep -q "2dummy" actual_error &&
+ test_i18ngrep -q "\--dirstat" actual_error
+'
+
+test_expect_success 'diff.dirstat=future_param,0,lines should warn, but still work' '
+ git -c diff.dirstat=future_param,0,lines diff --dirstat HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp expect_diff_dirstat actual_diff_dirstat &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "diff\\.dirstat" actual_error &&
+
+ git -c diff.dirstat=future_param,0,lines diff --dirstat -M HEAD^..HEAD >actual_diff_dirstat_M 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp expect_diff_dirstat_M actual_diff_dirstat_M &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "diff\\.dirstat" actual_error &&
+
+ git -c diff.dirstat=future_param,0,lines diff --dirstat -C -C HEAD^..HEAD >actual_diff_dirstat_CC 2>actual_error &&
+ test_debug "cat actual_error" &&
+ test_cmp expect_diff_dirstat_CC actual_diff_dirstat_CC &&
+ test_i18ngrep -q "future_param" actual_error &&
+ test_i18ngrep -q "diff\\.dirstat" actual_error
+'
+
+test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index b0b2684..0eace37 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -190,7 +190,7 @@ test_expect_success 'pushing explicit refspecs respects forcing' '
+refs/heads/master:refs/heads/master
) &&
parent_head=$(cd parent && git rev-parse --verify master) &&
- child_head=$(cd parent && git rev-parse --verify master) &&
+ child_head=$(cd child && git rev-parse --verify master) &&
test "$parent_head" = "$child_head"
'
@@ -210,7 +210,7 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
"+refs/heads/*:refs/heads/*"
) &&
parent_head=$(cd parent && git rev-parse --verify master) &&
- child_head=$(cd parent && git rev-parse --verify master) &&
+ child_head=$(cd child && git rev-parse --verify master) &&
test "$parent_head" = "$child_head"
'
diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh
new file mode 100755
index 0000000..62f2460
--- /dev/null
+++ b/t/t5532-fetch-proxy.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='fetching via git:// using core.gitproxy'
+. ./test-lib.sh
+
+test_expect_success 'setup remote repo' '
+ git init remote &&
+ (cd remote &&
+ echo content >file &&
+ git add file &&
+ git commit -m one
+ )
+'
+
+cat >proxy <<'EOF'
+#!/bin/sh
+echo >&2 "proxying for $*"
+cmd=`perl -e '
+ read(STDIN, $buf, 4);
+ my $n = hex($buf) - 4;
+ read(STDIN, $buf, $n);
+ my ($cmd, $other) = split /\0/, $buf;
+ # drop absolute-path on repo name
+ $cmd =~ s{ /}{ };
+ print $cmd;
+'`
+echo >&2 "Running '$cmd'"
+exec $cmd
+EOF
+chmod +x proxy
+test_expect_success 'setup local repo' '
+ git remote add fake git://example.com/remote &&
+ git config core.gitproxy ./proxy
+'
+
+test_expect_success 'fetch through proxy works' '
+ git fetch fake &&
+ echo one >expect &&
+ git log -1 --format=%s FETCH_HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
index d924056..a73c826 100755
--- a/t/t5541-http-push.sh
+++ b/t/t5541-http-push.sh
@@ -65,14 +65,16 @@ test_expect_success 'clone remote repository' '
git clone $HTTPD_URL/smart/test_repo.git test_repo_clone
'
-test_expect_success 'push to remote repository' '
+test_expect_success 'push to remote repository (standard)' '
cd "$ROOT_PATH"/test_repo_clone &&
: >path2 &&
git add path2 &&
test_tick &&
git commit -m path2 &&
HEAD=$(git rev-parse --verify HEAD) &&
- git push &&
+ GIT_CURL_VERBOSE=1 git push -v -v 2>err &&
+ ! grep "Expect: 100-continue" err &&
+ grep "POST git-receive-pack ([0-9]* bytes)" err &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
test $HEAD = $(git rev-parse --verify HEAD))
'
@@ -140,5 +142,17 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he
output
'
+test_expect_success 'push (chunked)' '
+ git checkout master &&
+ test_commit commit path3 &&
+ HEAD=$(git rev-parse --verify HEAD) &&
+ git config http.postbuffer 4 &&
+ test_when_finished "git config --unset http.postbuffer" &&
+ git push -v -v origin $BRANCH 2>err &&
+ grep "POST git-receive-pack (chunked)" err &&
+ (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+ test $HEAD = $(git rev-parse --verify HEAD))
+'
+
stop_httpd
test_done
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index 082032e..f80bba8 100755
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
@@ -8,38 +8,38 @@ test_description='Merge base and parent list computation.
. ./test-lib.sh
-test_expect_success 'setup' '
- T=$(git write-tree) &&
+M=1130000000
+Z=+0000
- M=1130000000 &&
- Z=+0000 &&
+GIT_COMMITTER_EMAIL=git@comm.iter.xz
+GIT_COMMITTER_NAME='C O Mmiter'
+GIT_AUTHOR_NAME='A U Thor'
+GIT_AUTHOR_EMAIL=git@au.thor.xz
+export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
- GIT_COMMITTER_EMAIL=git@comm.iter.xz &&
- GIT_COMMITTER_NAME="C O Mmiter" &&
- GIT_AUTHOR_NAME="A U Thor" &&
- GIT_AUTHOR_EMAIL=git@au.thor.xz &&
- export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
+doit () {
+ OFFSET=$1 &&
+ NAME=$2 &&
+ shift 2 &&
- doit() {
- OFFSET=$1 &&
- NAME=$2 &&
- shift 2 &&
+ PARENTS= &&
+ for P
+ do
+ PARENTS="${PARENTS}-p $P "
+ done &&
- PARENTS= &&
- for P
- do
- PARENTS="${PARENTS}-p $P "
- done &&
+ GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" &&
+ GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE &&
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE &&
- GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" &&
- GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE &&
- export GIT_COMMITTER_DATE GIT_AUTHOR_DATE &&
+ commit=$(echo $NAME | git commit-tree $T $PARENTS) &&
- commit=$(echo $NAME | git commit-tree $T $PARENTS) &&
+ echo $commit >.git/refs/tags/$NAME &&
+ echo $commit
+}
- echo $commit >.git/refs/tags/$NAME &&
- echo $commit
- }
+test_expect_success 'setup' '
+ T=$(git mktree </dev/null)
'
test_expect_success 'set up G and H' '
diff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh
index f1c32db..667b375 100755
--- a/t/t6017-rev-list-stdin.sh
+++ b/t/t6017-rev-list-stdin.sh
@@ -58,4 +58,21 @@ check side-3 ^side-4 -- file-3
check side-3 ^side-2
check side-3 ^side-2 -- file-1
+test_expect_success 'not only --stdin' '
+ cat >expect <<-EOF &&
+ 7
+
+ file-1
+ file-2
+ EOF
+ cat >input <<-EOF &&
+ ^master^
+ --
+ file-2
+ EOF
+ git log --pretty=tformat:%s --name-only --stdin master -- file-1 \
+ <input >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index fb8291c..f00cebf 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -69,6 +69,18 @@ test_expect_success 'rev-parse --glob=heads/subspace' '
'
+test_expect_failure 'rev-parse accepts --glob as detached option' '
+
+ compare rev-parse "subspace/one subspace/two" "--glob heads/subspace"
+
+'
+
+test_expect_failure 'rev-parse is not confused by option-like glob' '
+
+ compare rev-parse "master" "--glob --symbolic master"
+
+'
+
test_expect_success 'rev-parse --branches=subspace/*' '
compare rev-parse "subspace/one subspace/two" "--branches=subspace/*"
@@ -129,6 +141,12 @@ test_expect_success 'rev-list --glob refs/heads/subspace/*' '
'
+test_expect_success 'rev-list not confused by option-like --glob arg' '
+
+ compare rev-list "master" "--glob -0 master"
+
+'
+
test_expect_success 'rev-list --glob=heads/subspace/*' '
compare rev-list "subspace/one subspace/two" "--glob=heads/subspace/*"
@@ -213,4 +231,36 @@ test_expect_success 'rev-list --remotes=foo' '
'
+test_expect_success 'shortlog accepts --glob/--tags/--remotes' '
+
+ compare shortlog "subspace/one subspace/two" --branches=subspace &&
+ compare shortlog \
+ "master subspace-x someref other/three subspace/one subspace/two" \
+ --branches &&
+ compare shortlog master "--glob=heads/someref/* master" &&
+ compare shortlog "subspace/one subspace/two other/three" \
+ "--glob=heads/subspace/* --glob=heads/other/*" &&
+ compare shortlog \
+ "master other/three someref subspace-x subspace/one subspace/two" \
+ "--glob=heads/*" &&
+ compare shortlog foo/bar --tags=foo &&
+ compare shortlog foo/bar --tags &&
+ compare shortlog foo/baz --remotes=foo
+
+'
+
+test_expect_failure 'shortlog accepts --glob as detached option' '
+
+ compare shortlog \
+ "master other/three someref subspace-x subspace/one subspace/two" \
+ "--glob heads/*"
+
+'
+
+test_expect_failure 'shortlog --glob is not confused by option-like argument' '
+
+ compare shortlog master "--glob -e master"
+
+'
+
test_done
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index 5f3b604..1ed259d 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -609,67 +609,4 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
! test -f original
'
-test_expect_success 'setup avoid unnecessary update, normal rename' '
- git reset --hard &&
- git checkout --orphan avoid-unnecessary-update-1 &&
- git rm -rf . &&
- git clean -fdqx &&
-
- printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original &&
- git add -A &&
- git commit -m "Common commmit" &&
-
- git mv original rename &&
- echo 11 >>rename &&
- git add -u &&
- git commit -m "Renamed and modified" &&
-
- git checkout -b merge-branch-1 HEAD~1 &&
- echo "random content" >random-file &&
- git add -A &&
- git commit -m "Random, unrelated changes"
-'
-
-test_expect_success 'avoid unnecessary update, normal rename' '
- git checkout -q avoid-unnecessary-update-1^0 &&
- test-chmtime =1000000000 rename &&
- test-chmtime -v +0 rename >expect &&
- git merge merge-branch-1 &&
- test-chmtime -v +0 rename >actual &&
- test_cmp expect actual # "rename" should have stayed intact
-'
-
-test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' '
- git reset --hard &&
- git checkout --orphan avoid-unnecessary-update-2 &&
- git rm -rf . &&
- git clean -fdqx &&
-
- mkdir df &&
- printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file &&
- git add -A &&
- git commit -m "Common commmit" &&
-
- git mv df/file temp &&
- rm -rf df &&
- git mv temp df &&
- echo 11 >>df &&
- git add -u &&
- git commit -m "Renamed and modified" &&
-
- git checkout -b merge-branch-2 HEAD~1 &&
- >unrelated-change &&
- git add unrelated-change &&
- git commit -m "Only unrelated changes"
-'
-
-test_expect_failure 'avoid unnecessary update, with D/F conflict' '
- git checkout -q avoid-unnecessary-update-2^0 &&
- test-chmtime =1000000000 df &&
- test-chmtime -v +0 df >expect &&
- git merge merge-branch-2 &&
- test-chmtime -v +0 df >actual &&
- test_cmp expect actual # "df" should have stayed intact
-'
-
test_done
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index ae2194e..5c87f28 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -236,6 +236,20 @@ test_expect_success 'index-pack and replacements' '
git index-pack test-*.pack
'
-#
-#
+test_expect_success 'not just commits' '
+ echo replaced >file &&
+ git add file &&
+ REPLACED=$(git rev-parse :file) &&
+ mv file file.replaced &&
+
+ echo original >file &&
+ git add file &&
+ ORIGINAL=$(git rev-parse :file) &&
+ git update-ref refs/replace/$ORIGINAL $REPLACED &&
+ mv file file.original &&
+
+ git checkout file &&
+ test_cmp file.replaced file
+'
+
test_done
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
index 47096f9..1c908f4 100755
--- a/t/t7500-commit.sh
+++ b/t/t7500-commit.sh
@@ -123,6 +123,20 @@ test_expect_success 'commit message from file should override template' '
commit_msg_is "standard input msg"
'
+cat >"$TEMPLATE" <<\EOF
+
+
+### template
+
+EOF
+test_expect_success 'commit message from template with whitespace issue' '
+ echo "content galore" >>foo &&
+ git add foo &&
+ GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-whitespaced-content git commit \
+ --template "$TEMPLATE" &&
+ commit_msg_is "commit message"
+'
+
test_expect_success 'using alternate GIT_INDEX_FILE (1)' '
cp .git/index saved-index &&
diff --git a/t/t7500/add-whitespaced-content b/t/t7500/add-whitespaced-content
new file mode 100755
index 0000000..ccf07c6
--- /dev/null
+++ b/t/t7500/add-whitespaced-content
@@ -0,0 +1,8 @@
+#!/bin/sh
+sed -e 's/|$//' >>"$1" <<\EOF
+
+ |
+commit message |
+
+EOF
+exit 0
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7f7f7c7..3ad0436 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -42,10 +42,13 @@ test_expect_success \
"echo King of the bongo >file &&
test_must_fail git commit -m foo -a file"
-test_expect_success PERL \
- "using paths with --interactive" \
- "echo bong-o-bong >file &&
- ! (echo 7 | git commit -m foo --interactive file)"
+test_expect_success PERL 'can use paths with --interactive' '
+ echo bong-o-bong >file &&
+ # 2: update, 1:st path, that is all, 7: quit
+ ( echo 2; echo 1; echo; echo 7 ) |
+ git commit -m foo --interactive file &&
+ git reset --hard HEAD^
+'
test_expect_success \
"using invalid commit with -C" \
@@ -131,6 +134,16 @@ test_expect_success PERL \
"interactive add" \
"echo 7 | git commit --interactive | grep 'What now'"
+test_expect_success PERL \
+ "commit --interactive doesn't change index if editor aborts" \
+ "echo zoo >file &&
+ test_must_fail git diff --exit-code >diff1 &&
+ (echo u ; echo '*' ; echo q) |
+ (EDITOR=: && export EDITOR &&
+ test_must_fail git commit --interactive) &&
+ git diff >diff2 &&
+ test_cmp diff1 diff2"
+
test_expect_success \
"showing committed revisions" \
"git rev-list HEAD >current"
diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh
index c8d50a6..d31b34d 100755
--- a/t/t7506-status-submodule.sh
+++ b/t/t7506-status-submodule.sh
@@ -4,17 +4,21 @@ test_description='git status for submodule'
. ./test-lib.sh
-test_expect_success 'setup' '
- test_create_repo sub &&
+test_create_repo_with_commit () {
+ test_create_repo "$1" &&
(
- cd sub &&
+ cd "$1" &&
: >bar &&
git add bar &&
git commit -m " Add bar" &&
: >foo &&
git add foo &&
git commit -m " Add foo"
- ) &&
+ )
+}
+
+test_expect_success 'setup' '
+ test_create_repo_with_commit sub &&
echo output > .gitignore &&
git add sub .gitignore &&
git commit -m "Add submodule sub"
@@ -187,4 +191,84 @@ test_expect_success 'status -a clean (empty submodule dir)' '
test_i18ngrep "nothing to commit" output
'
+cat >status_expect <<\EOF
+AA .gitmodules
+A sub1
+EOF
+
+test_expect_success 'status with merge conflict in .gitmodules' '
+ git clone . super &&
+ test_create_repo_with_commit sub1 &&
+ test_tick &&
+ test_create_repo_with_commit sub2 &&
+ (
+ cd super &&
+ prev=$(git rev-parse HEAD) &&
+ git checkout -b add_sub1 &&
+ git submodule add ../sub1 &&
+ git commit -m "add sub1" &&
+ git checkout -b add_sub2 $prev &&
+ git submodule add ../sub2 &&
+ git commit -m "add sub2" &&
+ git checkout -b merge_conflict_gitmodules &&
+ test_must_fail git merge add_sub1 &&
+ git status -s >../status_actual 2>&1
+ ) &&
+ test_cmp status_actual status_expect
+'
+
+sha1_merge_sub1=$(cd sub1 && git rev-parse HEAD)
+sha1_merge_sub2=$(cd sub2 && git rev-parse HEAD)
+short_sha1_merge_sub1=$(cd sub1 && git rev-parse --short HEAD)
+short_sha1_merge_sub2=$(cd sub2 && git rev-parse --short HEAD)
+cat >diff_expect <<\EOF
+diff --cc .gitmodules
+index badaa4c,44f999a..0000000
+--- a/.gitmodules
++++ b/.gitmodules
+@@@ -1,3 -1,3 +1,9 @@@
+++<<<<<<< HEAD
+ +[submodule "sub2"]
+ + path = sub2
+ + url = ../sub2
+++=======
++ [submodule "sub1"]
++ path = sub1
++ url = ../sub1
+++>>>>>>> add_sub1
+EOF
+
+cat >diff_submodule_expect <<\EOF
+diff --cc .gitmodules
+index badaa4c,44f999a..0000000
+--- a/.gitmodules
++++ b/.gitmodules
+@@@ -1,3 -1,3 +1,9 @@@
+++<<<<<<< HEAD
+ +[submodule "sub2"]
+ + path = sub2
+ + url = ../sub2
+++=======
++ [submodule "sub1"]
++ path = sub1
++ url = ../sub1
+++>>>>>>> add_sub1
+EOF
+
+test_expect_success 'diff with merge conflict in .gitmodules' '
+ (
+ cd super &&
+ git diff >../diff_actual 2>&1
+ ) &&
+ test_cmp diff_actual diff_expect
+'
+
+test_expect_success 'diff --submodule with merge conflict in .gitmodules' '
+ (
+ cd super &&
+ git diff --submodule >../diff_submodule_actual 2>&1
+ ) &&
+ test_cmp diff_submodule_actual diff_submodule_expect
+'
+
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index e84e822..87aac83 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -28,80 +28,80 @@ Testing basic merge operations/option parsing.
. ./test-lib.sh
-test_expect_success 'set up test data and helpers' '
- printf "%s\n" 1 2 3 4 5 6 7 8 9 >file &&
- printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >file.1 &&
- printf "%s\n" 1 2 3 4 "5 X" 6 7 8 9 >file.5 &&
- printf "%s\n" 1 2 3 4 5 6 7 8 "9 X" >file.9 &&
- printf "%s\n" "1 X" 2 3 4 5 6 7 8 9 >result.1 &&
- printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 9 >result.1-5 &&
- printf "%s\n" "1 X" 2 3 4 "5 X" 6 7 8 "9 X" >result.1-5-9 &&
-
- create_merge_msgs() {
- echo "Merge commit '\''c2'\''" >msg.1-5 &&
- echo "Merge commit '\''c2'\''; commit '\''c3'\''" >msg.1-5-9 &&
- {
- echo "Squashed commit of the following:" &&
- echo &&
- git log --no-merges ^HEAD c1
- } >squash.1 &&
- {
- echo "Squashed commit of the following:" &&
- echo &&
- git log --no-merges ^HEAD c2
- } >squash.1-5 &&
- {
- echo "Squashed commit of the following:" &&
- echo &&
- git log --no-merges ^HEAD c2 c3
- } >squash.1-5-9 &&
- echo >msg.nolog &&
- {
- echo "* commit '\''c3'\'':" &&
- echo " commit 3" &&
- echo
- } >msg.log
- } &&
-
- verify_merge() {
- test_cmp "$2" "$1" &&
- git update-index --refresh &&
- git diff --exit-code &&
- if test -n "$3"
- then
- git show -s --pretty=format:%s HEAD >msg.act &&
- test_cmp "$3" msg.act
- fi
- } &&
-
- verify_head() {
- echo "$1" >head.expected &&
- git rev-parse HEAD >head.actual &&
- test_cmp head.expected head.actual
- } &&
-
- verify_parents() {
- printf "%s\n" "$@" >parents.expected &&
- >parents.actual &&
- i=1 &&
- while test $i -le $#
- do
- git rev-parse HEAD^$i >>parents.actual &&
- i=$(expr $i + 1) ||
- return 1
- done &&
- test_cmp parents.expected parents.actual
- } &&
-
- verify_mergeheads() {
- printf "%s\n" "$@" >mergehead.expected &&
- test_cmp mergehead.expected .git/MERGE_HEAD
- } &&
-
- verify_no_mergehead() {
- ! test -e .git/MERGE_HEAD
- }
-'
+printf '%s\n' 1 2 3 4 5 6 7 8 9 >file
+printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
+printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5
+printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
+printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
+printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
+printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+>empty
+
+create_merge_msgs () {
+ echo "Merge commit 'c2'" >msg.1-5 &&
+ echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 &&
+ {
+ echo "Squashed commit of the following:" &&
+ echo &&
+ git log --no-merges ^HEAD c1
+ } >squash.1 &&
+ {
+ echo "Squashed commit of the following:" &&
+ echo &&
+ git log --no-merges ^HEAD c2
+ } >squash.1-5 &&
+ {
+ echo "Squashed commit of the following:" &&
+ echo &&
+ git log --no-merges ^HEAD c2 c3
+ } >squash.1-5-9 &&
+ echo >msg.nolog &&
+ {
+ echo "* commit 'c3':" &&
+ echo " commit 3" &&
+ echo
+ } >msg.log
+}
+
+verify_merge () {
+ test_cmp "$2" "$1" &&
+ git update-index --refresh &&
+ git diff --exit-code &&
+ if test -n "$3"
+ then
+ git show -s --pretty=format:%s HEAD >msg.act &&
+ test_cmp "$3" msg.act
+ fi
+}
+
+verify_head () {
+ echo "$1" >head.expected &&
+ git rev-parse HEAD >head.actual &&
+ test_cmp head.expected head.actual
+}
+
+verify_parents () {
+ printf '%s\n' "$@" >parents.expected &&
+ >parents.actual &&
+ i=1 &&
+ while test $i -le $#
+ do
+ git rev-parse HEAD^$i >>parents.actual &&
+ i=$(expr $i + 1) ||
+ return 1
+ done &&
+ test_must_fail git rev-parse --verify "HEAD^$i" &&
+ test_cmp parents.expected parents.actual
+}
+
+verify_mergeheads () {
+ printf '%s\n' "$@" >mergehead.expected &&
+ test_cmp mergehead.expected .git/MERGE_HEAD
+}
+
+verify_no_mergehead () {
+ ! test -e .git/MERGE_HEAD
+}
test_expect_success 'setup' '
git add file &&
@@ -225,12 +225,28 @@ test_expect_success 'merge c1 with c2 and c3' '
test_debug 'git log --graph --decorate --oneline --all'
-test_expect_success 'failing merges with --ff-only' '
+test_expect_success 'merges with --ff-only' '
git reset --hard c1 &&
test_tick &&
test_must_fail git merge --ff-only c2 &&
test_must_fail git merge --ff-only c3 &&
- test_must_fail git merge --ff-only c2 c3
+ test_must_fail git merge --ff-only c2 c3 &&
+ git reset --hard c0 &&
+ git merge c3 &&
+ verify_head $c3
+'
+
+test_expect_success 'merges with merge.ff=only' '
+ git reset --hard c1 &&
+ test_tick &&
+ test_when_finished "git config --unset merge.ff" &&
+ git config merge.ff only &&
+ test_must_fail git merge c2 &&
+ test_must_fail git merge c3 &&
+ test_must_fail git merge c2 c3 &&
+ git reset --hard c0 &&
+ git merge c3 &&
+ verify_head $c3
'
test_expect_success 'merge c0 with c1 (no-commit)' '
@@ -324,6 +340,39 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' '
test_debug 'git log --graph --decorate --oneline --all'
+test_expect_success 'merge c1 with c2 (log in config)' '
+ git config branch.master.mergeoptions "" &&
+ git reset --hard c1 &&
+ git merge --log c2 &&
+ git show -s --pretty=tformat:%s%n%b >expect &&
+
+ git config branch.master.mergeoptions --log &&
+ git reset --hard c1 &&
+ git merge c2 &&
+ git show -s --pretty=tformat:%s%n%b >actual &&
+
+ test_cmp expect actual
+'
+
+test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
+ test_when_finished "git config --remove-section branch.master" &&
+ test_when_finished "git config --remove-section merge" &&
+ test_might_fail git config --remove-section branch.master &&
+ test_might_fail git config --remove-section merge &&
+
+ git reset --hard c1 &&
+ git merge c2 &&
+ git show -s --pretty=tformat:%s%n%b >expect &&
+
+ git config branch.master.mergeoptions "--no-log" &&
+ git config merge.log true &&
+ git reset --hard c1 &&
+ git merge c2 &&
+ git show -s --pretty=tformat:%s%n%b >actual &&
+
+ test_cmp expect actual
+'
+
test_expect_success 'merge c1 with c2 (squash in config)' '
git reset --hard c1 &&
git config branch.master.mergeoptions "--squash" &&
@@ -415,7 +464,41 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
test_debug 'git log --graph --decorate --oneline --all'
+test_expect_success 'merge c0 with c1 (merge.ff=false)' '
+ git reset --hard c0 &&
+ git config merge.ff false &&
+ test_tick &&
+ git merge c1 &&
+ git config --remove-section merge &&
+ verify_merge file result.1 &&
+ verify_parents $c0 $c1
+'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
+ git reset --hard c0 &&
+ git config branch.master.mergeoptions --ff &&
+ git config merge.ff false &&
+ test_tick &&
+ git merge c1 &&
+ git config --remove-section "branch.master" &&
+ git config --remove-section "merge" &&
+ verify_merge file result.1 &&
+ verify_parents "$c0"
+'
+
+test_expect_success 'tolerate unknown values for merge.ff' '
+ git reset --hard c0 &&
+ git config merge.ff something-new &&
+ test_tick &&
+ git merge c1 2>message &&
+ git config --remove-section "merge" &&
+ verify_head "$c1" &&
+ test_cmp empty message
+'
+
test_expect_success 'combining --squash and --no-ff is refused' '
+ git reset --hard c0 &&
test_must_fail git merge --squash --no-ff c1 &&
test_must_fail git merge --no-ff --squash c1
'
diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh
index 5d477e4..cf4c052 100755
--- a/t/t9116-git-svn-log.sh
+++ b/t/t9116-git-svn-log.sh
@@ -60,6 +60,21 @@ test_expect_success 'test ascending revision range' "
git svn log -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | test_cmp expected-range-r1-r2-r4 -
"
+test_expect_success 'test ascending revision range with --show-commit' "
+ git reset --hard trunk &&
+ git svn log --show-commit -r 1:4 | grep '^r[0-9]' | cut -d'|' -f1 | test_cmp expected-range-r1-r2-r4 -
+ "
+
+test_expect_success 'test ascending revision range with --show-commit (sha1)' "
+ git svn find-rev r1 >expected-range-r1-r2-r4-sha1 &&
+ git svn find-rev r2 >>expected-range-r1-r2-r4-sha1 &&
+ git svn find-rev r4 >>expected-range-r1-r2-r4-sha1 &&
+ git reset --hard trunk &&
+ git svn log --show-commit -r 1:4 | grep '^r[0-9]' | cut -d'|' -f2 >out &&
+ git rev-parse \$(cat out) >actual &&
+ test_cmp expected-range-r1-r2-r4-sha1 actual
+ "
+
printf 'r4 \nr2 \nr1 \n' > expected-range-r4-r2-r1
test_expect_success 'test descending revision range' "
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index afac5b5..71ef0ac 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -595,4 +595,53 @@ test_expect_success HIGHLIGHT \
git commit -m "Add test.sh" &&
gitweb_run "p=.git;a=blob;f=test.sh"'
+# ----------------------------------------------------------------------
+# forks of projects
+
+cat >>gitweb_config.perl <<\EOF &&
+$feature{'forks'}{'default'} = [1];
+EOF
+
+test_expect_success \
+ 'forks: prepare' \
+ 'git init --bare foo.git &&
+ git --git-dir=foo.git --work-tree=. add file &&
+ git --git-dir=foo.git --work-tree=. commit -m "Initial commit" &&
+ echo "foo" > foo.git/description &&
+ mkdir -p foo &&
+ (cd foo &&
+ git clone --shared --bare ../foo.git foo-forked.git &&
+ echo "fork of foo" > foo-forked.git/description)'
+
+test_expect_success \
+ 'forks: projects list' \
+ 'gitweb_run'
+
+test_expect_success \
+ 'forks: forks action' \
+ 'gitweb_run "p=foo.git;a=forks"'
+
+# ----------------------------------------------------------------------
+# content tags (tag cloud)
+
+cat >>gitweb_config.perl <<-\EOF &&
+# we don't test _setting_ content tags, so any true value is good
+$feature{'ctags'}{'default'} = ['ctags_script.cgi'];
+EOF
+
+test_expect_success \
+ 'ctags: tag cloud in projects list' \
+ 'mkdir .git/ctags &&
+ echo "2" > .git/ctags/foo &&
+ echo "1" > .git/ctags/bar &&
+ gitweb_run'
+
+test_expect_success \
+ 'ctags: search projects by existing tag' \
+ 'gitweb_run "by_tag=foo"'
+
+test_expect_success \
+ 'ctags: search projects by non existent tag' \
+ 'gitweb_run "by_tag=non-existent"'
+
test_done
diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh
index dd83890..731e64c 100755
--- a/t/t9502-gitweb-standalone-parse-output.sh
+++ b/t/t9502-gitweb-standalone-parse-output.sh
@@ -112,4 +112,78 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' '
'
test_debug 'cat gitweb.headers'
+# ----------------------------------------------------------------------
+# forks of projects
+
+test_expect_success 'forks: setup' '
+ git init --bare foo.git &&
+ echo file > file &&
+ git --git-dir=foo.git --work-tree=. add file &&
+ git --git-dir=foo.git --work-tree=. commit -m "Initial commit" &&
+ echo "foo" > foo.git/description &&
+ git clone --bare foo.git foo.bar.git &&
+ echo "foo.bar" > foo.bar.git/description &&
+ git clone --bare foo.git foo_baz.git &&
+ echo "foo_baz" > foo_baz.git/description &&
+ rm -fr foo &&
+ mkdir -p foo &&
+ (
+ cd foo &&
+ git clone --shared --bare ../foo.git foo-forked.git &&
+ echo "fork of foo" > foo-forked.git/description
+ )
+'
+
+test_expect_success 'forks: not skipped unless "forks" feature enabled' '
+ gitweb_run "a=project_list" &&
+ grep -q ">\\.git<" gitweb.body &&
+ grep -q ">foo\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -q ">foo\\.bar\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -q ">foo/foo-forked\\.git<" gitweb.body &&
+ grep -q ">fork of .*<" gitweb.body
+'
+
+cat >>gitweb_config.perl <<\EOF &&
+$feature{'forks'}{'default'} = [1];
+EOF
+
+test_expect_success 'forks: forks skipped if "forks" feature enabled' '
+ gitweb_run "a=project_list" &&
+ grep -q ">\\.git<" gitweb.body &&
+ grep -q ">foo\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -q ">foo\\.bar\\.git<" gitweb.body &&
+ grep -q ">foo_baz\\.git<" gitweb.body &&
+ grep -v ">foo/foo-forked\\.git<" gitweb.body &&
+ grep -v ">fork of .*<" gitweb.body
+'
+
+test_expect_success 'forks: "forks" action for forked repository' '
+ gitweb_run "p=foo.git;a=forks" &&
+ grep -q ">foo/foo-forked\\.git<" gitweb.body &&
+ grep -q ">fork of foo<" gitweb.body
+'
+
+test_expect_success 'forks: can access forked repository' '
+ gitweb_run "p=foo/foo-forked.git;a=summary" &&
+ grep -q "200 OK" gitweb.headers &&
+ grep -q ">fork of foo<" gitweb.body
+'
+
+test_expect_success 'forks: project_index lists all projects (incl. forks)' '
+ cat >expected <<-\EOF
+ .git
+ foo.bar.git
+ foo.git
+ foo/foo-forked.git
+ foo_baz.git
+ EOF
+ gitweb_run "a=project_index" &&
+ sed -e "s/ .*//" <gitweb.body | sort >actual &&
+ test_cmp expected actual
+'
+
+
test_done
diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh
index a523473..33b0127 100755
--- a/t/t9800-git-p4.sh
+++ b/t/t9800-git-p4.sh
@@ -12,6 +12,8 @@ test_description='git-p4 tests'
GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
P4DPORT=10669
+export P4PORT=localhost:$P4DPORT
+
db="$TRASH_DIRECTORY/db"
cli="$TRASH_DIRECTORY/cli"
git="$TRASH_DIRECTORY/git"
@@ -129,6 +131,129 @@ test_expect_success 'clone bare' '
rm -rf "$git" && mkdir "$git"
'
+p4_add_user() {
+ name=$1
+ fullname=$2
+ p4 user -f -i <<EOF &&
+User: $name
+Email: $name@localhost
+FullName: $fullname
+EOF
+ p4 passwd -P secret $name
+}
+
+p4_grant_admin() {
+ name=$1
+ p4 protect -o |\
+ awk "{print}END{print \" admin user $name * //depot/...\"}" |\
+ p4 protect -i
+}
+
+p4_check_commit_author() {
+ file=$1
+ user=$2
+ if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then
+ return 0
+ else
+ echo "file $file not modified by user $user" 1>&2
+ return 1
+ fi
+}
+
+make_change_by_user() {
+ file=$1 name=$2 email=$3 &&
+ echo "username: a change by $name" >>"$file" &&
+ git add "$file" &&
+ git commit --author "$name <$email>" -m "a change by $name"
+}
+
+# Test username support, submitting as user 'alice'
+test_expect_success 'preserve users' '
+ p4_add_user alice Alice &&
+ p4_add_user bob Bob &&
+ p4_grant_admin alice &&
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ echo "username: a change by alice" >> file1 &&
+ echo "username: a change by bob" >> file2 &&
+ git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
+ git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
+ git config git-p4.skipSubmitEditCheck true &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ p4_check_commit_author file1 alice &&
+ p4_check_commit_author file2 bob &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+# Test username support, submitting as bob, who lacks admin rights. Should
+# not submit change to p4 (git diff should show deltas).
+test_expect_success 'refuse to preserve users without perms' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ echo "username-noperms: a change by alice" >> file1 &&
+ git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
+ ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ ! git diff --exit-code HEAD..p4/master > /dev/null &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+# What happens with unknown author? Without allowMissingP4Users it should fail.
+test_expect_success 'preserve user where author is unknown to p4' '
+ "$GITP4" clone --dest="$git" //depot &&
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true
+ echo "username-bob: a change by bob" >> file1 &&
+ git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+ echo "username-unknown: a change by charlie" >> file1 &&
+ git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
+ ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+ ! git diff --exit-code HEAD..p4/master > /dev/null &&
+ echo "$0: repeat with allowMissingP4Users enabled" &&
+ git config git-p4.allowMissingP4Users true &&
+ git config git-p4.preserveUser true &&
+ P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
+ git diff --exit-code HEAD..p4/master > /dev/null &&
+ p4_check_commit_author file1 alice &&
+ cd "$TRASH_DIRECTORY" &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
+# changes that are not all ours.
+# Test: user in p4 and user unknown to p4.
+# Test: warning disabled and user is the same.
+test_expect_success 'not preserving user with mixed authorship' '
+ "$GITP4" clone --dest="$git" //depot &&
+ (
+ cd "$git" &&
+ git config git-p4.skipSubmitEditCheck true &&
+ p4_add_user derek Derek &&
+
+ make_change_by_user usernamefile3 Derek derek@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ grep "git author derek@localhost does not match" actual &&
+
+ make_change_by_user usernamefile3 Charlie charlie@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ grep "git author charlie@localhost does not match" actual &&
+
+ make_change_by_user usernamefile3 alice alice@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ ! grep "git author.*does not match" actual &&
+
+ git config git-p4.skipUserNameCheck true &&
+ make_change_by_user usernamefile3 Charlie charlie@localhost &&
+ P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
+ ! grep "git author.*does not match" actual &&
+
+ p4_check_commit_author usernamefile3 alice
+ ) &&
+ rm -rf "$git" && mkdir "$git"
+'
+
+
test_expect_success 'shutdown' '
pid=`pgrep -f p4d` &&
test -n "$pid" &&
diff --git a/unpack-trees.c b/unpack-trees.c
index 500ebcf..0bc4b2d 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -814,43 +814,45 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
return mask;
}
+static int clear_ce_flags_1(struct cache_entry **cache, int nr,
+ char *prefix, int prefix_len,
+ int select_mask, int clear_mask,
+ struct exclude_list *el, int defval);
+
/* Whole directory matching */
static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
char *prefix, int prefix_len,
char *basename,
int select_mask, int clear_mask,
- struct exclude_list *el)
+ struct exclude_list *el, int defval)
{
- struct cache_entry **cache_end = cache + nr;
+ struct cache_entry **cache_end;
int dtype = DT_DIR;
int ret = excluded_from_list(prefix, prefix_len, basename, &dtype, el);
prefix[prefix_len++] = '/';
- /* included, no clearing for any entries under this directory */
- if (!ret) {
- for (; cache != cache_end; cache++) {
- struct cache_entry *ce = *cache;
- if (strncmp(ce->name, prefix, prefix_len))
- break;
- }
- return nr - (cache_end - cache);
- }
+ /* If undecided, use matching result of parent dir in defval */
+ if (ret < 0)
+ ret = defval;
- /* excluded, clear all selected entries under this directory. */
- if (ret == 1) {
- for (; cache != cache_end; cache++) {
- struct cache_entry *ce = *cache;
- if (select_mask && !(ce->ce_flags & select_mask))
- continue;
- if (strncmp(ce->name, prefix, prefix_len))
- break;
- ce->ce_flags &= ~clear_mask;
- }
- return nr - (cache_end - cache);
+ for (cache_end = cache; cache_end != cache + nr; cache_end++) {
+ struct cache_entry *ce = *cache_end;
+ if (strncmp(ce->name, prefix, prefix_len))
+ break;
}
- return 0;
+ /*
+ * TODO: check el, if there are no patterns that may conflict
+ * with ret (iow, we know in advance the incl/excl
+ * decision for the entire directory), clear flag here without
+ * calling clear_ce_flags_1(). That function will call
+ * the expensive excluded_from_list() on every entry.
+ */
+ return clear_ce_flags_1(cache, cache_end - cache,
+ prefix, prefix_len,
+ select_mask, clear_mask,
+ el, ret);
}
/*
@@ -871,7 +873,7 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr,
static int clear_ce_flags_1(struct cache_entry **cache, int nr,
char *prefix, int prefix_len,
int select_mask, int clear_mask,
- struct exclude_list *el)
+ struct exclude_list *el, int defval)
{
struct cache_entry **cache_end = cache + nr;
@@ -882,7 +884,7 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr,
while(cache != cache_end) {
struct cache_entry *ce = *cache;
const char *name, *slash;
- int len, dtype;
+ int len, dtype, ret;
if (select_mask && !(ce->ce_flags & select_mask)) {
cache++;
@@ -911,7 +913,7 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr,
prefix, prefix_len + len,
prefix + prefix_len,
select_mask, clear_mask,
- el);
+ el, defval);
/* clear_c_f_dir eats a whole dir already? */
if (processed) {
@@ -922,13 +924,16 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr,
prefix[prefix_len + len++] = '/';
cache += clear_ce_flags_1(cache, cache_end - cache,
prefix, prefix_len + len,
- select_mask, clear_mask, el);
+ select_mask, clear_mask, el, defval);
continue;
}
/* Non-directory */
dtype = ce_to_dtype(ce);
- if (excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el) > 0)
+ ret = excluded_from_list(ce->name, ce_namelen(ce), name, &dtype, el);
+ if (ret < 0)
+ ret = defval;
+ if (ret > 0)
ce->ce_flags &= ~clear_mask;
cache++;
}
@@ -943,7 +948,7 @@ static int clear_ce_flags(struct cache_entry **cache, int nr,
return clear_ce_flags_1(cache, nr,
prefix, 0,
select_mask, clear_mask,
- el);
+ el, 0);
}
/*
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 164581f..0e2c169 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -347,7 +347,7 @@ int git_xmerge_style = -1;
int git_xmerge_config(const char *var, const char *value, void *cb)
{
- if (!strcasecmp(var, "merge.conflictstyle")) {
+ if (!strcmp(var, "merge.conflictstyle")) {
if (!value)
die("'%s' is not a boolean", var);
if (!strcmp(value, "diff3"))