Merge master branch for tracking l10n updates of next release

Use master branch to track l10n updates for git next release, while
use maint branch to track l10n updates for git stable version.
This commit is contained in:
Jiang Xin 2012-04-28 15:54:37 +08:00
commit 69c835701b
207 changed files with 7948 additions and 1903 deletions

3
.gitignore vendored
View File

@ -92,6 +92,7 @@
/git-name-rev /git-name-rev
/git-mv /git-mv
/git-notes /git-notes
/git-p4
/git-pack-redundant /git-pack-redundant
/git-pack-objects /git-pack-objects
/git-pack-refs /git-pack-refs
@ -180,9 +181,11 @@
/test-index-version /test-index-version
/test-line-buffer /test-line-buffer
/test-match-trees /test-match-trees
/test-mergesort
/test-mktemp /test-mktemp
/test-parse-options /test-parse-options
/test-path-utils /test-path-utils
/test-revision-walking
/test-run-command /test-run-command
/test-sha1 /test-sha1
/test-sigchain /test-sigchain

View File

@ -124,6 +124,16 @@ SHELL_PATH ?= $(SHELL)
# Shell quote; # Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
ifdef DEFAULT_PAGER
DEFAULT_PAGER_SQ = $(subst ','\'',$(DEFAULT_PAGER))
ASCIIDOC_EXTRA += -a 'git-default-pager=$(DEFAULT_PAGER_SQ)'
endif
ifdef DEFAULT_EDITOR
DEFAULT_EDITOR_SQ = $(subst ','\'',$(DEFAULT_EDITOR))
ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)'
endif
# #
# Please note that there is a minor bug in asciidoc. # Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here: # The version after 6.0.3 _will_ include the patch found here:

View File

@ -0,0 +1,50 @@
Git v1.7.10.1 Release Notes
===========================
Fixes since v1.7.10
-------------------
* "git add -p" is not designed to deal with unmerged paths but did
not exclude them and tried to apply funny patches only to fail.
* When PATH contains an unreadable directory, alias expansion code
did not kick in, and failed with an error that said "git-subcmd"
was not found.
* "git clean -d -f" (not "-d -f -f") is supposed to protect nested
working trees of independent git repositories that exist in the
current project working tree from getting removed, but the
protection applied only to such working trees that are at the
top-level of the current project by mistake.
* "git commit --author=$name" did not tell the name that was being
recorded in the resulting commit to hooks, even though it does do
so when the end user overrode the authorship via the
"GIT_AUTHOR_NAME" environment variable.
* When "git commit --template F" errors out because the user did not
touch the message, it claimed that it aborts due to "empty
message", which was utterly wrong.
* The regexp configured with diff.wordregex was incorrectly reused
across files.
* An age-old corner case bug in combine diff (only triggered with -U0
and the hunk at the beginning of the file needs to be shown) has
been fixed.
* Rename detection logic used to match two empty files as renames
during merge-recursive, leading to unnatural mismerges.
* Running "notes merge --commit" failed to perform correctly when run
from any directory inside $GIT_DIR/. When "notes merge" stops with
conflicts, $GIT_DIR/NOTES_MERGE_WORKTREE is the place a user edits
to resolve it.
* The 'push to upstream' implementation was broken in some corner
cases. "git push $there" without refspec, when the current branch
is set to push to a remote different from $there, used to push to
$there using the upstream information to a remote unreleated to
$there.
Also contains minor fixes and documentation updates.

View File

@ -0,0 +1,150 @@
Git v1.7.11 Release Notes
=========================
Updates since v1.7.10
---------------------
UI, Workflows & Features
* A third-party tool "git subtree" is distributed in contrib/
* Error messages given when @{u} is used for a branch without its
upstream configured have been clatified.
* Even with "-q"uiet option, "checkout" used to report setting up
tracking. Also "branch" learned the "-q"uiet option to squelch
informational message.
* The smart-http backend used to always override GIT_COMMITTER_*
variables with REMOTE_USER and REMOTE_ADDR, but these variables are
now preserved when set.
* "git am" learned the "--include" option, which is an opposite of
existing the "--exclude" option.
* When "git am -3" needs to fall back to an application to a
synthesized preimage followed by a 3-way merge, the paths that
needed such treatment are now reported to the end user, so that the
result in them can be eyeballed with extra care.
* The "fmt-merge-msg" command learns to list the primary contributors
involved in the side topic you are merging.
* The cases "git push" fails due to non-ff can be broken into three
categories; each case is given a separate advise message.
* "git push --recurse-submodules" learned to optionally look into the
histories of submodules bound to the superproject and push them
out.
* A 'snapshot' request to "gitweb" honors If-Modified-Since: header,
based on the commit date.
* "gitweb" learned to highlight the patch it outputs even more.
Foreign Interface
* "git svn" used to die with unwanted SIGPIPE when talking with HTTP
server that uses keep-alive.
* "git p4" has been moved out of contrib/ area.
Performance
* "git apply" had some memory leaks plugged.
* Setting up a revision traversal with many starting points was
inefficient as these were placed in a date-order priority queue
one-by-one. Now they are collected in the queue unordered first,
and sorted immediately before getting used.
Internal Implementation (please report possible regressions)
* "git rev-parse --show-prefix" used to emit nothing when run at the
top-level of the working tree, but now it gives a blank line.
* Minor memory leak during unpack_trees (hence "merge" and "checkout"
to check out another branch) has been plugged.
* More lower-level commands learned to use the streaming API to read
from the object store without keeping everything in core.
* Because "sh" on the user's PATH may be utterly broken on some
systems, run-command API now uses SHELL_PATH, not /bin/sh, when
spawning an external command (not applicable to Windows port).
* The API to iterate over refs/ hierarchy has been tweaked to allow
walking only a subset of it more efficiently.
Also contains minor documentation updates and code clean-ups.
Fixes since v1.7.10
-------------------
Unless otherwise noted, all the fixes since v1.7.10 in the maintenance
releases are contained in this release (see release notes to them for
details).
* Octopus merge strategy did not reduce heads that are recorded in the
final commit correctly.
(merge 5802f81 jc/merge-reduce-parents-early later to maint).
* In the older days, the header "Conflicts:" in "cherry-pick" and
"merge" was separated by a blank line from the list of paths that
follow for readability, but when "merge" was rewritten in C, we lost
it by mistake. Remove the newline from "cherry-pick" to make them
match again.
(merge 5112068 rt/cherry-revert-conflict-summary later to maint).
* The filesystem boundary was not correctly reported when .git
directory discovery stopped at a mount point.
(merge 2565b43 cb/maint-report-mount-point-correctly-in-setup later to maint).
* The command line parser choked "git cherry-pick $name" when $name
can be both revision name and a pathname, even though $name can
never be a path in the context of the command.
(merge 6d5b93f cb/cherry-pick-rev-path-confusion later to maint).
* HTTP transport that requires authentication did not work correctly
when multiple connections are used simultaneously.
(merge 6f4c347 cb/http-multi-curl-auth later to maint).
* The i18n of error message "git stash save" was not properly done.
(merge ed3c400 rl/maint-stash-i18n-save-error later to maint).
* The report from "git fetch" said "new branch" even for a non branch
ref.
(merge 0997ada mb/fetch-call-a-non-branch-a-ref later to maint).
* The "diff --no-index" codepath used limited-length buffers, risking
pathnames getting truncated. Update it to use the strbuf API.
(merge 875b91b jm/maint-strncpy-diff-no-index later to maint).
* The parser in "fast-import" did not diagnose ":9" style references
that is not followed by required SP/LF as an error.
(merge 06454cb pw/fast-import-dataref-parsing later to maint).
* When "git fetch" encounters repositories with too many references,
the command line of "fetch-pack" that is run by a helper
e.g. remote-curl, may fail to hold all of them. Now such an
internal invocation can feed the references through the standard
input of "fetch-pack".
(merge 7103d25 it/fetch-pack-many-refs later to maint).
* "git fetch" that recurses into submodules on demand did not check
if it needs to go into submodules when non branches (most notably,
tags) are fetched.
(merge a6801ad jl/maint-submodule-recurse-fetch later to maint).
* "git blame" started missing quite a few changes from the origin
since we stopped using the diff minimalization by default in v1.7.2
era.
(merge 059a500 jc/maint-blame-minimal later to maint).
* "log -p --graph" used with "--stat" had a few formatting error.
(merge e2c5966 lp/maint-diff-three-dash-with-graph later to maint).
* Giving "--continue" to a conflicted "rebase -i" session skipped a
commit that only results in changes to submodules.
(merge a6754cd jk/rebase-i-submodule-conflict-only later to maint).

View File

@ -0,0 +1,13 @@
Git v1.7.7.7 Release Notes
==========================
Fixes since v1.7.7.6
--------------------
* An error message from 'git bundle' had an unmatched single quote pair in it.
* 'git diff --histogram' option was not described.
* 'git imap-send' carried an unused dead code.
Also contains minor fixes and documentation updates.

View File

@ -0,0 +1,22 @@
Git v1.7.8.6 Release Notes
==========================
Fixes since v1.7.8.5
--------------------
* An error message from 'git bundle' had an unmatched single quote pair in it.
* 'git diff --histogram' option was not described.
* Documentation for 'git rev-list' had minor formatting errors.
* 'git imap-send' carried an unused dead code.
* The way 'git fetch' implemented its connectivity check over
received objects was overly pessimistic, and wasted a lot of
cycles.
* Various minor backports of fixes from the 'master' and the 'maint'
branch.
Also contains minor fixes and documentation updates.

View File

@ -0,0 +1,13 @@
Git v1.7.9.7 Release Notes
==========================
Fixes since v1.7.9.6
--------------------
* An error message from 'git bundle' had an unmatched single quote pair in it.
* The way 'git fetch' implemented its connectivity check over
received objects was overly pessimistic, and wasted a lot of
cycles.
Also contains minor fixes and documentation updates.

View File

@ -138,8 +138,23 @@ advice.*::
+ +
-- --
pushNonFastForward:: pushNonFastForward::
Advice shown when linkgit:git-push[1] refuses Set this variable to 'false' if you want to disable
non-fast-forward refs. 'pushNonFFCurrent', 'pushNonFFDefault', and
'pushNonFFMatching' simultaneously.
pushNonFFCurrent::
Advice shown when linkgit:git-push[1] fails due to a
non-fast-forward update to the current branch.
pushNonFFDefault::
Advice to set 'push.default' to 'upstream' or 'current'
when you ran linkgit:git-push[1] and pushed 'matching
refs' by default (i.e. you did not provide an explicit
refspec, and no 'push.default' configuration was set)
and it resulted in a non-fast-forward error.
pushNonFFMatching::
Advice shown when you ran linkgit:git-push[1] and pushed
'matching refs' explicitly (i.e. you used ':', or
specified a refspec that isn't your current branch) and
it resulted in a non-fast-forward error.
statusHints:: statusHints::
Directions on how to stage/unstage/add shown in the Directions on how to stage/unstage/add shown in the
output of linkgit:git-status[1] and the template shown output of linkgit:git-status[1] and the template shown

View File

@ -13,7 +13,7 @@ SYNOPSIS
[--3way] [--interactive] [--committer-date-is-author-date] [--3way] [--interactive] [--committer-date-is-author-date]
[--ignore-date] [--ignore-space-change | --ignore-whitespace] [--ignore-date] [--ignore-space-change | --ignore-whitespace]
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
[--exclude=<path>] [--reject] [-q | --quiet] [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
[--scissors | --no-scissors] [--scissors | --no-scissors]
[(<mbox> | <Maildir>)...] [(<mbox> | <Maildir>)...]
'git am' (--continue | --skip | --abort) 'git am' (--continue | --skip | --abort)
@ -92,6 +92,7 @@ default. You can use `--no-utf8` to override this.
-p<n>:: -p<n>::
--directory=<dir>:: --directory=<dir>::
--exclude=<path>:: --exclude=<path>::
--include=<path>::
--reject:: --reject::
These flags are passed to the 'git apply' (see linkgit:git-apply[1]) These flags are passed to the 'git apply' (see linkgit:git-apply[1])
program that applies program that applies

View File

@ -126,6 +126,11 @@ OPTIONS
relationship to upstream branch (if any). If given twice, print relationship to upstream branch (if any). If given twice, print
the name of the upstream branch, as well. the name of the upstream branch, as well.
-q::
--quiet::
Be more quiet when creating or deleting a branch, suppressing
non-error messages.
--abbrev=<length>:: --abbrev=<length>::
Alter the sha1's minimum display length in the output listing. Alter the sha1's minimum display length in the output listing.
The default value is 7 and can be overridden by the `core.abbrev` The default value is 7 and can be overridden by the `core.abbrev`

View File

@ -132,11 +132,14 @@ OPTIONS
-t <file>:: -t <file>::
--template=<file>:: --template=<file>::
Use the contents of the given file as the initial version When editing the commit message, start the editor with the
of the commit message. The editor is invoked and you can contents in the given file. The `commit.template` configuration
make subsequent changes. If a message is specified using variable is often used to give this option implicitly to the
the `-m` or `-F` options, this option has no effect. This command. This mechanism can be used by projects that want to
overrides the `commit.template` configuration variable. guide participants with some hints on what to write in the message
in what order. If the user exits the editor without editing the
message, the commit is aborted. This has no effect when a message
is given by other means, e.g. with the `-m` or `-F` options.
-s:: -s::
--signoff:: --signoff::

View File

@ -98,9 +98,10 @@ OPTIONS
options. options.
--cat-blob-fd=<fd>:: --cat-blob-fd=<fd>::
Specify the file descriptor that will be written to Write responses to `cat-blob` and `ls` queries to the
when the `cat-blob` command is encountered in the stream. file descriptor <fd> instead of `stdout`. Allows `progress`
The default behaviour is to write to `stdout`. output intended for the end-user to be separated from other
output.
--done:: --done::
Require a `done` command at the end of the stream. Require a `done` command at the end of the stream.
@ -942,6 +943,9 @@ This command can be used anywhere in the stream that comments are
accepted. In particular, the `cat-blob` command can be used in the accepted. In particular, the `cat-blob` command can be used in the
middle of a commit but not in the middle of a `data` command. middle of a commit but not in the middle of a `data` command.
See ``Responses To Commands'' below for details about how to read
this output safely.
`ls` `ls`
~~~~ ~~~~
Prints information about the object at a path to a file descriptor Prints information about the object at a path to a file descriptor
@ -991,6 +995,9 @@ instead report
missing SP <path> LF missing SP <path> LF
==== ====
See ``Responses To Commands'' below for details about how to read
this output safely.
`feature` `feature`
~~~~~~~~~ ~~~~~~~~~
Require that fast-import supports the specified feature, or abort if Require that fast-import supports the specified feature, or abort if
@ -1079,6 +1086,35 @@ If the `--done` command line option or `feature done` command is
in use, the `done` command is mandatory and marks the end of the in use, the `done` command is mandatory and marks the end of the
stream. stream.
Responses To Commands
---------------------
New objects written by fast-import are not available immediately.
Most fast-import commands have no visible effect until the next
checkpoint (or completion). The frontend can send commands to
fill fast-import's input pipe without worrying about how quickly
they will take effect, which improves performance by simplifying
scheduling.
For some frontends, though, it is useful to be able to read back
data from the current repository as it is being updated (for
example when the source material describes objects in terms of
patches to be applied to previously imported objects). This can
be accomplished by connecting the frontend and fast-import via
bidirectional pipes:
====
mkfifo fast-import-output
frontend <fast-import-output |
git fast-import >fast-import-output
====
A frontend set up this way can use `progress`, `ls`, and `cat-blob`
commands to read information from the import in progress.
To avoid deadlock, such frontends must completely consume any
pending output from `progress`, `ls`, and `cat-blob` before
performing writes to fast-import that might block.
Crash Reports Crash Reports
------------- -------------
If fast-import is supplied invalid input it will terminate with a If fast-import is supplied invalid input it will terminate with a

View File

@ -32,6 +32,16 @@ OPTIONS
--all:: --all::
Fetch all remote refs. Fetch all remote refs.
--stdin::
Take the list of refs from stdin, one per line. If there
are refs specified on the command line in addition to this
option, then the refs from stdin are processed after those
on the command line.
+
If '--stateless-rpc' is specified together with this option then
the list of refs must be in packet format (pkt-line). Each ref must
be in a separate packet, and the list must end with a flush packet.
-q:: -q::
--quiet:: --quiet::
Pass '-q' flag to 'git unpack-objects'; this makes the Pass '-q' flag to 'git unpack-objects'; this makes the

View File

@ -31,13 +31,6 @@ the updated p4 remote branch.
EXAMPLE EXAMPLE
------- -------
* Create an alias for 'git p4', using the full path to the 'git-p4'
script if needed:
+
------------
$ git config --global alias.p4 '!git-p4'
------------
* Clone a repository: * Clone a repository:
+ +
------------ ------------
@ -311,19 +304,19 @@ configuration file. This allows future 'git p4 submit' commands to
work properly; the submit command looks only at the variable and does work properly; the submit command looks only at the variable and does
not have a command-line option. not have a command-line option.
The full syntax for a p4 view is documented in 'p4 help views'. Git-p4 The full syntax for a p4 view is documented in 'p4 help views'. 'Git p4'
knows only a subset of the view syntax. It understands multi-line knows only a subset of the view syntax. It understands multi-line
mappings, overlays with '+', exclusions with '-' and double-quotes mappings, overlays with '+', exclusions with '-' and double-quotes
around whitespace. Of the possible wildcards, git-p4 only handles around whitespace. Of the possible wildcards, 'git p4' only handles
'...', and only when it is at the end of the path. Git-p4 will complain '...', and only when it is at the end of the path. 'Git p4' will complain
if it encounters an unhandled wildcard. if it encounters an unhandled wildcard.
Bugs in the implementation of overlap mappings exist. If multiple depot Bugs in the implementation of overlap mappings exist. If multiple depot
paths map through overlays to the same location in the repository, paths map through overlays to the same location in the repository,
git-p4 can choose the wrong one. This is hard to solve without 'git p4' can choose the wrong one. This is hard to solve without
dedicating a client spec just for git-p4. dedicating a client spec just for 'git p4'.
The name of the client can be given to git-p4 in multiple ways. The The name of the client can be given to 'git p4' in multiple ways. The
variable 'git-p4.client' takes precedence if it exists. Otherwise, variable 'git-p4.client' takes precedence if it exists. Otherwise,
normal p4 mechanisms of determining the client are used: environment normal p4 mechanisms of determining the client are used: environment
variable P4CLIENT, a file referenced by P4CONFIG, or the local host name. variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.

View File

@ -170,10 +170,16 @@ useful if you write an alias or script around 'git push'.
is specified. This flag forces progress status even if the is specified. This flag forces progress status even if the
standard error stream is not directed to a terminal. standard error stream is not directed to a terminal.
--recurse-submodules=check:: --recurse-submodules=check|on-demand::
Check whether all submodule commits used by the revisions to be Make sure all submodule commits used by the revisions to be
pushed are available on a remote tracking branch. Otherwise the pushed are available on a remote tracking branch. If 'check' is
push will be aborted and the command will exit with non-zero status. used git will verify that all submodule commits that changed in
the revisions to be pushed are available on at least one remote
of the submodule. If any commits are missing the push will be
aborted and exit with non-zero status. If 'on-demand' is used
all submodules that changed in the revisions to be pushed will
be pushed. If on-demand was not able to push all necessary
revisions it will also be aborted and exit with non-zero status.
include::urls-remotes.txt[] include::urls-remotes.txt[]

View File

@ -43,13 +43,21 @@ GIT_EDITOR::
`$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe" `$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
--nofork`. The order of preference is the `$GIT_EDITOR` --nofork`. The order of preference is the `$GIT_EDITOR`
environment variable, then `core.editor` configuration, then environment variable, then `core.editor` configuration, then
`$VISUAL`, then `$EDITOR`, and then finally 'vi'. `$VISUAL`, then `$EDITOR`, and then the default chosen at compile
time, which is usually 'vi'.
ifdef::git-default-editor[]
The build you are using chose '{git-default-editor}' as the default.
endif::git-default-editor[]
GIT_PAGER:: GIT_PAGER::
Text viewer for use by git commands (e.g., 'less'). The value Text viewer for use by git commands (e.g., 'less'). The value
is meant to be interpreted by the shell. The order of preference is meant to be interpreted by the shell. The order of preference
is the `$GIT_PAGER` environment variable, then `core.pager` is the `$GIT_PAGER` environment variable, then `core.pager`
configuration, then `$PAGER`, and then finally 'less'. configuration, then `$PAGER`, and then the default chosen at
compile time (usually 'less').
ifdef::git-default-pager[]
The build you are using chose '{git-default-pager}' as the default.
endif::git-default-pager[]
Diagnostics Diagnostics
----------- -----------

View File

@ -49,9 +49,10 @@ Documentation for older releases are available here:
* release notes for * release notes for
link:RelNotes/1.7.10.txt[1.7.10]. link:RelNotes/1.7.10.txt[1.7.10].
* link:v1.7.9.6/git.html[documentation for release 1.7.9.6] * link:v1.7.9.7/git.html[documentation for release 1.7.9.7]
* release notes for * release notes for
link:RelNotes/1.7.9.7.txt[1.7.9.7],
link:RelNotes/1.7.9.6.txt[1.7.9.6], link:RelNotes/1.7.9.6.txt[1.7.9.6],
link:RelNotes/1.7.9.5.txt[1.7.9.5], link:RelNotes/1.7.9.5.txt[1.7.9.5],
link:RelNotes/1.7.9.4.txt[1.7.9.4], link:RelNotes/1.7.9.4.txt[1.7.9.4],
@ -60,9 +61,10 @@ Documentation for older releases are available here:
link:RelNotes/1.7.9.1.txt[1.7.9.1], link:RelNotes/1.7.9.1.txt[1.7.9.1],
link:RelNotes/1.7.9.txt[1.7.9]. link:RelNotes/1.7.9.txt[1.7.9].
* link:v1.7.8.5/git.html[documentation for release 1.7.8.5] * link:v1.7.8.6/git.html[documentation for release 1.7.8.6]
* release notes for * release notes for
link:RelNotes/1.7.8.6.txt[1.7.8.6],
link:RelNotes/1.7.8.5.txt[1.7.8.5], link:RelNotes/1.7.8.5.txt[1.7.8.5],
link:RelNotes/1.7.8.4.txt[1.7.8.4], link:RelNotes/1.7.8.4.txt[1.7.8.4],
link:RelNotes/1.7.8.3.txt[1.7.8.3], link:RelNotes/1.7.8.3.txt[1.7.8.3],
@ -70,9 +72,10 @@ Documentation for older releases are available here:
link:RelNotes/1.7.8.1.txt[1.7.8.1], link:RelNotes/1.7.8.1.txt[1.7.8.1],
link:RelNotes/1.7.8.txt[1.7.8]. link:RelNotes/1.7.8.txt[1.7.8].
* link:v1.7.7.6/git.html[documentation for release 1.7.7.6] * link:v1.7.7.7/git.html[documentation for release 1.7.7.7]
* release notes for * release notes for
link:RelNotes/1.7.7.7.txt[1.7.7.7],
link:RelNotes/1.7.7.6.txt[1.7.7.6], link:RelNotes/1.7.7.6.txt[1.7.7.6],
link:RelNotes/1.7.7.5.txt[1.7.7.5], link:RelNotes/1.7.7.5.txt[1.7.7.5],
link:RelNotes/1.7.7.4.txt[1.7.7.4], link:RelNotes/1.7.7.4.txt[1.7.7.4],

View File

@ -56,6 +56,11 @@ function.
returning a `struct commit *` each time you call it. The end of the returning a `struct commit *` each time you call it. The end of the
revision list is indicated by returning a NULL pointer. revision list is indicated by returning a NULL pointer.
`reset_revision_walk`::
Reset the flags used by the revision walking api. You can use
this to do multiple sequencial revision walks.
Data structures Data structures
--------------- ---------------

View File

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

View File

@ -131,6 +131,9 @@ Issues of note:
use English. Under autoconf the configure script will do this use English. Under autoconf the configure script will do this
automatically if it can't find libintl on the system. automatically if it can't find libintl on the system.
- Python version 2.6 or later is needed to use the git-p4
interface to Perforce.
- Some platform specific issues are dealt with Makefile rules, - Some platform specific issues are dealt with Makefile rules,
but depending on your specific installation, you may not but depending on your specific installation, you may not
have all the libraries/tools needed, or you may have have all the libraries/tools needed, or you may have

View File

@ -440,6 +440,7 @@ SCRIPT_PERL += git-send-email.perl
SCRIPT_PERL += git-svn.perl SCRIPT_PERL += git-svn.perl
SCRIPT_PYTHON += git-remote-testgit.py SCRIPT_PYTHON += git-remote-testgit.py
SCRIPT_PYTHON += git-p4.py
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@ -480,9 +481,11 @@ TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-index-version TEST_PROGRAMS_NEED_X += test-index-version
TEST_PROGRAMS_NEED_X += test-line-buffer TEST_PROGRAMS_NEED_X += test-line-buffer
TEST_PROGRAMS_NEED_X += test-match-trees TEST_PROGRAMS_NEED_X += test-match-trees
TEST_PROGRAMS_NEED_X += test-mergesort
TEST_PROGRAMS_NEED_X += test-mktemp TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils TEST_PROGRAMS_NEED_X += test-path-utils
TEST_PROGRAMS_NEED_X += test-revision-walking
TEST_PROGRAMS_NEED_X += test-run-command TEST_PROGRAMS_NEED_X += test-run-command
TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-sigchain
@ -590,6 +593,7 @@ LIB_H += log-tree.h
LIB_H += mailmap.h LIB_H += mailmap.h
LIB_H += merge-file.h LIB_H += merge-file.h
LIB_H += merge-recursive.h LIB_H += merge-recursive.h
LIB_H += mergesort.h
LIB_H += notes.h LIB_H += notes.h
LIB_H += notes-cache.h LIB_H += notes-cache.h
LIB_H += notes-merge.h LIB_H += notes-merge.h
@ -694,6 +698,7 @@ LIB_OBJS += mailmap.o
LIB_OBJS += match-trees.o LIB_OBJS += match-trees.o
LIB_OBJS += merge-file.o LIB_OBJS += merge-file.o
LIB_OBJS += merge-recursive.o LIB_OBJS += merge-recursive.o
LIB_OBJS += mergesort.o
LIB_OBJS += name-hash.o LIB_OBJS += name-hash.o
LIB_OBJS += notes.o LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o LIB_OBJS += notes-cache.o
@ -1849,6 +1854,13 @@ DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)' BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
endif endif
ifdef SHELL_PATH
SHELL_PATH_CQ = "$(subst ",\",$(subst \,\\,$(SHELL_PATH)))"
SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
endif
ALL_CFLAGS += $(BASIC_CFLAGS) ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS)
@ -2258,6 +2270,8 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
$(VCSSVN_LIB): $(VCSSVN_OBJS) $(VCSSVN_LIB): $(VCSSVN_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
export DEFAULT_EDITOR DEFAULT_PAGER
doc: doc:
$(MAKE) -C Documentation all $(MAKE) -C Documentation all

View File

@ -1 +1 @@
Documentation/RelNotes/1.7.10.txt Documentation/RelNotes/1.7.11.txt

View File

@ -1,6 +1,9 @@
#include "cache.h" #include "cache.h"
int advice_push_nonfastforward = 1; int advice_push_nonfastforward = 1;
int advice_push_non_ff_current = 1;
int advice_push_non_ff_default = 1;
int advice_push_non_ff_matching = 1;
int advice_status_hints = 1; int advice_status_hints = 1;
int advice_commit_before_merge = 1; int advice_commit_before_merge = 1;
int advice_resolve_conflict = 1; int advice_resolve_conflict = 1;
@ -12,6 +15,9 @@ static struct {
int *preference; int *preference;
} advice_config[] = { } advice_config[] = {
{ "pushnonfastforward", &advice_push_nonfastforward }, { "pushnonfastforward", &advice_push_nonfastforward },
{ "pushnonffcurrent", &advice_push_non_ff_current },
{ "pushnonffdefault", &advice_push_non_ff_default },
{ "pushnonffmatching", &advice_push_non_ff_matching },
{ "statushints", &advice_status_hints }, { "statushints", &advice_status_hints },
{ "commitbeforemerge", &advice_commit_before_merge }, { "commitbeforemerge", &advice_commit_before_merge },
{ "resolveconflict", &advice_resolve_conflict }, { "resolveconflict", &advice_resolve_conflict },

View File

@ -4,6 +4,9 @@
#include "git-compat-util.h" #include "git-compat-util.h"
extern int advice_push_nonfastforward; extern int advice_push_nonfastforward;
extern int advice_push_non_ff_current;
extern int advice_push_non_ff_default;
extern int advice_push_non_ff_matching;
extern int advice_status_hints; extern int advice_status_hints;
extern int advice_commit_before_merge; extern int advice_commit_before_merge;
extern int advice_resolve_conflict; extern int advice_resolve_conflict;

View File

@ -101,9 +101,10 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
* config. * config.
*/ */
static int setup_tracking(const char *new_ref, const char *orig_ref, static int setup_tracking(const char *new_ref, const char *orig_ref,
enum branch_track track) enum branch_track track, int quiet)
{ {
struct tracking tracking; struct tracking tracking;
int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
if (strlen(new_ref) > 1024 - 7 - 7 - 1) if (strlen(new_ref) > 1024 - 7 - 7 - 1)
return error("Tracking not set up: name too long: %s", return error("Tracking not set up: name too long: %s",
@ -128,7 +129,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
return error("Not tracking: ambiguous information for ref %s", return error("Not tracking: ambiguous information for ref %s",
orig_ref); orig_ref);
install_branch_config(BRANCH_CONFIG_VERBOSE, new_ref, tracking.remote, install_branch_config(config_flags, new_ref, tracking.remote,
tracking.src ? tracking.src : orig_ref); tracking.src ? tracking.src : orig_ref);
free(tracking.src); free(tracking.src);
@ -191,7 +192,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
void create_branch(const char *head, void create_branch(const char *head,
const char *name, const char *start_name, const char *name, const char *start_name,
int force, int reflog, int clobber_head, int force, int reflog, int clobber_head,
enum branch_track track) int quiet, enum branch_track track)
{ {
struct ref_lock *lock = NULL; struct ref_lock *lock = NULL;
struct commit *commit; struct commit *commit;
@ -260,7 +261,7 @@ void create_branch(const char *head,
start_name); start_name);
if (real_ref && track) if (real_ref && track)
setup_tracking(ref.buf+11, real_ref, track); setup_tracking(ref.buf+11, real_ref, track, quiet);
if (!dont_change_ref) if (!dont_change_ref)
if (write_ref_sha1(lock, sha1, msg) < 0) if (write_ref_sha1(lock, sha1, msg) < 0)

View File

@ -14,7 +14,7 @@
*/ */
void create_branch(const char *head, const char *name, const char *start_name, void create_branch(const char *head, const char *name, const char *start_name,
int force, int reflog, int force, int reflog,
int clobber_head, enum branch_track track); int clobber_head, int quiet, enum branch_track track);
/* /*
* Validates that the requested branch may be created, returning the * Validates that the requested branch may be created, returning the

View File

@ -152,9 +152,14 @@ struct fragment {
unsigned long leading, trailing; unsigned long leading, trailing;
unsigned long oldpos, oldlines; unsigned long oldpos, oldlines;
unsigned long newpos, newlines; unsigned long newpos, newlines;
/*
* 'patch' is usually borrowed from buf in apply_patch(),
* but some codepaths store an allocated buffer.
*/
const char *patch; const char *patch;
unsigned free_patch:1,
rejected:1;
int size; int size;
int rejected;
int linenr; int linenr;
struct fragment *next; struct fragment *next;
}; };
@ -196,6 +201,36 @@ struct patch {
struct patch *next; struct patch *next;
}; };
static void free_fragment_list(struct fragment *list)
{
while (list) {
struct fragment *next = list->next;
if (list->free_patch)
free((char *)list->patch);
free(list);
list = next;
}
}
static void free_patch(struct patch *patch)
{
free_fragment_list(patch->fragments);
free(patch->def_name);
free(patch->old_name);
free(patch->new_name);
free(patch->result);
free(patch);
}
static void free_patch_list(struct patch *list)
{
while (list) {
struct patch *next = list->next;
free_patch(list);
list = next;
}
}
/* /*
* A line in a file, len-bytes long (includes the terminating LF, * A line in a file, len-bytes long (includes the terminating LF,
* except for an incomplete line at the end if the file ends with * except for an incomplete line at the end if the file ends with
@ -302,6 +337,11 @@ static void add_line_info(struct image *img, const char *bol, size_t len, unsign
img->nr++; img->nr++;
} }
/*
* "buf" has the file contents to be patched (read from various sources).
* attach it to "image" and add line-based index to it.
* "image" now owns the "buf".
*/
static void prepare_image(struct image *image, char *buf, size_t len, static void prepare_image(struct image *image, char *buf, size_t len,
int prepare_linetable) int prepare_linetable)
{ {
@ -353,7 +393,6 @@ static void say_patch_name(FILE *output, const char *pre,
fputs(post, output); fputs(post, output);
} }
#define CHUNKSIZE (8192)
#define SLOP (16) #define SLOP (16)
static void read_patch_file(struct strbuf *sb, int fd) static void read_patch_file(struct strbuf *sb, int fd)
@ -416,7 +455,7 @@ static char *squash_slash(char *name)
return name; return name;
} }
static char *find_name_gnu(const char *line, char *def, int p_value) static char *find_name_gnu(const char *line, const char *def, int p_value)
{ {
struct strbuf name = STRBUF_INIT; struct strbuf name = STRBUF_INIT;
char *cp; char *cp;
@ -439,11 +478,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value)
cp++; cp++;
} }
/* name can later be freed, so we need
* to memmove, not just return cp
*/
strbuf_remove(&name, 0, cp - name.buf); strbuf_remove(&name, 0, cp - name.buf);
free(def);
if (root) if (root)
strbuf_insert(&name, 0, root, root_len); strbuf_insert(&name, 0, root, root_len);
return squash_slash(strbuf_detach(&name, NULL)); return squash_slash(strbuf_detach(&name, NULL));
@ -608,8 +643,13 @@ static size_t diff_timestamp_len(const char *line, size_t len)
return line + len - end; return line + len - end;
} }
static char *find_name_common(const char *line, char *def, int p_value, static char *null_strdup(const char *s)
const char *end, int terminate) {
return s ? xstrdup(s) : NULL;
}
static char *find_name_common(const char *line, const char *def,
int p_value, const char *end, int terminate)
{ {
int len; int len;
const char *start = NULL; const char *start = NULL;
@ -630,10 +670,10 @@ static char *find_name_common(const char *line, char *def, int p_value,
start = line; start = line;
} }
if (!start) if (!start)
return squash_slash(def); return squash_slash(null_strdup(def));
len = line - start; len = line - start;
if (!len) if (!len)
return squash_slash(def); return squash_slash(null_strdup(def));
/* /*
* Generally we prefer the shorter name, especially * Generally we prefer the shorter name, especially
@ -644,8 +684,7 @@ static char *find_name_common(const char *line, char *def, int p_value,
if (def) { if (def) {
int deflen = strlen(def); int deflen = strlen(def);
if (deflen < len && !strncmp(start, def, deflen)) if (deflen < len && !strncmp(start, def, deflen))
return squash_slash(def); return squash_slash(xstrdup(def));
free(def);
} }
if (root) { if (root) {
@ -842,8 +881,10 @@ static void parse_traditional_patch(const char *first, const char *second, struc
name = find_name_traditional(first, NULL, p_value); name = find_name_traditional(first, NULL, p_value);
patch->old_name = name; patch->old_name = name;
} else { } else {
name = find_name_traditional(first, NULL, p_value); char *first_name;
name = find_name_traditional(second, name, p_value); first_name = find_name_traditional(first, NULL, p_value);
name = find_name_traditional(second, first_name, p_value);
free(first_name);
if (has_epoch_timestamp(first)) { if (has_epoch_timestamp(first)) {
patch->is_new = 1; patch->is_new = 1;
patch->is_delete = 0; patch->is_delete = 0;
@ -853,7 +894,8 @@ static void parse_traditional_patch(const char *first, const char *second, struc
patch->is_delete = 1; patch->is_delete = 1;
patch->old_name = name; patch->old_name = name;
} else { } else {
patch->old_name = patch->new_name = name; patch->old_name = name;
patch->new_name = xstrdup(name);
} }
} }
if (!name) if (!name)
@ -903,13 +945,19 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
static int gitdiff_oldname(const char *line, struct patch *patch) static int gitdiff_oldname(const char *line, struct patch *patch)
{ {
char *orig = patch->old_name;
patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old"); patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
if (orig != patch->old_name)
free(orig);
return 0; return 0;
} }
static int gitdiff_newname(const char *line, struct patch *patch) static int gitdiff_newname(const char *line, struct patch *patch)
{ {
char *orig = patch->new_name;
patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new"); patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
if (orig != patch->new_name)
free(orig);
return 0; return 0;
} }
@ -928,20 +976,23 @@ static int gitdiff_newmode(const char *line, struct patch *patch)
static int gitdiff_delete(const char *line, struct patch *patch) static int gitdiff_delete(const char *line, struct patch *patch)
{ {
patch->is_delete = 1; patch->is_delete = 1;
patch->old_name = patch->def_name; free(patch->old_name);
patch->old_name = null_strdup(patch->def_name);
return gitdiff_oldmode(line, patch); return gitdiff_oldmode(line, patch);
} }
static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_newfile(const char *line, struct patch *patch)
{ {
patch->is_new = 1; patch->is_new = 1;
patch->new_name = patch->def_name; free(patch->new_name);
patch->new_name = null_strdup(patch->def_name);
return gitdiff_newmode(line, patch); return gitdiff_newmode(line, patch);
} }
static int gitdiff_copysrc(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch)
{ {
patch->is_copy = 1; patch->is_copy = 1;
free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0; return 0;
} }
@ -949,6 +1000,7 @@ static int gitdiff_copysrc(const char *line, struct patch *patch)
static int gitdiff_copydst(const char *line, struct patch *patch) static int gitdiff_copydst(const char *line, struct patch *patch)
{ {
patch->is_copy = 1; patch->is_copy = 1;
free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0; return 0;
} }
@ -956,6 +1008,7 @@ static int gitdiff_copydst(const char *line, struct patch *patch)
static int gitdiff_renamesrc(const char *line, struct patch *patch) static int gitdiff_renamesrc(const char *line, struct patch *patch)
{ {
patch->is_rename = 1; patch->is_rename = 1;
free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0; return 0;
} }
@ -963,6 +1016,7 @@ static int gitdiff_renamesrc(const char *line, struct patch *patch)
static int gitdiff_renamedst(const char *line, struct patch *patch) static int gitdiff_renamedst(const char *line, struct patch *patch)
{ {
patch->is_rename = 1; patch->is_rename = 1;
free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0; return 0;
} }
@ -1044,7 +1098,7 @@ static const char *stop_at_slash(const char *line, int llen)
* creation or deletion of an empty file. In any of these cases, * creation or deletion of an empty file. In any of these cases,
* both sides are the same name under a/ and b/ respectively. * both sides are the same name under a/ and b/ respectively.
*/ */
static char *git_header_name(char *line, int llen) static char *git_header_name(const char *line, int llen)
{ {
const char *name; const char *name;
const char *second = NULL; const char *second = NULL;
@ -1171,7 +1225,7 @@ static char *git_header_name(char *line, int llen)
} }
/* Verify that we recognize the lines following a git header */ /* Verify that we recognize the lines following a git header */
static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch) static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
{ {
unsigned long offset; unsigned long offset;
@ -1287,7 +1341,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect
return offset + ex; return offset + ex;
} }
static void recount_diff(char *line, int size, struct fragment *fragment) static void recount_diff(const char *line, int size, struct fragment *fragment)
{ {
int oldlines = 0, newlines = 0, ret = 0; int oldlines = 0, newlines = 0, ret = 0;
@ -1341,7 +1395,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment)
* Parse a unified diff fragment header of the * Parse a unified diff fragment header of the
* form "@@ -a,b +c,d @@" * form "@@ -a,b +c,d @@"
*/ */
static int parse_fragment_header(char *line, int len, struct fragment *fragment) static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
{ {
int offset; int offset;
@ -1355,7 +1409,7 @@ static int parse_fragment_header(char *line, int len, struct fragment *fragment)
return offset; return offset;
} }
static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch) static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
{ {
unsigned long offset, len; unsigned long offset, len;
@ -1403,7 +1457,8 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
if (!patch->def_name) if (!patch->def_name)
die("git diff header lacks filename information when removing " die("git diff header lacks filename information when removing "
"%d leading pathname components (line %d)" , p_value, linenr); "%d leading pathname components (line %d)" , p_value, linenr);
patch->old_name = patch->new_name = patch->def_name; patch->old_name = xstrdup(patch->def_name);
patch->new_name = xstrdup(patch->def_name);
} }
if (!patch->is_delete && !patch->new_name) if (!patch->is_delete && !patch->new_name)
die("git diff header lacks filename information " die("git diff header lacks filename information "
@ -1466,7 +1521,7 @@ static void check_whitespace(const char *line, int len, unsigned ws_rule)
* between a "---" that is part of a patch, and a "---" that starts * between a "---" that is part of a patch, and a "---" that starts
* the next patch is to look at the line counts.. * the next patch is to look at the line counts..
*/ */
static int parse_fragment(char *line, unsigned long size, static int parse_fragment(const char *line, unsigned long size,
struct patch *patch, struct fragment *fragment) struct patch *patch, struct fragment *fragment)
{ {
int added, deleted; int added, deleted;
@ -1562,7 +1617,15 @@ static int parse_fragment(char *line, unsigned long size,
return offset; return offset;
} }
static int parse_single_patch(char *line, unsigned long size, struct patch *patch) /*
* We have seen "diff --git a/... b/..." header (or a traditional patch
* header). Read hunks that belong to this patch into fragments and hang
* them to the given patch structure.
*
* The (fragment->patch, fragment->size) pair points into the memory given
* by the caller, not a copy, when we return.
*/
static int parse_single_patch(const char *line, unsigned long size, struct patch *patch)
{ {
unsigned long offset = 0; unsigned long offset = 0;
unsigned long oldlines = 0, newlines = 0, context = 0; unsigned long oldlines = 0, newlines = 0, context = 0;
@ -1655,6 +1718,11 @@ static char *inflate_it(const void *data, unsigned long size,
return out; return out;
} }
/*
* Read a binary hunk and return a new fragment; fragment->patch
* points at an allocated memory that the caller must free, so
* it is marked as "->free_patch = 1".
*/
static struct fragment *parse_binary_hunk(char **buf_p, static struct fragment *parse_binary_hunk(char **buf_p,
unsigned long *sz_p, unsigned long *sz_p,
int *status_p, int *status_p,
@ -1742,6 +1810,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
frag = xcalloc(1, sizeof(*frag)); frag = xcalloc(1, sizeof(*frag));
frag->patch = inflate_it(data, hunk_size, origlen); frag->patch = inflate_it(data, hunk_size, origlen);
frag->free_patch = 1;
if (!frag->patch) if (!frag->patch)
goto corrupt; goto corrupt;
free(data); free(data);
@ -1807,6 +1876,13 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
return used; return used;
} }
/*
* Read the patch text in "buffer" taht extends for "size" bytes; stop
* reading after seeing a single patch (i.e. changes to a single file).
* Create fragments (i.e. patch hunks) and hang them to the given patch.
* Return the number of bytes consumed, so that the caller can call us
* again for the next patch.
*/
static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
{ {
int hdrsize, patchsize; int hdrsize, patchsize;
@ -2367,6 +2443,11 @@ static void remove_last_line(struct image *img)
img->len -= img->line[--img->nr].len; img->len -= img->line[--img->nr].len;
} }
/*
* The change from "preimage" and "postimage" has been found to
* apply at applied_pos (counts in line numbers) in "img".
* Update "img" to remove "preimage" and replace it with "postimage".
*/
static void update_image(struct image *img, static void update_image(struct image *img,
int applied_pos, int applied_pos,
struct image *preimage, struct image *preimage,
@ -2438,6 +2519,11 @@ static void update_image(struct image *img,
img->nr = nr; img->nr = nr;
} }
/*
* Use the patch-hunk text in "frag" to prepare two images (preimage and
* postimage) for the hunk. Find lines that match "preimage" in "img" and
* replace the part of "img" with "postimage" text.
*/
static int apply_one_fragment(struct image *img, struct fragment *frag, static int apply_one_fragment(struct image *img, struct fragment *frag,
int inaccurate_eof, unsigned ws_rule, int inaccurate_eof, unsigned ws_rule,
int nth_fragment) int nth_fragment)
@ -2728,6 +2814,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
return -1; return -1;
} }
/*
* Replace "img" with the result of applying the binary patch.
* The binary patch data itself in patch->fragment is still kept
* but the preimage prepared by the caller in "img" is freed here
* or in the helper function apply_binary_fragment() this calls.
*/
static int apply_binary(struct image *img, struct patch *patch) static int apply_binary(struct image *img, struct patch *patch)
{ {
const char *name = patch->old_name ? patch->old_name : patch->new_name; const char *name = patch->old_name ? patch->old_name : patch->new_name;
@ -2935,7 +3027,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
return error("patch %s has been renamed/deleted", return error("patch %s has been renamed/deleted",
patch->old_name); patch->old_name);
} }
/* We have a patched copy in memory use that */ /* We have a patched copy in memory; use that. */
strbuf_add(&buf, tpatch->result, tpatch->resultsize); strbuf_add(&buf, tpatch->result, tpatch->resultsize);
} else if (cached) { } else if (cached) {
if (read_file_or_gitlink(ce, &buf)) if (read_file_or_gitlink(ce, &buf))
@ -2948,7 +3040,10 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
/* /*
* There is no way to apply subproject * There is no way to apply subproject
* patch without looking at the index. * patch without looking at the index.
* NEEDSWORK: shouldn't this be flagged
* as an error???
*/ */
free_fragment_list(patch->fragments);
patch->fragments = NULL; patch->fragments = NULL;
} }
} else { } else {
@ -3085,10 +3180,15 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
is_new: is_new:
patch->is_new = 1; patch->is_new = 1;
patch->is_delete = 0; patch->is_delete = 0;
free(patch->old_name);
patch->old_name = NULL; patch->old_name = NULL;
return 0; return 0;
} }
/*
* Check and apply the patch in-core; leave the result in patch->result
* for the caller to write it out to the final destination.
*/
static int check_patch(struct patch *patch) static int check_patch(struct patch *patch)
{ {
struct stat st; struct stat st;
@ -3665,15 +3765,8 @@ static void prefix_patches(struct patch *p)
if (!prefix || p->is_toplevel_relative) if (!prefix || p->is_toplevel_relative)
return; return;
for ( ; p; p = p->next) { for ( ; p; p = p->next) {
if (p->new_name == p->old_name) { prefix_one(&p->new_name);
char *prefixed = p->new_name; prefix_one(&p->old_name);
prefix_one(&prefixed);
p->new_name = p->old_name = prefixed;
}
else {
prefix_one(&p->new_name);
prefix_one(&p->old_name);
}
} }
} }
@ -3683,12 +3776,10 @@ static void prefix_patches(struct patch *p)
static int apply_patch(int fd, const char *filename, int options) static int apply_patch(int fd, const char *filename, int options)
{ {
size_t offset; size_t offset;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; /* owns the patch text */
struct patch *list = NULL, **listp = &list; struct patch *list = NULL, **listp = &list;
int skipped_patch = 0; int skipped_patch = 0;
/* FIXME - memory leak when using multiple patch files as inputs */
memset(&fn_table, 0, sizeof(struct string_list));
patch_input_file = filename; patch_input_file = filename;
read_patch_file(&buf, fd); read_patch_file(&buf, fd);
offset = 0; offset = 0;
@ -3712,8 +3803,7 @@ static int apply_patch(int fd, const char *filename, int options)
listp = &patch->next; listp = &patch->next;
} }
else { else {
/* perhaps free it a bit better? */ free_patch(patch);
free(patch);
skipped_patch++; skipped_patch++;
} }
offset += nr; offset += nr;
@ -3754,7 +3844,9 @@ static int apply_patch(int fd, const char *filename, int options)
if (summary) if (summary)
summary_patch_list(list); summary_patch_list(list);
free_patch_list(list);
strbuf_release(&buf); strbuf_release(&buf);
string_list_clear(&fn_table, 0);
return 0; return 0;
} }

View File

@ -2302,6 +2302,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR), OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL), OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE), OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
OPT_BIT(0, "minimal", &xdl_opts, "Spend extra cycles to find better match", XDF_NEED_MINIMAL),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"), OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"), OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },

View File

@ -146,7 +146,8 @@ static int branch_merged(int kind, const char *name,
return merged; return merged;
} }
static int delete_branches(int argc, const char **argv, int force, int kinds) static int delete_branches(int argc, const char **argv, int force, int kinds,
int quiet)
{ {
struct commit *rev, *head_rev = NULL; struct commit *rev, *head_rev = NULL;
unsigned char sha1[20]; unsigned char sha1[20];
@ -216,9 +217,10 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
ret = 1; ret = 1;
} else { } else {
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
printf(_("Deleted %sbranch %s (was %s).\n"), remote, if (!quiet)
bname.buf, printf(_("Deleted %sbranch %s (was %s).\n"),
find_unique_abbrev(sha1, DEFAULT_ABBREV)); remote, bname.buf,
find_unique_abbrev(sha1, DEFAULT_ABBREV));
strbuf_addf(&buf, "branch.%s", bname.buf); strbuf_addf(&buf, "branch.%s", bname.buf);
if (git_config_rename_section(buf.buf, NULL) < 0) if (git_config_rename_section(buf.buf, NULL) < 0)
warning(_("Update of config-file failed")); warning(_("Update of config-file failed"));
@ -678,6 +680,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
int delete = 0, rename = 0, force_create = 0, list = 0; int delete = 0, rename = 0, force_create = 0, list = 0;
int verbose = 0, abbrev = -1, detached = 0; int verbose = 0, abbrev = -1, detached = 0;
int reflog = 0, edit_description = 0; int reflog = 0, edit_description = 0;
int quiet = 0;
enum branch_track track; enum branch_track track;
int kinds = REF_LOCAL_BRANCH; int kinds = REF_LOCAL_BRANCH;
struct commit_list *with_commit = NULL; struct commit_list *with_commit = NULL;
@ -686,6 +689,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_GROUP("Generic options"), OPT_GROUP("Generic options"),
OPT__VERBOSE(&verbose, OPT__VERBOSE(&verbose,
"show hash and subject, give twice for upstream branch"), "show hash and subject, give twice for upstream branch"),
OPT__QUIET(&quiet, "suppress informational messages"),
OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
BRANCH_TRACK_EXPLICIT), BRANCH_TRACK_EXPLICIT),
OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
@ -766,7 +770,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
abbrev = DEFAULT_ABBREV; abbrev = DEFAULT_ABBREV;
if (delete) if (delete)
return delete_branches(argc, argv, delete > 1, kinds); return delete_branches(argc, argv, delete > 1, kinds, quiet);
else if (list) else if (list)
return print_ref_list(kinds, detached, verbose, abbrev, return print_ref_list(kinds, detached, verbose, abbrev,
with_commit, argv); with_commit, argv);
@ -808,7 +812,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (kinds != REF_LOCAL_BRANCH) if (kinds != REF_LOCAL_BRANCH)
die(_("-a and -r options to 'git branch' do not make sense with a branch name")); die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
create_branch(head, argv[0], (argc == 2) ? argv[1] : head, create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
force_create, reflog, 0, track); force_create, reflog, 0, quiet, track);
} else } else
usage_with_options(builtin_branch_usage, options); usage_with_options(builtin_branch_usage, options);

View File

@ -11,6 +11,7 @@
#include "parse-options.h" #include "parse-options.h"
#include "diff.h" #include "diff.h"
#include "userdiff.h" #include "userdiff.h"
#include "streaming.h"
#define BATCH 1 #define BATCH 1
#define BATCH_CHECK 2 #define BATCH_CHECK 2
@ -127,6 +128,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
return cmd_ls_tree(2, ls_args, NULL); return cmd_ls_tree(2, ls_args, NULL);
} }
if (type == OBJ_BLOB)
return stream_blob_to_fd(1, sha1, NULL, 0);
buf = read_sha1_file(sha1, &type, &size); buf = read_sha1_file(sha1, &type, &size);
if (!buf) if (!buf)
die("Cannot read object %s", obj_name); die("Cannot read object %s", obj_name);
@ -149,6 +152,28 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
break; break;
case 0: case 0:
if (type_from_string(exp_type) == OBJ_BLOB) {
unsigned char blob_sha1[20];
if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
enum object_type type;
unsigned long size;
char *buffer = read_sha1_file(sha1, &type, &size);
if (memcmp(buffer, "object ", 7) ||
get_sha1_hex(buffer + 7, blob_sha1))
die("%s not a valid tag", sha1_to_hex(sha1));
free(buffer);
} else
hashcpy(blob_sha1, sha1);
if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
return stream_blob_to_fd(1, blob_sha1, NULL, 0);
/*
* we attempted to dereference a tag to a blob
* and failed; there may be new dereference
* mechanisms this code is not aware of.
* fall-back to the usual case.
*/
}
buf = read_object_with_reference(sha1, exp_type, &size, NULL); buf = read_object_with_reference(sha1, exp_type, &size, NULL);
break; break;

View File

@ -543,6 +543,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
opts->new_branch_force ? 1 : 0, opts->new_branch_force ? 1 : 0,
opts->new_branch_log, opts->new_branch_log,
opts->new_branch_force ? 1 : 0, opts->new_branch_force ? 1 : 0,
opts->quiet,
opts->track); opts->track);
new->name = opts->new_branch; new->name = opts->new_branch;
setup_branch_path(new); setup_branch_path(new);

View File

@ -533,9 +533,20 @@ static int is_a_merge(const struct commit *current_head)
static const char sign_off_header[] = "Signed-off-by: "; static const char sign_off_header[] = "Signed-off-by: ";
static void export_one(const char *var, const char *s, const char *e, int hack)
{
struct strbuf buf = STRBUF_INIT;
if (hack)
strbuf_addch(&buf, hack);
strbuf_addf(&buf, "%.*s", (int)(e - s), s);
setenv(var, buf.buf, 1);
strbuf_release(&buf);
}
static void determine_author_info(struct strbuf *author_ident) static void determine_author_info(struct strbuf *author_ident)
{ {
char *name, *email, *date; char *name, *email, *date;
struct ident_split author;
name = getenv("GIT_AUTHOR_NAME"); name = getenv("GIT_AUTHOR_NAME");
email = getenv("GIT_AUTHOR_EMAIL"); email = getenv("GIT_AUTHOR_EMAIL");
@ -585,6 +596,11 @@ static void determine_author_info(struct strbuf *author_ident)
date = force_date; date = force_date;
strbuf_addstr(author_ident, fmt_ident(name, email, date, strbuf_addstr(author_ident, fmt_ident(name, email, date,
IDENT_ERROR_ON_NO_NAME)); IDENT_ERROR_ON_NO_NAME));
if (!split_ident_line(&author, author_ident->buf, author_ident->len)) {
export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
}
} }
static int ends_rfc2822_footer(struct strbuf *sb) static int ends_rfc2822_footer(struct strbuf *sb)
@ -652,6 +668,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
int ident_shown = 0; int ident_shown = 0;
int clean_message_contents = (cleanup_mode != CLEANUP_NONE); int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
if (!no_verify && run_hook(index_file, "pre-commit", NULL)) if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0; return 0;
@ -771,9 +790,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_release(&sb); strbuf_release(&sb);
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
/* This checks if committer ident is explicitly given */ /* This checks if committer ident is explicitly given */
strbuf_addstr(&committer_ident, git_committer_info(0)); strbuf_addstr(&committer_ident, git_committer_info(0));
if (use_editor && include_status) { if (use_editor && include_status) {
@ -905,27 +921,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
return 1; return 1;
} }
/* static int rest_is_empty(struct strbuf *sb, int start)
* Find out if the message in the strbuf contains only whitespace and
* Signed-off-by lines.
*/
static int message_is_empty(struct strbuf *sb)
{ {
struct strbuf tmpl = STRBUF_INIT; int i, eol;
const char *nl; const char *nl;
int eol, i, start = 0;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
/* See if the template is just a prefix of the message. */
if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
if (start + tmpl.len <= sb->len &&
memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
start += tmpl.len;
}
strbuf_release(&tmpl);
/* Check if the rest is just whitespace and Signed-of-by's. */ /* Check if the rest is just whitespace and Signed-of-by's. */
for (i = start; i < sb->len; i++) { for (i = start; i < sb->len; i++) {
@ -948,6 +947,40 @@ static int message_is_empty(struct strbuf *sb)
return 1; return 1;
} }
/*
* Find out if the message in the strbuf contains only whitespace and
* Signed-off-by lines.
*/
static int message_is_empty(struct strbuf *sb)
{
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
return rest_is_empty(sb, 0);
}
/*
* See if the user edited the message in the editor or left what
* was in the template intact
*/
static int template_untouched(struct strbuf *sb)
{
struct strbuf tmpl = STRBUF_INIT;
char *start;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
return 0;
stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
start = (char *)skip_prefix(sb->buf, tmpl.buf);
if (!start)
start = sb->buf;
strbuf_release(&tmpl);
return rest_is_empty(sb, start - sb->buf);
}
static const char *find_author_by_nickname(const char *name) static const char *find_author_by_nickname(const char *name)
{ {
struct rev_info revs; struct rev_info revs;
@ -1055,6 +1088,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
die(_("Only one of -c/-C/-F/--fixup can be used.")); die(_("Only one of -c/-C/-F/--fixup can be used."));
if (message.len && f > 0) if (message.len && f > 0)
die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
if (f || message.len)
template_file = NULL;
if (edit_message) if (edit_message)
use_message = edit_message; use_message = edit_message;
if (amend && !use_message && !fixup_message) if (amend && !use_message && !fixup_message)
@ -1494,6 +1529,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (cleanup_mode != CLEANUP_NONE) if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL); stripspace(&sb, cleanup_mode == CLEANUP_ALL);
if (template_untouched(&sb) && !allow_empty_message) {
rollback_index_files();
fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
exit(1);
}
if (message_is_empty(&sb) && !allow_empty_message) { if (message_is_empty(&sb) && !allow_empty_message) {
rollback_index_files(); rollback_index_files();
fprintf(stderr, _("Aborting commit due to empty commit message.\n")); fprintf(stderr, _("Aborting commit due to empty commit message.\n"));

View File

@ -327,7 +327,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
add_head_to_pending(&rev); add_head_to_pending(&rev);
if (!rev.pending.nr) { if (!rev.pending.nr) {
struct tree *tree; struct tree *tree;
tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN); tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
add_pending_object(&rev, &tree->object, "HEAD"); add_pending_object(&rev, &tree->object, "HEAD");
} }
break; break;

View File

@ -23,7 +23,9 @@ static struct fetch_pack_args args = {
}; };
static const char fetch_pack_usage[] = static const char fetch_pack_usage[] =
"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]"; "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
"[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
#define COMPLETE (1U << 0) #define COMPLETE (1U << 0)
#define COMMON (1U << 1) #define COMMON (1U << 1)
@ -942,6 +944,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
args.fetch_all = 1; args.fetch_all = 1;
continue; continue;
} }
if (!strcmp("--stdin", arg)) {
args.stdin_refs = 1;
continue;
}
if (!strcmp("-v", arg)) { if (!strcmp("-v", arg)) {
args.verbose = 1; args.verbose = 1;
continue; continue;
@ -973,6 +979,40 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
if (!dest) if (!dest)
usage(fetch_pack_usage); usage(fetch_pack_usage);
if (args.stdin_refs) {
/*
* Copy refs from cmdline to new growable list, then
* append the refs from the standard input.
*/
int alloc_heads = nr_heads;
int size = nr_heads * sizeof(*heads);
heads = memcpy(xmalloc(size), heads, size);
if (args.stateless_rpc) {
/* in stateless RPC mode we use pkt-line to read
* from stdin, until we get a flush packet
*/
static char line[1000];
for (;;) {
int n = packet_read_line(0, line, sizeof(line));
if (!n)
break;
if (line[n-1] == '\n')
n--;
ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
heads[nr_heads++] = xmemdupz(line, n);
}
}
else {
/* read from stdin one ref per line, until EOF */
struct strbuf line = STRBUF_INIT;
while (strbuf_getline(&line, stdin, '\n') != EOF) {
ALLOC_GROW(heads, nr_heads + 1, alloc_heads);
heads[nr_heads++] = strbuf_detach(&line, NULL);
}
strbuf_release(&line);
}
}
if (args.stateless_rpc) { if (args.stateless_rpc) {
conn = NULL; conn = NULL;
fd[0] = 0; fd[0] = 0;

View File

@ -240,6 +240,7 @@ static int s_update_ref(const char *action,
static int update_local_ref(struct ref *ref, static int update_local_ref(struct ref *ref,
const char *remote, const char *remote,
const struct ref *remote_ref,
struct strbuf *display) struct strbuf *display)
{ {
struct commit *current = NULL, *updated; struct commit *current = NULL, *updated;
@ -293,18 +294,26 @@ static int update_local_ref(struct ref *ref,
const char *msg; const char *msg;
const char *what; const char *what;
int r; int r;
if (!strncmp(ref->name, "refs/tags/", 10)) { /*
* Nicely describe the new ref we're fetching.
* Base this on the remote's ref name, as it's
* more likely to follow a standard layout.
*/
const char *name = remote_ref ? remote_ref->name : "";
if (!prefixcmp(name, "refs/tags/")) {
msg = "storing tag"; msg = "storing tag";
what = _("[new tag]"); what = _("[new tag]");
} } else if (!prefixcmp(name, "refs/heads/")) {
else {
msg = "storing head"; msg = "storing head";
what = _("[new branch]"); what = _("[new branch]");
if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && } else {
(recurse_submodules != RECURSE_SUBMODULES_ON)) msg = "storing ref";
check_for_new_submodule_commits(ref->new_sha1); what = _("[new ref]");
} }
if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
(recurse_submodules != RECURSE_SUBMODULES_ON))
check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref(msg, ref, 0); r = s_update_ref(msg, ref, 0);
strbuf_addf(display, "%c %-*s %-*s -> %s%s", strbuf_addf(display, "%c %-*s %-*s -> %s%s",
r ? '!' : '*', r ? '!' : '*',
@ -466,7 +475,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
strbuf_reset(&note); strbuf_reset(&note);
if (ref) { if (ref) {
rc |= update_local_ref(ref, what, &note); rc |= update_local_ref(ref, what, rm, &note);
free(ref); free(ref);
} else } else
strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD", strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",

View File

@ -27,6 +27,8 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
merge_log_config = DEFAULT_MERGE_LOG_LEN; merge_log_config = DEFAULT_MERGE_LOG_LEN;
} else if (!strcmp(key, "merge.branchdesc")) { } else if (!strcmp(key, "merge.branchdesc")) {
use_branch_desc = git_config_bool(key, value); use_branch_desc = git_config_bool(key, value);
} else {
return git_default_config(key, value, cb);
} }
return 0; return 0;
} }
@ -53,7 +55,48 @@ static void init_src_data(struct src_data *data)
static struct string_list srcs = STRING_LIST_INIT_DUP; static struct string_list srcs = STRING_LIST_INIT_DUP;
static struct string_list origins = STRING_LIST_INIT_DUP; static struct string_list origins = STRING_LIST_INIT_DUP;
static int handle_line(char *line) struct merge_parents {
int alloc, nr;
struct merge_parent {
unsigned char given[20];
unsigned char commit[20];
unsigned char used;
} *item;
};
/*
* I know, I know, this is inefficient, but you won't be pulling and merging
* hundreds of heads at a time anyway.
*/
static struct merge_parent *find_merge_parent(struct merge_parents *table,
unsigned char *given,
unsigned char *commit)
{
int i;
for (i = 0; i < table->nr; i++) {
if (given && hashcmp(table->item[i].given, given))
continue;
if (commit && hashcmp(table->item[i].commit, commit))
continue;
return &table->item[i];
}
return NULL;
}
static void add_merge_parent(struct merge_parents *table,
unsigned char *given,
unsigned char *commit)
{
if (table->nr && find_merge_parent(table, given, commit))
return;
ALLOC_GROW(table->item, table->nr + 1, table->alloc);
hashcpy(table->item[table->nr].given, given);
hashcpy(table->item[table->nr].commit, commit);
table->item[table->nr].used = 0;
table->nr++;
}
static int handle_line(char *line, struct merge_parents *merge_parents)
{ {
int i, len = strlen(line); int i, len = strlen(line);
struct origin_data *origin_data; struct origin_data *origin_data;
@ -61,6 +104,7 @@ static int handle_line(char *line)
struct src_data *src_data; struct src_data *src_data;
struct string_list_item *item; struct string_list_item *item;
int pulling_head = 0; int pulling_head = 0;
unsigned char sha1[20];
if (len < 43 || line[40] != '\t') if (len < 43 || line[40] != '\t')
return 1; return 1;
@ -71,14 +115,15 @@ static int handle_line(char *line)
if (line[41] != '\t') if (line[41] != '\t')
return 2; return 2;
line[40] = 0; i = get_sha1_hex(line, sha1);
origin_data = xcalloc(1, sizeof(struct origin_data)); if (i)
i = get_sha1(line, origin_data->sha1);
line[40] = '\t';
if (i) {
free(origin_data);
return 3; return 3;
}
if (!find_merge_parent(merge_parents, sha1, NULL))
return 0; /* subsumed by other parents */
origin_data = xcalloc(1, sizeof(struct origin_data));
hashcpy(origin_data->sha1, sha1);
if (line[len - 1] == '\n') if (line[len - 1] == '\n')
line[len - 1] = 0; line[len - 1] = 0;
@ -180,6 +225,101 @@ static void add_branch_desc(struct strbuf *out, const char *name)
strbuf_release(&desc); strbuf_release(&desc);
} }
#define util_as_integral(elem) ((intptr_t)((elem)->util))
static void record_person(int which, struct string_list *people,
struct commit *commit)
{
char name_buf[MAX_GITNAME], *name, *name_end;
struct string_list_item *elem;
const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
name = strstr(commit->buffer, field);
if (!name)
return;
name += strlen(field);
name_end = strchrnul(name, '<');
if (*name_end)
name_end--;
while (isspace(*name_end) && name <= name_end)
name_end--;
if (name_end < name || name + MAX_GITNAME <= name_end)
return;
memcpy(name_buf, name, name_end - name + 1);
name_buf[name_end - name + 1] = '\0';
elem = string_list_lookup(people, name_buf);
if (!elem) {
elem = string_list_insert(people, name_buf);
elem->util = (void *)0;
}
elem->util = (void*)(util_as_integral(elem) + 1);
}
static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
{
const struct string_list_item *a = a_, *b = b_;
return util_as_integral(b) - util_as_integral(a);
}
static void add_people_count(struct strbuf *out, struct string_list *people)
{
if (people->nr == 1)
strbuf_addf(out, "%s", people->items[0].string);
else if (people->nr == 2)
strbuf_addf(out, "%s (%d) and %s (%d)",
people->items[0].string,
(int)util_as_integral(&people->items[0]),
people->items[1].string,
(int)util_as_integral(&people->items[1]));
else if (people->nr)
strbuf_addf(out, "%s (%d) and others",
people->items[0].string,
(int)util_as_integral(&people->items[0]));
}
static void credit_people(struct strbuf *out,
struct string_list *them,
int kind)
{
const char *label;
const char *me;
if (kind == 'a') {
label = "\nBy ";
me = git_author_info(IDENT_NO_DATE);
} else {
label = "\nvia ";
me = git_committer_info(IDENT_NO_DATE);
}
if (!them->nr ||
(them->nr == 1 &&
me &&
(me = skip_prefix(me, them->items->string)) != NULL &&
skip_prefix(me, " <")))
return;
strbuf_addstr(out, label);
add_people_count(out, them);
}
static void add_people_info(struct strbuf *out,
struct string_list *authors,
struct string_list *committers)
{
if (authors->nr)
qsort(authors->items,
authors->nr, sizeof(authors->items[0]),
cmp_string_list_util_as_integral);
if (committers->nr)
qsort(committers->items,
committers->nr, sizeof(committers->items[0]),
cmp_string_list_util_as_integral);
credit_people(out, authors, 'a');
credit_people(out, committers, 'c');
}
static void shortlog(const char *name, static void shortlog(const char *name,
struct origin_data *origin_data, struct origin_data *origin_data,
struct commit *head, struct commit *head,
@ -190,6 +330,8 @@ static void shortlog(const char *name,
struct commit *commit; struct commit *commit;
struct object *branch; struct object *branch;
struct string_list subjects = STRING_LIST_INIT_DUP; struct string_list subjects = STRING_LIST_INIT_DUP;
struct string_list authors = STRING_LIST_INIT_DUP;
struct string_list committers = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
const unsigned char *sha1 = origin_data->sha1; const unsigned char *sha1 = origin_data->sha1;
@ -199,7 +341,6 @@ static void shortlog(const char *name,
return; return;
setup_revisions(0, NULL, rev, NULL); setup_revisions(0, NULL, rev, NULL);
rev->ignore_merges = 1;
add_pending_object(rev, branch, name); add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD"); add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING; head->object.flags |= UNINTERESTING;
@ -208,10 +349,15 @@ static void shortlog(const char *name,
while ((commit = get_revision(rev)) != NULL) { while ((commit = get_revision(rev)) != NULL) {
struct pretty_print_context ctx = {0}; struct pretty_print_context ctx = {0};
/* ignore merges */ if (commit->parents && commit->parents->next) {
if (commit->parents && commit->parents->next) /* do not list a merge but count committer */
record_person('c', &committers, commit);
continue; continue;
}
if (!count)
/* the 'tip' committer */
record_person('c', &committers, commit);
record_person('a', &authors, commit);
count++; count++;
if (subjects.nr > limit) if (subjects.nr > limit)
continue; continue;
@ -226,6 +372,7 @@ static void shortlog(const char *name,
string_list_append(&subjects, strbuf_detach(&sb, NULL)); string_list_append(&subjects, strbuf_detach(&sb, NULL));
} }
add_people_info(out, &authors, &committers);
if (count > limit) if (count > limit)
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else else
@ -246,6 +393,8 @@ static void shortlog(const char *name,
rev->commits = NULL; rev->commits = NULL;
rev->pending.nr = 0; rev->pending.nr = 0;
string_list_clear(&authors, 0);
string_list_clear(&committers, 0);
string_list_clear(&subjects, 0); string_list_clear(&subjects, 0);
} }
@ -366,6 +515,67 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
strbuf_release(&tagbuf); strbuf_release(&tagbuf);
} }
static void find_merge_parents(struct merge_parents *result,
struct strbuf *in, unsigned char *head)
{
struct commit_list *parents, *next;
struct commit *head_commit;
int pos = 0, i, j;
parents = NULL;
while (pos < in->len) {
int len;
char *p = in->buf + pos;
char *newline = strchr(p, '\n');
unsigned char sha1[20];
struct commit *parent;
struct object *obj;
len = newline ? newline - p : strlen(p);
pos += len + !!newline;
if (len < 43 ||
get_sha1_hex(p, sha1) ||
p[40] != '\t' ||
p[41] != '\t')
continue; /* skip not-for-merge */
/*
* Do not use get_merge_parent() here; we do not have
* "name" here and we do not want to contaminate its
* util field yet.
*/
obj = parse_object(sha1);
parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
if (!parent)
continue;
commit_list_insert(parent, &parents);
add_merge_parent(result, obj->sha1, parent->object.sha1);
}
head_commit = lookup_commit(head);
if (head_commit)
commit_list_insert(head_commit, &parents);
parents = reduce_heads(parents);
while (parents) {
for (i = 0; i < result->nr; i++)
if (!hashcmp(result->item[i].commit,
parents->item->object.sha1))
result->item[i].used = 1;
next = parents->next;
free(parents);
parents = next;
}
for (i = j = 0; i < result->nr; i++) {
if (result->item[i].used) {
if (i != j)
result->item[j] = result->item[i];
j++;
}
}
result->nr = j;
}
int fmt_merge_msg(struct strbuf *in, struct strbuf *out, int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
struct fmt_merge_msg_opts *opts) struct fmt_merge_msg_opts *opts)
{ {
@ -373,6 +583,9 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
unsigned char head_sha1[20]; unsigned char head_sha1[20];
const char *current_branch; const char *current_branch;
void *current_branch_to_free; void *current_branch_to_free;
struct merge_parents merge_parents;
memset(&merge_parents, 0, sizeof(merge_parents));
/* get current branch */ /* get current branch */
current_branch = current_branch_to_free = current_branch = current_branch_to_free =
@ -382,6 +595,8 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
if (!prefixcmp(current_branch, "refs/heads/")) if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11; current_branch += 11;
find_merge_parents(&merge_parents, in, head_sha1);
/* get a line */ /* get a line */
while (pos < in->len) { while (pos < in->len) {
int len; int len;
@ -392,7 +607,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
pos += len + !!newline; pos += len + !!newline;
i++; i++;
p[len] = 0; p[len] = 0;
if (handle_line(p)) if (handle_line(p, &merge_parents))
die ("Error in line %d: %.*s", i, len, p); die ("Error in line %d: %.*s", i, len, p);
} }
@ -423,6 +638,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
strbuf_complete_line(out); strbuf_complete_line(out);
free(current_branch_to_free); free(current_branch_to_free);
free(merge_parents.item);
return 0; return 0;
} }

View File

@ -12,6 +12,7 @@
#include "parse-options.h" #include "parse-options.h"
#include "dir.h" #include "dir.h"
#include "progress.h" #include "progress.h"
#include "streaming.h"
#define REACHABLE 0x0001 #define REACHABLE 0x0001
#define SEEN 0x0002 #define SEEN 0x0002
@ -238,13 +239,8 @@ static void check_unreachable_object(struct object *obj)
if (!(f = fopen(filename, "w"))) if (!(f = fopen(filename, "w")))
die_errno("Could not open '%s'", filename); die_errno("Could not open '%s'", filename);
if (obj->type == OBJ_BLOB) { if (obj->type == OBJ_BLOB) {
enum object_type type; if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1))
unsigned long size;
char *buf = read_sha1_file(obj->sha1,
&type, &size);
if (buf && fwrite(buf, 1, size, f) != size)
die_errno("Could not write '%s'", filename); die_errno("Could not write '%s'", filename);
free(buf);
} else } else
fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
if (fclose(f)) if (fclose(f))

View File

@ -20,6 +20,7 @@
#include "string-list.h" #include "string-list.h"
#include "parse-options.h" #include "parse-options.h"
#include "branch.h" #include "branch.h"
#include "streaming.h"
/* Set a default date-time format for git log ("log.date" config variable) */ /* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL; static const char *default_date_mode = NULL;
@ -383,8 +384,13 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
strbuf_release(&out); strbuf_release(&out);
} }
static int show_object(const unsigned char *sha1, int show_tag_object, static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
struct rev_info *rev) {
fflush(stdout);
return stream_blob_to_fd(1, sha1, NULL, 0);
}
static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
{ {
unsigned long size; unsigned long size;
enum object_type type; enum object_type type;
@ -394,16 +400,16 @@ static int show_object(const unsigned char *sha1, int show_tag_object,
if (!buf) if (!buf)
return error(_("Could not read object %s"), sha1_to_hex(sha1)); return error(_("Could not read object %s"), sha1_to_hex(sha1));
if (show_tag_object) assert(type == OBJ_TAG);
while (offset < size && buf[offset] != '\n') { while (offset < size && buf[offset] != '\n') {
int new_offset = offset + 1; int new_offset = offset + 1;
while (new_offset < size && buf[new_offset++] != '\n') while (new_offset < size && buf[new_offset++] != '\n')
; /* do nothing */ ; /* do nothing */
if (!prefixcmp(buf + offset, "tagger ")) if (!prefixcmp(buf + offset, "tagger "))
show_tagger(buf + offset + 7, show_tagger(buf + offset + 7,
new_offset - offset - 7, rev); new_offset - offset - 7, rev);
offset = new_offset; offset = new_offset;
} }
if (offset < size) if (offset < size)
fwrite(buf + offset, size - offset, 1, stdout); fwrite(buf + offset, size - offset, 1, stdout);
@ -463,7 +469,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
const char *name = objects[i].name; const char *name = objects[i].name;
switch (o->type) { switch (o->type) {
case OBJ_BLOB: case OBJ_BLOB:
ret = show_object(o->sha1, 0, NULL); ret = show_blob_object(o->sha1, NULL);
break; break;
case OBJ_TAG: { case OBJ_TAG: {
struct tag *t = (struct tag *)o; struct tag *t = (struct tag *)o;
@ -474,7 +480,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
t->tag, t->tag,
diff_get_color_opt(&rev.diffopt, DIFF_RESET)); diff_get_color_opt(&rev.diffopt, DIFF_RESET));
ret = show_object(o->sha1, 1, &rev); ret = show_tag_object(o->sha1, &rev);
rev.shown_one = 1; rev.shown_one = 1;
if (ret) if (ret)
break; break;

View File

@ -52,7 +52,6 @@ static int fast_forward_only, option_edit = -1;
static int allow_trivial = 1, have_message; static int allow_trivial = 1, have_message;
static int overwrite_ignore = 1; static int overwrite_ignore = 1;
static struct strbuf merge_msg = STRBUF_INIT; static struct strbuf merge_msg = STRBUF_INIT;
static struct commit_list *remoteheads;
static struct strategy **use_strategies; static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc; static size_t use_strategies_nr, use_strategies_alloc;
static const char **xopts; static const char **xopts;
@ -318,7 +317,7 @@ static void finish_up_to_date(const char *msg)
drop_save(); drop_save();
} }
static void squash_message(struct commit *commit) static void squash_message(struct commit *commit, struct commit_list *remoteheads)
{ {
struct rev_info rev; struct rev_info rev;
struct strbuf out = STRBUF_INIT; struct strbuf out = STRBUF_INIT;
@ -366,6 +365,7 @@ static void squash_message(struct commit *commit)
} }
static void finish(struct commit *head_commit, static void finish(struct commit *head_commit,
struct commit_list *remoteheads,
const unsigned char *new_head, const char *msg) const unsigned char *new_head, const char *msg)
{ {
struct strbuf reflog_message = STRBUF_INIT; struct strbuf reflog_message = STRBUF_INIT;
@ -380,7 +380,7 @@ static void finish(struct commit *head_commit,
getenv("GIT_REFLOG_ACTION"), msg); getenv("GIT_REFLOG_ACTION"), msg);
} }
if (squash) { if (squash) {
squash_message(head_commit); squash_message(head_commit, remoteheads);
} else { } else {
if (verbosity >= 0 && !merge_msg.len) if (verbosity >= 0 && !merge_msg.len)
printf(_("No merge message -- not updating HEAD\n")); printf(_("No merge message -- not updating HEAD\n"));
@ -683,6 +683,7 @@ int try_merge_command(const char *strategy, size_t xopts_nr,
} }
static int try_merge_strategy(const char *strategy, struct commit_list *common, static int try_merge_strategy(const char *strategy, struct commit_list *common,
struct commit_list *remoteheads,
struct commit *head, const char *head_arg) struct commit *head, const char *head_arg)
{ {
int index_fd; int index_fd;
@ -876,14 +877,14 @@ static void read_merge_msg(struct strbuf *msg)
die_errno(_("Could not read from '%s'"), filename); die_errno(_("Could not read from '%s'"), filename);
} }
static void write_merge_state(void); static void write_merge_state(struct commit_list *);
static void abort_commit(const char *err_msg) static void abort_commit(struct commit_list *remoteheads, const char *err_msg)
{ {
if (err_msg) if (err_msg)
error("%s", err_msg); error("%s", err_msg);
fprintf(stderr, fprintf(stderr,
_("Not committing merge; use 'git commit' to complete the merge.\n")); _("Not committing merge; use 'git commit' to complete the merge.\n"));
write_merge_state(); write_merge_state(remoteheads);
exit(1); exit(1);
} }
@ -894,7 +895,7 @@ N_("Please enter a commit message to explain why this merge is necessary,\n"
"Lines starting with '#' will be ignored, and an empty message aborts\n" "Lines starting with '#' will be ignored, and an empty message aborts\n"
"the commit.\n"); "the commit.\n");
static void prepare_to_commit(void) static void prepare_to_commit(struct commit_list *remoteheads)
{ {
struct strbuf msg = STRBUF_INIT; struct strbuf msg = STRBUF_INIT;
const char *comment = _(merge_editor_comment); const char *comment = _(merge_editor_comment);
@ -907,18 +908,18 @@ static void prepare_to_commit(void)
git_path("MERGE_MSG"), "merge", NULL, NULL); git_path("MERGE_MSG"), "merge", NULL, NULL);
if (0 < option_edit) { if (0 < option_edit) {
if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
abort_commit(NULL); abort_commit(remoteheads, NULL);
} }
read_merge_msg(&msg); read_merge_msg(&msg);
stripspace(&msg, 0 < option_edit); stripspace(&msg, 0 < option_edit);
if (!msg.len) if (!msg.len)
abort_commit(_("Empty commit message.")); abort_commit(remoteheads, _("Empty commit message."));
strbuf_release(&merge_msg); strbuf_release(&merge_msg);
strbuf_addbuf(&merge_msg, &msg); strbuf_addbuf(&merge_msg, &msg);
strbuf_release(&msg); strbuf_release(&msg);
} }
static int merge_trivial(struct commit *head) static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
{ {
unsigned char result_tree[20], result_commit[20]; unsigned char result_tree[20], result_commit[20];
struct commit_list *parent = xmalloc(sizeof(*parent)); struct commit_list *parent = xmalloc(sizeof(*parent));
@ -929,45 +930,37 @@ static int merge_trivial(struct commit *head)
parent->next = xmalloc(sizeof(*parent->next)); parent->next = xmalloc(sizeof(*parent->next));
parent->next->item = remoteheads->item; parent->next->item = remoteheads->item;
parent->next->next = NULL; parent->next->next = NULL;
prepare_to_commit(); prepare_to_commit(remoteheads);
if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL, if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
sign_commit)) sign_commit))
die(_("failed to write commit object")); die(_("failed to write commit object"));
finish(head, result_commit, "In-index merge"); finish(head, remoteheads, result_commit, "In-index merge");
drop_save(); drop_save();
return 0; return 0;
} }
static int finish_automerge(struct commit *head, static int finish_automerge(struct commit *head,
int head_subsumed,
struct commit_list *common, struct commit_list *common,
struct commit_list *remoteheads,
unsigned char *result_tree, unsigned char *result_tree,
const char *wt_strategy) const char *wt_strategy)
{ {
struct commit_list *parents = NULL, *j; struct commit_list *parents = NULL;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
unsigned char result_commit[20]; unsigned char result_commit[20];
free_commit_list(common); free_commit_list(common);
if (allow_fast_forward) { parents = remoteheads;
parents = remoteheads; if (!head_subsumed || !allow_fast_forward)
commit_list_insert(head, &parents); commit_list_insert(head, &parents);
parents = reduce_heads(parents);
} else {
struct commit_list **pptr = &parents;
pptr = &commit_list_insert(head,
pptr)->next;
for (j = remoteheads; j; j = j->next)
pptr = &commit_list_insert(j->item, pptr)->next;
}
strbuf_addch(&merge_msg, '\n'); strbuf_addch(&merge_msg, '\n');
prepare_to_commit(); prepare_to_commit(remoteheads);
free_commit_list(remoteheads);
if (commit_tree(&merge_msg, result_tree, parents, result_commit, if (commit_tree(&merge_msg, result_tree, parents, result_commit,
NULL, sign_commit)) NULL, sign_commit))
die(_("failed to write commit object")); die(_("failed to write commit object"));
strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
finish(head, result_commit, buf.buf); finish(head, remoteheads, result_commit, buf.buf);
strbuf_release(&buf); strbuf_release(&buf);
drop_save(); drop_save();
return 0; return 0;
@ -1072,7 +1065,7 @@ static int setup_with_upstream(const char ***argv)
return i; return i;
} }
static void write_merge_state(void) static void write_merge_state(struct commit_list *remoteheads)
{ {
const char *filename; const char *filename;
int fd; int fd;
@ -1137,6 +1130,39 @@ static int default_edit_option(void)
st_stdin.st_mode == st_stdout.st_mode); st_stdin.st_mode == st_stdout.st_mode);
} }
static struct commit_list *collect_parents(struct commit *head_commit,
int *head_subsumed,
int argc, const char **argv)
{
int i;
struct commit_list *remoteheads = NULL, *parents, *next;
struct commit_list **remotes = &remoteheads;
if (head_commit)
remotes = &commit_list_insert(head_commit, remotes)->next;
for (i = 0; i < argc; i++) {
struct commit *commit = get_merge_parent(argv[i]);
if (!commit)
die(_("%s - not something we can merge"), argv[i]);
remotes = &commit_list_insert(commit, remotes)->next;
}
*remotes = NULL;
parents = reduce_heads(remoteheads);
*head_subsumed = 1; /* we will flip this to 0 when we find it */
for (remoteheads = NULL, remotes = &remoteheads;
parents;
parents = next) {
struct commit *commit = parents->item;
next = parents->next;
if (commit == head_commit)
*head_subsumed = 0;
else
remotes = &commit_list_insert(commit, remotes)->next;
}
return remoteheads;
}
int cmd_merge(int argc, const char **argv, const char *prefix) int cmd_merge(int argc, const char **argv, const char *prefix)
{ {
@ -1146,11 +1172,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
struct commit *head_commit; struct commit *head_commit;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
const char *head_arg; const char *head_arg;
int flag, i, ret = 0; int flag, i, ret = 0, head_subsumed;
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
struct commit_list *common = NULL; struct commit_list *common = NULL;
const char *best_strategy = NULL, *wt_strategy = NULL; const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads; struct commit_list *remoteheads, *p;
void *branch_to_free; void *branch_to_free;
if (argc == 2 && !strcmp(argv[1], "-h")) if (argc == 2 && !strcmp(argv[1], "-h"))
@ -1255,6 +1281,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
head_arg = argv[1]; head_arg = argv[1];
argv += 2; argv += 2;
argc -= 2; argc -= 2;
remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
} else if (!head_commit) { } else if (!head_commit) {
struct commit *remote_head; struct commit *remote_head;
/* /*
@ -1270,7 +1297,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!allow_fast_forward) if (!allow_fast_forward)
die(_("Non-fast-forward commit does not make sense into " die(_("Non-fast-forward commit does not make sense into "
"an empty head")); "an empty head"));
remote_head = get_merge_parent(argv[0]); remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
remote_head = remoteheads->item;
if (!remote_head) if (!remote_head)
die(_("%s - not something we can merge"), argv[0]); die(_("%s - not something we can merge"), argv[0]);
read_empty(remote_head->object.sha1, 0); read_empty(remote_head->object.sha1, 0);
@ -1288,8 +1316,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* the standard merge summary message to be appended * the standard merge summary message to be appended
* to the given message. * to the given message.
*/ */
for (i = 0; i < argc; i++) remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
merge_name(argv[i], &merge_names); for (p = remoteheads; p; p = p->next)
merge_name(merge_remote_util(p->item)->name, &merge_names);
if (!have_message || shortlog_len) { if (!have_message || shortlog_len) {
struct fmt_merge_msg_opts opts; struct fmt_merge_msg_opts opts;
@ -1308,19 +1337,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
builtin_merge_options); builtin_merge_options);
strbuf_addstr(&buf, "merge"); strbuf_addstr(&buf, "merge");
for (i = 0; i < argc; i++) for (p = remoteheads; p; p = p->next)
strbuf_addf(&buf, " %s", argv[i]); strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name);
setenv("GIT_REFLOG_ACTION", buf.buf, 0); setenv("GIT_REFLOG_ACTION", buf.buf, 0);
strbuf_reset(&buf); strbuf_reset(&buf);
for (i = 0; i < argc; i++) { for (p = remoteheads; p; p = p->next) {
struct commit *commit = get_merge_parent(argv[i]); struct commit *commit = p->item;
if (!commit)
die(_("%s - not something we can merge"), argv[i]);
remotes = &commit_list_insert(commit, remotes)->next;
strbuf_addf(&buf, "GITHEAD_%s", strbuf_addf(&buf, "GITHEAD_%s",
sha1_to_hex(commit->object.sha1)); sha1_to_hex(commit->object.sha1));
setenv(buf.buf, argv[i], 1); setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf); strbuf_reset(&buf);
if (!fast_forward_only && if (!fast_forward_only &&
merge_remote_util(commit) && merge_remote_util(commit) &&
@ -1333,7 +1359,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
option_edit = default_edit_option(); option_edit = default_edit_option();
if (!use_strategies) { if (!use_strategies) {
if (!remoteheads->next) if (!remoteheads)
; /* already up-to-date */
else if (!remoteheads->next)
add_strategies(pull_twohead, DEFAULT_TWOHEAD); add_strategies(pull_twohead, DEFAULT_TWOHEAD);
else else
add_strategies(pull_octopus, DEFAULT_OCTOPUS); add_strategies(pull_octopus, DEFAULT_OCTOPUS);
@ -1346,7 +1374,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
allow_trivial = 0; allow_trivial = 0;
} }
if (!remoteheads->next) if (!remoteheads)
; /* already up-to-date */
else if (!remoteheads->next)
common = get_merge_bases(head_commit, remoteheads->item, 1); common = get_merge_bases(head_commit, remoteheads->item, 1);
else { else {
struct commit_list *list = remoteheads; struct commit_list *list = remoteheads;
@ -1358,10 +1388,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
NULL, 0, DIE_ON_ERR); NULL, 0, DIE_ON_ERR);
if (!common) if (remoteheads && !common)
; /* No common ancestors found. We need a real merge. */ ; /* No common ancestors found. We need a real merge. */
else if (!remoteheads->next && !common->next && else if (!remoteheads ||
common->item == remoteheads->item) { (!remoteheads->next && !common->next &&
common->item == remoteheads->item)) {
/* /*
* If head can reach all the merge then we are up to date. * If head can reach all the merge then we are up to date.
* but first the most common case of merging one remote. * but first the most common case of merging one remote.
@ -1399,7 +1430,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
goto done; goto done;
} }
finish(head_commit, commit->object.sha1, msg.buf); finish(head_commit, remoteheads, commit->object.sha1, msg.buf);
drop_save(); drop_save();
goto done; goto done;
} else if (!remoteheads->next && common->next) } else if (!remoteheads->next && common->next)
@ -1421,7 +1452,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!read_tree_trivial(common->item->object.sha1, if (!read_tree_trivial(common->item->object.sha1,
head_commit->object.sha1, head_commit->object.sha1,
remoteheads->item->object.sha1)) { remoteheads->item->object.sha1)) {
ret = merge_trivial(head_commit); ret = merge_trivial(head_commit, remoteheads);
goto done; goto done;
} }
printf(_("Nope.\n")); printf(_("Nope.\n"));
@ -1492,7 +1523,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
wt_strategy = use_strategies[i]->name; wt_strategy = use_strategies[i]->name;
ret = try_merge_strategy(use_strategies[i]->name, ret = try_merge_strategy(use_strategies[i]->name,
common, head_commit, head_arg); common, remoteheads,
head_commit, head_arg);
if (!option_commit && !ret) { if (!option_commit && !ret) {
merge_was_ok = 1; merge_was_ok = 1;
/* /*
@ -1534,8 +1566,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* auto resolved the merge cleanly. * auto resolved the merge cleanly.
*/ */
if (automerge_was_ok) { if (automerge_was_ok) {
ret = finish_automerge(head_commit, common, result_tree, ret = finish_automerge(head_commit, head_subsumed,
wt_strategy); common, remoteheads,
result_tree, wt_strategy);
goto done; goto done;
} }
@ -1560,13 +1593,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
restore_state(head_commit->object.sha1, stash); restore_state(head_commit->object.sha1, stash);
printf(_("Using the %s to prepare resolving by hand.\n"), printf(_("Using the %s to prepare resolving by hand.\n"),
best_strategy); best_strategy);
try_merge_strategy(best_strategy, common, head_commit, head_arg); try_merge_strategy(best_strategy, common, remoteheads,
head_commit, head_arg);
} }
if (squash) if (squash)
finish(head_commit, NULL, NULL); finish(head_commit, remoteheads, NULL, NULL);
else else
write_merge_state(); write_merge_state(remoteheads);
if (merge_was_ok) if (merge_was_ok)
fprintf(stderr, _("Automatic merge went well; " fprintf(stderr, _("Automatic merge went well; "

View File

@ -24,6 +24,7 @@ static int progress = -1;
static const char **refspec; static const char **refspec;
static int refspec_nr; static int refspec_nr;
static int refspec_alloc; static int refspec_alloc;
static int default_matching_used;
static void add_refspec(const char *ref) static void add_refspec(const char *ref)
{ {
@ -65,6 +66,16 @@ static void set_refspecs(const char **refs, int nr)
} }
} }
static int push_url_of_remote(struct remote *remote, const char ***url_p)
{
if (remote->pushurl_nr) {
*url_p = remote->pushurl;
return remote->pushurl_nr;
}
*url_p = remote->url;
return remote->url_nr;
}
static void setup_push_upstream(struct remote *remote) static void setup_push_upstream(struct remote *remote)
{ {
struct strbuf refspec = STRBUF_INIT; struct strbuf refspec = STRBUF_INIT;
@ -76,7 +87,7 @@ static void setup_push_upstream(struct remote *remote)
"\n" "\n"
" git push %s HEAD:<name-of-remote-branch>\n"), " git push %s HEAD:<name-of-remote-branch>\n"),
remote->name); remote->name);
if (!branch->merge_nr || !branch->merge) if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n" die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n" "To push the current branch and set the remote as upstream, use\n"
"\n" "\n"
@ -87,6 +98,12 @@ static void setup_push_upstream(struct remote *remote)
if (branch->merge_nr != 1) if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, " die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name); "refusing to push."), branch->name);
if (strcmp(branch->remote_name, remote->name))
die(_("You are pushing to remote '%s', which is not the upstream of\n"
"your current branch '%s', without telling me what to push\n"
"to update which remote branch."),
remote->name, branch->name);
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf); add_refspec(refspec.buf);
} }
@ -95,6 +112,9 @@ static void setup_default_push_refspecs(struct remote *remote)
{ {
switch (push_default) { switch (push_default) {
default: default:
case PUSH_DEFAULT_UNSPECIFIED:
default_matching_used = 1;
/* fallthru */
case PUSH_DEFAULT_MATCHING: case PUSH_DEFAULT_MATCHING:
add_refspec(":"); add_refspec(":");
break; break;
@ -114,6 +134,45 @@ static void setup_default_push_refspecs(struct remote *remote)
} }
} }
static const char message_advice_pull_before_push[] =
N_("Updates were rejected because the tip of your current branch is behind\n"
"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
"before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_use_upstream[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
"counterpart. If you did not intend to push that branch, you may want to\n"
"specify branches to push or set the 'push.default' configuration\n"
"variable to 'current' or 'upstream' to push only the current branch.");
static const char message_advice_checkout_pull_push[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
"counterpart. Check out this branch and merge the remote changes\n"
"(e.g. 'git pull') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static void advise_pull_before_push(void)
{
if (!advice_push_non_ff_current || !advice_push_nonfastforward)
return;
advise(_(message_advice_pull_before_push));
}
static void advise_use_upstream(void)
{
if (!advice_push_non_ff_default || !advice_push_nonfastforward)
return;
advise(_(message_advice_use_upstream));
}
static void advise_checkout_pull_push(void)
{
if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
return;
advise(_(message_advice_checkout_pull_push));
}
static int push_with_options(struct transport *transport, int flags) static int push_with_options(struct transport *transport, int flags)
{ {
int err; int err;
@ -135,14 +194,21 @@ static int push_with_options(struct transport *transport, int flags)
error(_("failed to push some refs to '%s'"), transport->url); error(_("failed to push some refs to '%s'"), transport->url);
err |= transport_disconnect(transport); err |= transport_disconnect(transport);
if (!err) if (!err)
return 0; return 0;
if (nonfastforward && advice_push_nonfastforward) { switch (nonfastforward) {
fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n" default:
"Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" break;
"'Note about fast-forwards' section of 'git push --help' for details.\n")); case NON_FF_HEAD:
advise_pull_before_push();
break;
case NON_FF_OTHER:
if (default_matching_used)
advise_use_upstream();
else
advise_checkout_pull_push();
break;
} }
return 1; return 1;
@ -196,13 +262,7 @@ static int do_push(const char *repo, int flags)
setup_default_push_refspecs(remote); setup_default_push_refspecs(remote);
} }
errs = 0; errs = 0;
if (remote->pushurl_nr) { url_nr = push_url_of_remote(remote, &url);
url = remote->pushurl;
url_nr = remote->pushurl_nr;
} else {
url = remote->url;
url_nr = remote->url_nr;
}
if (url_nr) { if (url_nr) {
for (i = 0; i < url_nr; i++) { for (i = 0; i < url_nr; i++) {
struct transport *transport = struct transport *transport =
@ -224,13 +284,21 @@ static int option_parse_recurse_submodules(const struct option *opt,
const char *arg, int unset) const char *arg, int unset)
{ {
int *flags = opt->value; int *flags = opt->value;
if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
die("%s can only be used once.", opt->long_name);
if (arg) { if (arg) {
if (!strcmp(arg, "check")) if (!strcmp(arg, "check"))
*flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
else if (!strcmp(arg, "on-demand"))
*flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
else else
die("bad %s argument: %s", opt->long_name, arg); die("bad %s argument: %s", opt->long_name, arg);
} else } else
die("option %s needs an argument (check)", opt->long_name); die("option %s needs an argument (check|on-demand)",
opt->long_name);
return 0; return 0;
} }

View File

@ -9,7 +9,7 @@
static const char * const builtin_remote_usage[] = { static const char * const builtin_remote_usage[] = {
"git remote [-v | --verbose]", "git remote [-v | --verbose]",
"git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>", "git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>",
"git remote rename <old> <new>", "git remote rename <old> <new>",
"git remote rm <name>", "git remote rm <name>",
"git remote set-head <name> (-a | -d | <branch>)", "git remote set-head <name> (-a | -d | <branch>)",
@ -17,7 +17,7 @@ static const char * const builtin_remote_usage[] = {
"git remote prune [-n | --dry-run] <name>", "git remote prune [-n | --dry-run] <name>",
"git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]", "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
"git remote set-branches [--add] <name> <branch>...", "git remote set-branches [--add] <name> <branch>...",
"git remote set-url <name> <newurl> [<oldurl>]", "git remote set-url [--push] <name> <newurl> [<oldurl>]",
"git remote set-url --add <name> <newurl>", "git remote set-url --add <name> <newurl>",
"git remote set-url --delete <name> <url>", "git remote set-url --delete <name> <url>",
NULL NULL

View File

@ -634,6 +634,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--show-prefix")) { if (!strcmp(arg, "--show-prefix")) {
if (prefix) if (prefix)
puts(prefix); puts(prefix);
else
putchar('\n');
continue; continue;
} }
if (!strcmp(arg, "--show-cdup")) { if (!strcmp(arg, "--show-cdup")) {

View File

@ -86,6 +86,7 @@ static void verify_opt_mutually_compatible(const char *me, ...)
break; break;
} }
} }
va_end(ap);
if (opt1 && opt2) if (opt1 && opt2)
die(_("%s: %s cannot be used with %s"), me, opt1, opt2); die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
@ -181,12 +182,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (opts->subcommand != REPLAY_NONE) { if (opts->subcommand != REPLAY_NONE) {
opts->revs = NULL; opts->revs = NULL;
} else { } else {
struct setup_revision_opt s_r_opt;
opts->revs = xmalloc(sizeof(*opts->revs)); opts->revs = xmalloc(sizeof(*opts->revs));
init_revisions(opts->revs, NULL); init_revisions(opts->revs, NULL);
opts->revs->no_walk = 1; opts->revs->no_walk = 1;
if (argc < 2) if (argc < 2)
usage_with_options(usage_str, options); usage_with_options(usage_str, options);
argc = setup_revisions(argc, argv, opts->revs, NULL); memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.assume_dashdash = 1;
argc = setup_revisions(argc, argv, opts->revs, &s_r_opt);
} }
if (argc > 1) if (argc > 1)

View File

@ -15,6 +15,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix)
OPT_END() OPT_END()
}; };
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, argc = parse_options(argc, argv, prefix, options,
update_server_info_usage, 0); update_server_info_usage, 0);
if (argc > 0) if (argc > 0)

View File

@ -289,7 +289,7 @@ int create_bundle(struct bundle_header *header, const char *path,
argc = setup_revisions(argc, argv, &revs, NULL); argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1) if (argc > 1)
return error("unrecognized argument: %s'", argv[1]); return error("unrecognized argument: %s", argv[1]);
object_array_remove_duplicates(&revs.pending); object_array_remove_duplicates(&revs.pending);

34
cache.h
View File

@ -625,7 +625,8 @@ enum push_default_type {
PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING, PUSH_DEFAULT_MATCHING,
PUSH_DEFAULT_UPSTREAM, PUSH_DEFAULT_UPSTREAM,
PUSH_DEFAULT_CURRENT PUSH_DEFAULT_CURRENT,
PUSH_DEFAULT_UNSPECIFIED
}; };
extern enum branch_track git_branch_track; extern enum branch_track git_branch_track;
@ -708,6 +709,19 @@ static inline void hashclr(unsigned char *hash)
#define EMPTY_TREE_SHA1_BIN \ #define EMPTY_TREE_SHA1_BIN \
((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL) ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
#define EMPTY_BLOB_SHA1_HEX \
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
#define EMPTY_BLOB_SHA1_BIN_LITERAL \
"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
"\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
#define EMPTY_BLOB_SHA1_BIN \
((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
static inline int is_empty_blob_sha1(const unsigned char *sha1)
{
return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
}
int git_mkstemp(char *path, size_t n, const char *template); int git_mkstemp(char *path, size_t n, const char *template);
int git_mkstemps(char *path, size_t n, const char *template, int suffix_len); int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
@ -928,6 +942,22 @@ extern const char *fmt_name(const char *name, const char *email);
extern const char *git_editor(void); extern const char *git_editor(void);
extern const char *git_pager(int stdout_is_tty); extern const char *git_pager(int stdout_is_tty);
struct ident_split {
const char *name_begin;
const char *name_end;
const char *mail_begin;
const char *mail_end;
const char *date_begin;
const char *date_end;
const char *tz_begin;
const char *tz_end;
};
/*
* Signals an success with 0, but time part of the result may be NULL
* if the input lacks timestamp and zone
*/
extern int split_ident_line(struct ident_split *, const char *, int);
struct checkout { struct checkout {
const char *base_dir; const char *base_dir;
int base_dir_len; int base_dir_len;
@ -1276,4 +1306,6 @@ extern struct startup_info *startup_info;
/* builtin/merge.c */ /* builtin/merge.c */
int checkout_fast_forward(const unsigned char *from, const unsigned char *to); int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
int sane_execvp(const char *file, char *const argv[]);
#endif /* CACHE_H */ #endif /* CACHE_H */

View File

@ -423,7 +423,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt,
hunk_begin, j); hunk_begin, j);
la = (la + context < cnt + 1) ? la = (la + context < cnt + 1) ?
(la + context) : cnt + 1; (la + context) : cnt + 1;
while (j <= --la) { while (la && j <= --la) {
if (sline[la].flag & mark) { if (sline[la].flag & mark) {
contin = 1; contin = 1;
break; break;

View File

@ -76,6 +76,7 @@ git-mktree plumbingmanipulators
git-mv mainporcelain common git-mv mainporcelain common
git-name-rev plumbinginterrogators git-name-rev plumbinginterrogators
git-notes mainporcelain git-notes mainporcelain
git-p4 foreignscminterface
git-pack-objects plumbingmanipulators git-pack-objects plumbingmanipulators
git-pack-redundant plumbinginterrogators git-pack-redundant plumbinginterrogators
git-pack-refs ancillarymanipulators git-pack-refs ancillarymanipulators

View File

@ -7,6 +7,7 @@
#include "revision.h" #include "revision.h"
#include "notes.h" #include "notes.h"
#include "gpg-interface.h" #include "gpg-interface.h"
#include "mergesort.h"
int save_commit_buffer = 1; int save_commit_buffer = 1;
@ -360,6 +361,21 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
return new_list; return new_list;
} }
void commit_list_reverse(struct commit_list **list_p)
{
struct commit_list *prev = NULL, *curr = *list_p, *next;
if (!list_p)
return;
while (curr) {
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
*list_p = prev;
}
unsigned commit_list_count(const struct commit_list *l) unsigned commit_list_count(const struct commit_list *l)
{ {
unsigned c = 0; unsigned c = 0;
@ -390,15 +406,31 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm
return commit_list_insert(item, pp); return commit_list_insert(item, pp);
} }
static int commit_list_compare_by_date(const void *a, const void *b)
{
unsigned long a_date = ((const struct commit_list *)a)->item->date;
unsigned long b_date = ((const struct commit_list *)b)->item->date;
if (a_date < b_date)
return 1;
if (a_date > b_date)
return -1;
return 0;
}
static void *commit_list_get_next(const void *a)
{
return ((const struct commit_list *)a)->next;
}
static void commit_list_set_next(void *a, void *next)
{
((struct commit_list *)a)->next = next;
}
void commit_list_sort_by_date(struct commit_list **list) void commit_list_sort_by_date(struct commit_list **list)
{ {
struct commit_list *ret = NULL; *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
while (*list) { commit_list_compare_by_date);
commit_list_insert_by_date((*list)->item, &ret);
*list = (*list)->next;
}
*list = ret;
} }
struct commit *pop_most_recent_commit(struct commit_list **list, struct commit *pop_most_recent_commit(struct commit_list **list,

View File

@ -57,6 +57,7 @@ unsigned commit_list_count(const struct commit_list *l);
struct commit_list *commit_list_insert_by_date(struct commit *item, struct commit_list *commit_list_insert_by_date(struct commit *item,
struct commit_list **list); struct commit_list **list);
void commit_list_sort_by_date(struct commit_list **list); void commit_list_sort_by_date(struct commit_list **list);
void commit_list_reverse(struct commit_list **list_p);
void free_commit_list(struct commit_list *list); void free_commit_list(struct commit_list *list);

View File

@ -1003,7 +1003,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
} }
} }
void mingw_execvp(const char *cmd, char *const *argv) int mingw_execvp(const char *cmd, char *const *argv)
{ {
char **path = get_path_split(); char **path = get_path_split();
char *prog = path_lookup(cmd, path, 0); char *prog = path_lookup(cmd, path, 0);
@ -1015,11 +1015,13 @@ void mingw_execvp(const char *cmd, char *const *argv)
errno = ENOENT; errno = ENOENT;
free_path_split(path); free_path_split(path);
return -1;
} }
void mingw_execv(const char *cmd, char *const *argv) int mingw_execv(const char *cmd, char *const *argv)
{ {
mingw_execve(cmd, argv, environ); mingw_execve(cmd, argv, environ);
return -1;
} }
int mingw_kill(pid_t pid, int sig) int mingw_kill(pid_t pid, int sig)

View File

@ -22,9 +22,10 @@ typedef int socklen_t;
#define S_IWOTH 0 #define S_IWOTH 0
#define S_IXOTH 0 #define S_IXOTH 0
#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) #define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
#define S_ISUID 0
#define S_ISGID 0 #define S_ISUID 0004000
#define S_ISVTX 0 #define S_ISGID 0002000
#define S_ISVTX 0001000
#define WIFEXITED(x) 1 #define WIFEXITED(x) 1
#define WIFSIGNALED(x) 0 #define WIFSIGNALED(x) 0
@ -274,9 +275,9 @@ int mingw_utime(const char *file_name, const struct utimbuf *times);
pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
const char *dir, const char *dir,
int fhin, int fhout, int fherr); int fhin, int fhout, int fherr);
void mingw_execvp(const char *cmd, char *const *argv); int mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp #define execvp mingw_execvp
void mingw_execv(const char *cmd, char *const *argv); int mingw_execv(const char *cmd, char *const *argv);
#define execv mingw_execv #define execv mingw_execv
static inline unsigned int git_ntohl(unsigned int x) static inline unsigned int git_ntohl(unsigned int x)

View File

@ -1,65 +1,55 @@
# -*- Autoconf -*- # -*- Autoconf -*-
# Process this file with autoconf to produce a configure script. # Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59) ## Definitions of private macros.
AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
AC_CONFIG_SRCDIR([git.c])
config_file=config.mak.autogen
config_append=config.mak.append
config_in=config.mak.in
echo "# ${config_append}. Generated by configure." > "${config_append}"
## Definitions of macros
# GIT_CONF_APPEND_LINE(LINE) # GIT_CONF_APPEND_LINE(LINE)
# -------------------------- # --------------------------
# Append LINE to file ${config_append} # Append LINE to file ${config_append}
AC_DEFUN([GIT_CONF_APPEND_LINE], AC_DEFUN([GIT_CONF_APPEND_LINE],
[echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE [echo "$1" >> "${config_append}"])
#
# GIT_ARG_SET_PATH(PROGRAM) # GIT_ARG_SET_PATH(PROGRAM)
# ------------------------- # -------------------------
# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM # Provide --with-PROGRAM=PATH option to set PATH to PROGRAM
# Optional second argument allows setting NO_PROGRAM=YesPlease if # Optional second argument allows setting NO_PROGRAM=YesPlease if
# --without-PROGRAM version used. # --without-PROGRAM version used.
AC_DEFUN([GIT_ARG_SET_PATH], AC_DEFUN([GIT_ARG_SET_PATH],
[AC_ARG_WITH([$1], [AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-$1=PATH], [AS_HELP_STRING([--with-$1=PATH],
[provide PATH to $1])], [provide PATH to $1])],
[GIT_CONF_APPEND_PATH($1,$2)],[]) [GIT_CONF_APPEND_PATH([$1], [$2])],
])# GIT_ARG_SET_PATH [])])
#
# GIT_CONF_APPEND_PATH(PROGRAM) # GIT_CONF_APPEND_PATH(PROGRAM)
# ------------------------------ # -----------------------------
# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH # Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH
# Used by GIT_ARG_SET_PATH(PROGRAM) # Used by GIT_ARG_SET_PATH(PROGRAM)
# Optional second argument allows setting NO_PROGRAM=YesPlease if # Optional second argument allows setting NO_PROGRAM=YesPlease if
# --without-PROGRAM is used. # --without-PROGRAM is used.
AC_DEFUN([GIT_CONF_APPEND_PATH], AC_DEFUN([GIT_CONF_APPEND_PATH],
[PROGRAM=m4_toupper($1); \ [m4_pushdef([GIT_UC_PROGRAM], m4_toupper([$1]))dnl
if test "$withval" = "no"; then \ PROGRAM=GIT_UC_PROGRAM
if test -n "$2"; then \ if test "$withval" = "no"; then
m4_toupper($1)_PATH=$withval; \ if test -n "$2"; then
AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \ GIT_UC_PROGRAM[]_PATH=$withval
GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \ AC_MSG_NOTICE([Disabling use of ${PROGRAM}])
GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \ GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease)
else \ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=)
AC_MSG_ERROR([You cannot use git without $1]); \ else
fi; \ AC_MSG_ERROR([You cannot use git without $1])
else \ fi
if test "$withval" = "yes"; then \ else
AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ if test "$withval" = "yes"; then
else \ AC_MSG_WARN([You should provide path for --with-$1=PATH])
m4_toupper($1)_PATH=$withval; \ else
AC_MSG_NOTICE([Setting m4_toupper($1)_PATH to $withval]); \ GIT_UC_PROGRAM[]_PATH=$withval
GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \ AC_MSG_NOTICE([Setting GIT_UC_PROGRAM[]_PATH to $withval])
fi; \ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval)
fi; \ fi
]) # GIT_CONF_APPEND_PATH fi
# m4_popdef([GIT_UC_PROGRAM])])
# GIT_PARSE_WITH(PACKAGE) # GIT_PARSE_WITH(PACKAGE)
# ----------------------- # -----------------------
# For use in AC_ARG_WITH action-if-found, for packages default ON. # For use in AC_ARG_WITH action-if-found, for packages default ON.
@ -67,21 +57,22 @@ fi; \
# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH # * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH
# * Unset NO_PACKAGE for --with-PACKAGE without ARG # * Unset NO_PACKAGE for --with-PACKAGE without ARG
AC_DEFUN([GIT_PARSE_WITH], AC_DEFUN([GIT_PARSE_WITH],
[PACKAGE=m4_toupper($1); \ [m4_pushdef([GIT_UC_PACKAGE], m4_toupper([$1]))dnl
if test "$withval" = "no"; then \ PACKAGE=GIT_UC_PACKAGE
m4_toupper(NO_$1)=YesPlease; \ if test "$withval" = "no"; then
elif test "$withval" = "yes"; then \ NO_[]GIT_UC_PACKAGE=YesPlease
m4_toupper(NO_$1)=; \ elif test "$withval" = "yes"; then
else \ NO_[]GIT_UC_PACKAGE=
m4_toupper(NO_$1)=; \ else
m4_toupper($1)DIR=$withval; \ NO_[]GIT_UC_PACKAGE=
AC_MSG_NOTICE([Setting m4_toupper($1)DIR to $withval]); \ GIT_UC_PACKAGE[]DIR=$withval
GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ AC_MSG_NOTICE([Setting GIT_UC_PACKAGE[]DIR to $withval])
fi \ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval)
])# GIT_PARSE_WITH fi
# m4_popdef([GIT_UC_PACKAGE])])
# GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT) # GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
# --------------------- # -----------------------------------------------------
# Set VAR to the value specied by --with-WITHNAME. # Set VAR to the value specied by --with-WITHNAME.
# No verification of arguments is performed, but warnings are issued # No verification of arguments is performed, but warnings are issued
# if either 'yes' or 'no' is specified. # if either 'yes' or 'no' is specified.
@ -90,33 +81,32 @@ fi \
AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR], AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
[AC_ARG_WITH([$1], [AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-$1=VALUE], $3)], [AS_HELP_STRING([--with-$1=VALUE], $3)],
if test -n "$withval"; then \ if test -n "$withval"; then
if test "$withval" = "yes" -o "$withval" = "no"; then \ if test "$withval" = "yes" -o "$withval" = "no"; then
AC_MSG_WARN([You likely do not want either 'yes' or 'no' as] AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
[a value for $1 ($2). Maybe you do...?]); \ [a value for $1 ($2). Maybe you do...?])
fi; \ fi
\ AC_MSG_NOTICE([Setting $2 to $withval])
AC_MSG_NOTICE([Setting $2 to $withval]); \ GIT_CONF_APPEND_LINE($2=$withval)
GIT_CONF_APPEND_LINE($2=$withval); \
fi)])# GIT_PARSE_WITH_SET_MAKE_VAR fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
dnl #
dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE) # GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
dnl ----------------------------------------- # -----------------------------------------
dnl Similar to AC_CHECK_FUNC, but on systems that do not generate # Similar to AC_CHECK_FUNC, but on systems that do not generate
dnl warnings for missing prototypes (e.g. FreeBSD when compiling without # warnings for missing prototypes (e.g. FreeBSD when compiling without
dnl -Wall), it does not work. By looking for function definition in # -Wall), it does not work. By looking for function definition in
dnl libraries, this problem can be worked around. # libraries, this problem can be worked around.
AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[ AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[
AC_SEARCH_LIBS([$1],, AC_SEARCH_LIBS([$1],,
[$2],[$3]) [$2],[$3])
],[$3])]) ],[$3])])
dnl #
dnl GIT_STASH_FLAGS(BASEPATH_VAR) # GIT_STASH_FLAGS(BASEPATH_VAR)
dnl ----------------------------- # -----------------------------
dnl Allow for easy stashing of LDFLAGS and CPPFLAGS before running # Allow for easy stashing of LDFLAGS and CPPFLAGS before running
dnl tests that may want to take user settings into account. # tests that may want to take user settings into account.
AC_DEFUN([GIT_STASH_FLAGS],[ AC_DEFUN([GIT_STASH_FLAGS],[
if test -n "$1"; then if test -n "$1"; then
old_CPPFLAGS="$CPPFLAGS" old_CPPFLAGS="$CPPFLAGS"
@ -137,6 +127,19 @@ if test -n "$1"; then
fi fi
]) ])
## Configure body starts here.
AC_PREREQ(2.59)
AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
AC_CONFIG_SRCDIR([git.c])
config_file=config.mak.autogen
config_append=config.mak.append
config_in=config.mak.in
echo "# ${config_append}. Generated by configure." > "${config_append}"
# Directories holding "saner" versions of common or POSIX binaries. # Directories holding "saner" versions of common or POSIX binaries.
AC_ARG_WITH([sane-tool-path], AC_ARG_WITH([sane-tool-path],
[AS_HELP_STRING( [AS_HELP_STRING(
@ -161,14 +164,13 @@ AC_ARG_WITH([sane-tool-path],
AC_ARG_WITH([lib], AC_ARG_WITH([lib],
[AS_HELP_STRING([--with-lib=ARG], [AS_HELP_STRING([--with-lib=ARG],
[ARG specifies alternative name for lib directory])], [ARG specifies alternative name for lib directory])],
[if test "$withval" = "no" || test "$withval" = "yes"; then \ [if test "$withval" = "no" || test "$withval" = "yes"; then
AC_MSG_WARN([You should provide name for --with-lib=ARG]); \ AC_MSG_WARN([You should provide name for --with-lib=ARG])
else \ else
lib=$withval; \ lib=$withval
AC_MSG_NOTICE([Setting lib to '$lib']); \ AC_MSG_NOTICE([Setting lib to '$lib'])
GIT_CONF_APPEND_LINE(lib=$withval); \ GIT_CONF_APPEND_LINE(lib=$withval)
fi; \ fi])
],[])
if test -z "$lib"; then if test -z "$lib"; then
AC_MSG_NOTICE([Setting lib to 'lib' (the default)]) AC_MSG_NOTICE([Setting lib to 'lib' (the default)])
@ -234,9 +236,9 @@ AC_MSG_NOTICE([CHECKS for site configuration])
# /foo/bar/include and /foo/bar/lib directories. # /foo/bar/include and /foo/bar/lib directories.
AC_ARG_WITH(openssl, AC_ARG_WITH(openssl,
AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),
GIT_PARSE_WITH(openssl)) GIT_PARSE_WITH([openssl]))
#
# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be # Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
# able to use Perl-compatible regular expressions. # able to use Perl-compatible regular expressions.
# #
@ -246,17 +248,16 @@ GIT_PARSE_WITH(openssl))
AC_ARG_WITH(libpcre, AC_ARG_WITH(libpcre,
AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)]) AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)])
AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]), AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]),
if test "$withval" = "no"; then \ if test "$withval" = "no"; then
USE_LIBPCRE=; \ USE_LIBPCRE=
elif test "$withval" = "yes"; then \ elif test "$withval" = "yes"; then
USE_LIBPCRE=YesPlease; \ USE_LIBPCRE=YesPlease
else else
USE_LIBPCRE=YesPlease; \ USE_LIBPCRE=YesPlease
LIBPCREDIR=$withval; \ LIBPCREDIR=$withval
AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \ AC_MSG_NOTICE([Setting LIBPCREDIR to $withval])
GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \ GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval)
fi \ fi)
)
# #
# Define NO_CURL if you do not have curl installed. git-http-pull and # Define NO_CURL if you do not have curl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https:// # git-http-push are not built, and you cannot use http:// and https://
@ -364,7 +365,7 @@ AC_ARG_WITH(tcltk,
AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)]) AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.]) AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.])
AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if]) AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if])
AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),\ AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),
GIT_PARSE_WITH(tcltk)) GIT_PARSE_WITH(tcltk))
# #

View File

@ -304,16 +304,16 @@ __git_ps1 ()
fi fi
} }
# __gitcomp_1 requires 2 arguments
__gitcomp_1 () __gitcomp_1 ()
{ {
local c IFS=' '$'\t'$'\n' local c IFS=$' \t\n'
for c in $1; do for c in $1; do
case "$c$2" in c="$c$2"
--*=*) printf %s$'\n' "$c$2" ;; case $c in
*.) printf %s$'\n' "$c$2" ;; --*=*|*.) ;;
*) printf %s$'\n' "$c$2 " ;; *) c="$c " ;;
esac esac
printf '%s\n' "$c"
done done
} }
@ -1658,7 +1658,7 @@ _git_notes ()
__gitcomp '--ref' __gitcomp '--ref'
;; ;;
,*) ,*)
case "${words[cword-1]}" in case "$prev" in
--ref) --ref)
__gitcomp_nl "$(__git_refs)" __gitcomp_nl "$(__git_refs)"
;; ;;
@ -1684,7 +1684,7 @@ _git_notes ()
prune,*) prune,*)
;; ;;
*) *)
case "${words[cword-1]}" in case "$prev" in
-m|-F) -m|-F)
;; ;;
*) *)
@ -2623,8 +2623,9 @@ _git ()
case "$i" in case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;; --git-dir=*) __git_dir="${i#--git-dir=}" ;;
--bare) __git_dir="." ;; --bare) __git_dir="." ;;
--version|-p|--paginate) ;;
--help) command="help"; break ;; --help) command="help"; break ;;
-c) c=$((++c)) ;;
-*) ;;
*) command="$i"; break ;; *) command="$i"; break ;;
esac esac
((c++)) ((c++))
@ -2639,9 +2640,12 @@ _git ()
--bare --bare
--version --version
--exec-path --exec-path
--exec-path=
--html-path --html-path
--info-path
--work-tree= --work-tree=
--namespace= --namespace=
--no-replace-objects
--help --help
" "
;; ;;

View File

@ -0,0 +1,12 @@
The git-p4 script moved to the top-level of the git source directory.
Invoke it as any other git command, like "git p4 clone", for instance.
Note that the top-level git-p4.py script is now the source. It is
built using make to git-p4, which will be installed.
Windows users can copy the git-p4.py source script directly, possibly
invoking it through a batch file called "git-p4.bat" in the same folder.
It should contain just one line:
@python "%~d0%~p0git-p4.py" %*

View File

@ -1 +0,0 @@
@python "%~d0%~p0git-p4" %*

5
contrib/subtree/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*~
git-subtree.xml
git-subtree.1
mainline
subproj

339
contrib/subtree/COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 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.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, 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 or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
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 give any other recipients of the Program a copy of this License
along with the Program.
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 Program or any portion
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
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 Program, 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 Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) 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; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, 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 executable. However, as a
special exception, the source code 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.
If distribution of executable or 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 counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program 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.
5. 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 Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program 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 to
this License.
7. 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 Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program 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 Program.
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.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program 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.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 Program
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 Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, 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
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), 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 Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. 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 program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

28
contrib/subtree/INSTALL Normal file
View File

@ -0,0 +1,28 @@
HOW TO INSTALL git-subtree
==========================
First, build from the top source directory.
Then, in contrib/subtree, run:
make
make install
make install-doc
If you used configure to do the main build the git-subtree build will
pick up those settings. If not, you will likely have to provide a
value for prefix:
make prefix=<some dir>
make prefix=<some dir> install
make prefix=<some dir> install-doc
To run tests first copy git-subtree to the main build area so the
newly-built git can find it:
cp git-subtree ../..
Then:
make test

52
contrib/subtree/Makefile Normal file
View File

@ -0,0 +1,52 @@
-include ../../config.mak.autogen
-include ../../config.mak
prefix ?= /usr/local
mandir ?= $(prefix)/share/man
libexecdir ?= $(prefix)/libexec/git-core
gitdir ?= $(shell git --exec-path)
man1dir ?= $(mandir)/man1
gitver ?= $(word 3,$(shell git --version))
# this should be set to a 'standard' bsd-type install program
INSTALL ?= install
ASCIIDOC_CONF = ../../Documentation/asciidoc.conf
MANPAGE_NORMAL_XSL = ../../Documentation/manpage-normal.xsl
GIT_SUBTREE_SH := git-subtree.sh
GIT_SUBTREE := git-subtree
GIT_SUBTREE_DOC := git-subtree.1
GIT_SUBTREE_XML := git-subtree.xml
GIT_SUBTREE_TXT := git-subtree.txt
all: $(GIT_SUBTREE)
$(GIT_SUBTREE): $(GIT_SUBTREE_SH)
cp $< $@ && chmod +x $@
doc: $(GIT_SUBTREE_DOC)
install: $(GIT_SUBTREE)
$(INSTALL) -m 755 $(GIT_SUBTREE) $(libexecdir)
install-doc: install-man
install-man: $(GIT_SUBTREE_DOC)
$(INSTALL) -m 644 $^ $(man1dir)
$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
xmlto -m $(MANPAGE_NORMAL_XSL) man $^
$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \
-agit_version=$(gitver) $^
test:
$(MAKE) -C t/ test
clean:
rm -f *~ *.xml *.html *.1
rm -rf subproj mainline

8
contrib/subtree/README Normal file
View File

@ -0,0 +1,8 @@
Please read git-subtree.txt for documentation.
Please don't contact me using github mail; it's slow, ugly, and worst of
all, redundant. Email me instead at apenwarr@gmail.com and I'll be happy to
help.
Avery

712
contrib/subtree/git-subtree.sh Executable file
View File

@ -0,0 +1,712 @@
#!/bin/bash
#
# git-subtree.sh: split/join git repositories in subdirectories of this one
#
# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
#
if [ $# -eq 0 ]; then
set -- -h
fi
OPTS_SPEC="\
git subtree add --prefix=<prefix> <commit>
git subtree merge --prefix=<prefix> <commit>
git subtree pull --prefix=<prefix> <repository> <refspec...>
git subtree push --prefix=<prefix> <repository> <refspec...>
git subtree split --prefix=<prefix> <commit...>
--
h,help show the help
q quiet
d show debug messages
P,prefix= the name of the subdir to split out
m,message= use the given message as the commit message for the merge commit
options for 'split'
annotate= add a prefix to commit message of new commits
b,branch= create a new branch from the split subtree
ignore-joins ignore prior --rejoin commits
onto= try connecting new tree to an existing one
rejoin merge the new branch back into HEAD
options for 'add', 'merge', 'pull' and 'push'
squash merge subtree changes as a single commit
"
eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
PATH=$PATH:$(git --exec-path)
. git-sh-setup
require_work_tree
quiet=
branch=
debug=
command=
onto=
rejoin=
ignore_joins=
annotate=
squash=
message=
debug()
{
if [ -n "$debug" ]; then
echo "$@" >&2
fi
}
say()
{
if [ -z "$quiet" ]; then
echo "$@" >&2
fi
}
assert()
{
if "$@"; then
:
else
die "assertion failed: " "$@"
fi
}
#echo "Options: $*"
while [ $# -gt 0 ]; do
opt="$1"
shift
case "$opt" in
-q) quiet=1 ;;
-d) debug=1 ;;
--annotate) annotate="$1"; shift ;;
--no-annotate) annotate= ;;
-b) branch="$1"; shift ;;
-P) prefix="$1"; shift ;;
-m) message="$1"; shift ;;
--no-prefix) prefix= ;;
--onto) onto="$1"; shift ;;
--no-onto) onto= ;;
--rejoin) rejoin=1 ;;
--no-rejoin) rejoin= ;;
--ignore-joins) ignore_joins=1 ;;
--no-ignore-joins) ignore_joins= ;;
--squash) squash=1 ;;
--no-squash) squash= ;;
--) break ;;
*) die "Unexpected option: $opt" ;;
esac
done
command="$1"
shift
case "$command" in
add|merge|pull) default= ;;
split|push) default="--default HEAD" ;;
*) die "Unknown command '$command'" ;;
esac
if [ -z "$prefix" ]; then
die "You must provide the --prefix option."
fi
case "$command" in
add) [ -e "$prefix" ] &&
die "prefix '$prefix' already exists." ;;
*) [ -e "$prefix" ] ||
die "'$prefix' does not exist; use 'git subtree add'" ;;
esac
dir="$(dirname "$prefix/.")"
if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
revs=$(git rev-parse $default --revs-only "$@") || exit $?
dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
if [ -n "$dirs" ]; then
die "Error: Use --prefix instead of bare filenames."
fi
fi
debug "command: {$command}"
debug "quiet: {$quiet}"
debug "revs: {$revs}"
debug "dir: {$dir}"
debug "opts: {$*}"
debug
cache_setup()
{
cachedir="$GIT_DIR/subtree-cache/$$"
rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
debug "Using cachedir: $cachedir" >&2
}
cache_get()
{
for oldrev in $*; do
if [ -r "$cachedir/$oldrev" ]; then
read newrev <"$cachedir/$oldrev"
echo $newrev
fi
done
}
cache_miss()
{
for oldrev in $*; do
if [ ! -r "$cachedir/$oldrev" ]; then
echo $oldrev
fi
done
}
check_parents()
{
missed=$(cache_miss $*)
for miss in $missed; do
if [ ! -r "$cachedir/notree/$miss" ]; then
debug " incorrect order: $miss"
fi
done
}
set_notree()
{
echo "1" > "$cachedir/notree/$1"
}
cache_set()
{
oldrev="$1"
newrev="$2"
if [ "$oldrev" != "latest_old" \
-a "$oldrev" != "latest_new" \
-a -e "$cachedir/$oldrev" ]; then
die "cache for $oldrev already exists!"
fi
echo "$newrev" >"$cachedir/$oldrev"
}
rev_exists()
{
if git rev-parse "$1" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
rev_is_descendant_of_branch()
{
newrev="$1"
branch="$2"
branch_hash=$(git rev-parse $branch)
match=$(git rev-list -1 $branch_hash ^$newrev)
if [ -z "$match" ]; then
return 0
else
return 1
fi
}
# if a commit doesn't have a parent, this might not work. But we only want
# to remove the parent from the rev-list, and since it doesn't exist, it won't
# be there anyway, so do nothing in that case.
try_remove_previous()
{
if rev_exists "$1^"; then
echo "^$1^"
fi
}
find_latest_squash()
{
debug "Looking for latest squash ($dir)..."
dir="$1"
sq=
main=
sub=
git log --grep="^git-subtree-dir: $dir/*\$" \
--pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
while read a b junk; do
debug "$a $b $junk"
debug "{{$sq/$main/$sub}}"
case "$a" in
START) sq="$b" ;;
git-subtree-mainline:) main="$b" ;;
git-subtree-split:) sub="$b" ;;
END)
if [ -n "$sub" ]; then
if [ -n "$main" ]; then
# a rejoin commit?
# Pretend its sub was a squash.
sq="$sub"
fi
debug "Squash found: $sq $sub"
echo "$sq" "$sub"
break
fi
sq=
main=
sub=
;;
esac
done
}
find_existing_splits()
{
debug "Looking for prior splits..."
dir="$1"
revs="$2"
main=
sub=
git log --grep="^git-subtree-dir: $dir/*\$" \
--pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
while read a b junk; do
case "$a" in
START) sq="$b" ;;
git-subtree-mainline:) main="$b" ;;
git-subtree-split:) sub="$b" ;;
END)
debug " Main is: '$main'"
if [ -z "$main" -a -n "$sub" ]; then
# squash commits refer to a subtree
debug " Squash: $sq from $sub"
cache_set "$sq" "$sub"
fi
if [ -n "$main" -a -n "$sub" ]; then
debug " Prior: $main -> $sub"
cache_set $main $sub
cache_set $sub $sub
try_remove_previous "$main"
try_remove_previous "$sub"
fi
main=
sub=
;;
esac
done
}
copy_commit()
{
# We're going to set some environment vars here, so
# do it in a subshell to get rid of them safely later
debug copy_commit "{$1}" "{$2}" "{$3}"
git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
(
read GIT_AUTHOR_NAME
read GIT_AUTHOR_EMAIL
read GIT_AUTHOR_DATE
read GIT_COMMITTER_NAME
read GIT_COMMITTER_EMAIL
read GIT_COMMITTER_DATE
export GIT_AUTHOR_NAME \
GIT_AUTHOR_EMAIL \
GIT_AUTHOR_DATE \
GIT_COMMITTER_NAME \
GIT_COMMITTER_EMAIL \
GIT_COMMITTER_DATE
(echo -n "$annotate"; cat ) |
git commit-tree "$2" $3 # reads the rest of stdin
) || die "Can't copy commit $1"
}
add_msg()
{
dir="$1"
latest_old="$2"
latest_new="$3"
if [ -n "$message" ]; then
commit_message="$message"
else
commit_message="Add '$dir/' from commit '$latest_new'"
fi
cat <<-EOF
$commit_message
git-subtree-dir: $dir
git-subtree-mainline: $latest_old
git-subtree-split: $latest_new
EOF
}
add_squashed_msg()
{
if [ -n "$message" ]; then
echo "$message"
else
echo "Merge commit '$1' as '$2'"
fi
}
rejoin_msg()
{
dir="$1"
latest_old="$2"
latest_new="$3"
if [ -n "$message" ]; then
commit_message="$message"
else
commit_message="Split '$dir/' into commit '$latest_new'"
fi
cat <<-EOF
$commit_message
git-subtree-dir: $dir
git-subtree-mainline: $latest_old
git-subtree-split: $latest_new
EOF
}
squash_msg()
{
dir="$1"
oldsub="$2"
newsub="$3"
newsub_short=$(git rev-parse --short "$newsub")
if [ -n "$oldsub" ]; then
oldsub_short=$(git rev-parse --short "$oldsub")
echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
echo
git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
else
echo "Squashed '$dir/' content from commit $newsub_short"
fi
echo
echo "git-subtree-dir: $dir"
echo "git-subtree-split: $newsub"
}
toptree_for_commit()
{
commit="$1"
git log -1 --pretty=format:'%T' "$commit" -- || exit $?
}
subtree_for_commit()
{
commit="$1"
dir="$2"
git ls-tree "$commit" -- "$dir" |
while read mode type tree name; do
assert [ "$name" = "$dir" ]
assert [ "$type" = "tree" -o "$type" = "commit" ]
[ "$type" = "commit" ] && continue # ignore submodules
echo $tree
break
done
}
tree_changed()
{
tree=$1
shift
if [ $# -ne 1 ]; then
return 0 # weird parents, consider it changed
else
ptree=$(toptree_for_commit $1)
if [ "$ptree" != "$tree" ]; then
return 0 # changed
else
return 1 # not changed
fi
fi
}
new_squash_commit()
{
old="$1"
oldsub="$2"
newsub="$3"
tree=$(toptree_for_commit $newsub) || exit $?
if [ -n "$old" ]; then
squash_msg "$dir" "$oldsub" "$newsub" |
git commit-tree "$tree" -p "$old" || exit $?
else
squash_msg "$dir" "" "$newsub" |
git commit-tree "$tree" || exit $?
fi
}
copy_or_skip()
{
rev="$1"
tree="$2"
newparents="$3"
assert [ -n "$tree" ]
identical=
nonidentical=
p=
gotparents=
for parent in $newparents; do
ptree=$(toptree_for_commit $parent) || exit $?
[ -z "$ptree" ] && continue
if [ "$ptree" = "$tree" ]; then
# an identical parent could be used in place of this rev.
identical="$parent"
else
nonidentical="$parent"
fi
# sometimes both old parents map to the same newparent;
# eliminate duplicates
is_new=1
for gp in $gotparents; do
if [ "$gp" = "$parent" ]; then
is_new=
break
fi
done
if [ -n "$is_new" ]; then
gotparents="$gotparents $parent"
p="$p -p $parent"
fi
done
if [ -n "$identical" ]; then
echo $identical
else
copy_commit $rev $tree "$p" || exit $?
fi
}
ensure_clean()
{
if ! git diff-index HEAD --exit-code --quiet 2>&1; then
die "Working tree has modifications. Cannot add."
fi
if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
die "Index has modifications. Cannot add."
fi
}
cmd_add()
{
if [ -e "$dir" ]; then
die "'$dir' already exists. Cannot add."
fi
ensure_clean
if [ $# -eq 1 ]; then
"cmd_add_commit" "$@"
elif [ $# -eq 2 ]; then
"cmd_add_repository" "$@"
else
say "error: parameters were '$@'"
die "Provide either a refspec or a repository and refspec."
fi
}
cmd_add_repository()
{
echo "git fetch" "$@"
repository=$1
refspec=$2
git fetch "$@" || exit $?
revs=FETCH_HEAD
set -- $revs
cmd_add_commit "$@"
}
cmd_add_commit()
{
revs=$(git rev-parse $default --revs-only "$@") || exit $?
set -- $revs
rev="$1"
debug "Adding $dir as '$rev'..."
git read-tree --prefix="$dir" $rev || exit $?
git checkout -- "$dir" || exit $?
tree=$(git write-tree) || exit $?
headrev=$(git rev-parse HEAD) || exit $?
if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
headp="-p $headrev"
else
headp=
fi
if [ -n "$squash" ]; then
rev=$(new_squash_commit "" "" "$rev") || exit $?
commit=$(add_squashed_msg "$rev" "$dir" |
git commit-tree $tree $headp -p "$rev") || exit $?
else
commit=$(add_msg "$dir" "$headrev" "$rev" |
git commit-tree $tree $headp -p "$rev") || exit $?
fi
git reset "$commit" || exit $?
say "Added dir '$dir'"
}
cmd_split()
{
debug "Splitting $dir..."
cache_setup || exit $?
if [ -n "$onto" ]; then
debug "Reading history for --onto=$onto..."
git rev-list $onto |
while read rev; do
# the 'onto' history is already just the subdir, so
# any parent we find there can be used verbatim
debug " cache: $rev"
cache_set $rev $rev
done
fi
if [ -n "$ignore_joins" ]; then
unrevs=
else
unrevs="$(find_existing_splits "$dir" "$revs")"
fi
# We can't restrict rev-list to only $dir here, because some of our
# parents have the $dir contents the root, and those won't match.
# (and rev-list --follow doesn't seem to solve this)
grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
revmax=$(eval "$grl" | wc -l)
revcount=0
createcount=0
eval "$grl" |
while read rev parents; do
revcount=$(($revcount + 1))
say -n "$revcount/$revmax ($createcount) "
debug "Processing commit: $rev"
exists=$(cache_get $rev)
if [ -n "$exists" ]; then
debug " prior: $exists"
continue
fi
createcount=$(($createcount + 1))
debug " parents: $parents"
newparents=$(cache_get $parents)
debug " newparents: $newparents"
tree=$(subtree_for_commit $rev "$dir")
debug " tree is: $tree"
check_parents $parents
# ugly. is there no better way to tell if this is a subtree
# vs. a mainline commit? Does it matter?
if [ -z $tree ]; then
set_notree $rev
if [ -n "$newparents" ]; then
cache_set $rev $rev
fi
continue
fi
newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
debug " newrev is: $newrev"
cache_set $rev $newrev
cache_set latest_new $newrev
cache_set latest_old $rev
done || exit $?
latest_new=$(cache_get latest_new)
if [ -z "$latest_new" ]; then
die "No new revisions were found"
fi
if [ -n "$rejoin" ]; then
debug "Merging split branch into HEAD..."
latest_old=$(cache_get latest_old)
git merge -s ours \
-m "$(rejoin_msg $dir $latest_old $latest_new)" \
$latest_new >&2 || exit $?
fi
if [ -n "$branch" ]; then
if rev_exists "refs/heads/$branch"; then
if ! rev_is_descendant_of_branch $latest_new $branch; then
die "Branch '$branch' is not an ancestor of commit '$latest_new'."
fi
action='Updated'
else
action='Created'
fi
git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
say "$action branch '$branch'"
fi
echo $latest_new
exit 0
}
cmd_merge()
{
revs=$(git rev-parse $default --revs-only "$@") || exit $?
ensure_clean
set -- $revs
if [ $# -ne 1 ]; then
die "You must provide exactly one revision. Got: '$revs'"
fi
rev="$1"
if [ -n "$squash" ]; then
first_split="$(find_latest_squash "$dir")"
if [ -z "$first_split" ]; then
die "Can't squash-merge: '$dir' was never added."
fi
set $first_split
old=$1
sub=$2
if [ "$sub" = "$rev" ]; then
say "Subtree is already at commit $rev."
exit 0
fi
new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
debug "New squash commit: $new"
rev="$new"
fi
version=$(git version)
if [ "$version" \< "git version 1.7" ]; then
if [ -n "$message" ]; then
git merge -s subtree --message="$message" $rev
else
git merge -s subtree $rev
fi
else
if [ -n "$message" ]; then
git merge -Xsubtree="$prefix" --message="$message" $rev
else
git merge -Xsubtree="$prefix" $rev
fi
fi
}
cmd_pull()
{
ensure_clean
git fetch "$@" || exit $?
revs=FETCH_HEAD
set -- $revs
cmd_merge "$@"
}
cmd_push()
{
if [ $# -ne 2 ]; then
die "You must provide <repository> <refspec>"
fi
if [ -e "$dir" ]; then
repository=$1
refspec=$2
echo "git push using: " $repository $refspec
git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
else
die "'$dir' must already exist. Try 'git subtree add'."
fi
}
"cmd_$command" "$@"

View File

@ -0,0 +1,366 @@
git-subtree(1)
==============
NAME
----
git-subtree - Merge subtrees together and split repository into subtrees
SYNOPSIS
--------
[verse]
'git subtree' add -P <prefix> <commit>
'git subtree' pull -P <prefix> <repository> <refspec...>
'git subtree' push -P <prefix> <repository> <refspec...>
'git subtree' merge -P <prefix> <commit>
'git subtree' split -P <prefix> [OPTIONS] [<commit>]
DESCRIPTION
-----------
Subtrees allow subprojects to be included within a subdirectory
of the main project, optionally including the subproject's
entire history.
For example, you could include the source code for a library
as a subdirectory of your application.
Subtrees are not to be confused with submodules, which are meant for
the same task. Unlike submodules, subtrees do not need any special
constructions (like .gitmodule files or gitlinks) be present in
your repository, and do not force end-users of your
repository to do anything special or to understand how subtrees
work. A subtree is just a subdirectory that can be
committed to, branched, and merged along with your project in
any way you want.
They are also not to be confused with using the subtree merge
strategy. The main difference is that, besides merging
the other project as a subdirectory, you can also extract the
entire history of a subdirectory from your project and make it
into a standalone project. Unlike the subtree merge strategy
you can alternate back and forth between these
two operations. If the standalone library gets updated, you can
automatically merge the changes into your project; if you
update the library inside your project, you can "split" the
changes back out again and merge them back into the library
project.
For example, if a library you made for one application ends up being
useful elsewhere, you can extract its entire history and publish
that as its own git repository, without accidentally
intermingling the history of your application project.
[TIP]
In order to keep your commit messages clean, we recommend that
people split their commits between the subtrees and the main
project as much as possible. That is, if you make a change that
affects both the library and the main application, commit it in
two pieces. That way, when you split the library commits out
later, their descriptions will still make sense. But if this
isn't important to you, it's not *necessary*. git subtree will
simply leave out the non-library-related parts of the commit
when it splits it out into the subproject later.
COMMANDS
--------
add::
Create the <prefix> subtree by importing its contents
from the given <refspec> or <repository> and remote <refspec>.
A new commit is created automatically, joining the imported
project's history with your own. With '--squash', imports
only a single commit from the subproject, rather than its
entire history.
merge::
Merge recent changes up to <commit> into the <prefix>
subtree. As with normal 'git merge', this doesn't
remove your own local changes; it just merges those
changes into the latest <commit>. With '--squash',
creates only one commit that contains all the changes,
rather than merging in the entire history.
If you use '--squash', the merge direction doesn't
always have to be forward; you can use this command to
go back in time from v2.5 to v2.4, for example. If your
merge introduces a conflict, you can resolve it in the
usual ways.
pull::
Exactly like 'merge', but parallels 'git pull' in that
it fetches the given commit from the specified remote
repository.
push::
Does a 'split' (see above) using the <prefix> supplied
and then does a 'git push' to push the result to the
repository and refspec. This can be used to push your
subtree to different branches of the remote repository.
split::
Extract a new, synthetic project history from the
history of the <prefix> subtree. The new history
includes only the commits (including merges) that
affected <prefix>, and each of those commits now has the
contents of <prefix> at the root of the project instead
of in a subdirectory. Thus, the newly created history
is suitable for export as a separate git repository.
After splitting successfully, a single commit id is
printed to stdout. This corresponds to the HEAD of the
newly created tree, which you can manipulate however you
want.
Repeated splits of exactly the same history are
guaranteed to be identical (ie. to produce the same
commit ids). Because of this, if you add new commits
and then re-split, the new commits will be attached as
commits on top of the history you generated last time,
so 'git merge' and friends will work as expected.
Note that if you use '--squash' when you merge, you
should usually not just '--rejoin' when you split.
OPTIONS
-------
-q::
--quiet::
Suppress unnecessary output messages on stderr.
-d::
--debug::
Produce even more unnecessary output messages on stderr.
-P <prefix>::
--prefix=<prefix>::
Specify the path in the repository to the subtree you
want to manipulate. This option is mandatory
for all commands.
-m <message>::
--message=<message>::
This option is only valid for add, merge and pull (unsure).
Specify <message> as the commit message for the merge commit.
OPTIONS FOR add, merge, push, pull
----------------------------------
--squash::
This option is only valid for add, merge, push and pull
commands.
Instead of merging the entire history from the subtree
project, produce only a single commit that contains all
the differences you want to merge, and then merge that
new commit into your project.
Using this option helps to reduce log clutter. People
rarely want to see every change that happened between
v1.0 and v1.1 of the library they're using, since none of the
interim versions were ever included in their application.
Using '--squash' also helps avoid problems when the same
subproject is included multiple times in the same
project, or is removed and then re-added. In such a
case, it doesn't make sense to combine the histories
anyway, since it's unclear which part of the history
belongs to which subtree.
Furthermore, with '--squash', you can switch back and
forth between different versions of a subtree, rather
than strictly forward. 'git subtree merge --squash'
always adjusts the subtree to match the exactly
specified commit, even if getting to that commit would
require undoing some changes that were added earlier.
Whether or not you use '--squash', changes made in your
local repository remain intact and can be later split
and send upstream to the subproject.
OPTIONS FOR split
-----------------
--annotate=<annotation>::
This option is only valid for the split command.
When generating synthetic history, add <annotation> as a
prefix to each commit message. Since we're creating new
commits with the same commit message, but possibly
different content, from the original commits, this can help
to differentiate them and avoid confusion.
Whenever you split, you need to use the same
<annotation>, or else you don't have a guarantee that
the new re-created history will be identical to the old
one. That will prevent merging from working correctly.
git subtree tries to make it work anyway, particularly
if you use --rejoin, but it may not always be effective.
-b <branch>::
--branch=<branch>::
This option is only valid for the split command.
After generating the synthetic history, create a new
branch called <branch> that contains the new history.
This is suitable for immediate pushing upstream.
<branch> must not already exist.
--ignore-joins::
This option is only valid for the split command.
If you use '--rejoin', git subtree attempts to optimize
its history reconstruction to generate only the new
commits since the last '--rejoin'. '--ignore-join'
disables this behaviour, forcing it to regenerate the
entire history. In a large project, this can take a
long time.
--onto=<onto>::
This option is only valid for the split command.
If your subtree was originally imported using something
other than git subtree, its history may not match what
git subtree is expecting. In that case, you can specify
the commit id <onto> that corresponds to the first
revision of the subproject's history that was imported
into your project, and git subtree will attempt to build
its history from there.
If you used 'git subtree add', you should never need
this option.
--rejoin::
This option is only valid for the split command.
After splitting, merge the newly created synthetic
history back into your main project. That way, future
splits can search only the part of history that has
been added since the most recent --rejoin.
If your split commits end up merged into the upstream
subproject, and then you want to get the latest upstream
version, this will allow git's merge algorithm to more
intelligently avoid conflicts (since it knows these
synthetic commits are already part of the upstream
repository).
Unfortunately, using this option results in 'git log'
showing an extra copy of every new commit that was
created (the original, and the synthetic one).
If you do all your merges with '--squash', don't use
'--rejoin' when you split, because you don't want the
subproject's history to be part of your project anyway.
EXAMPLE 1. Add command
----------------------
Let's assume that you have a local repository that you would like
to add an external vendor library to. In this case we will add the
git-subtree repository as a subdirectory of your already existing
git-extensions repository in ~/git-extensions/:
$ git subtree add --prefix=git-subtree --squash \
git://github.com/apenwarr/git-subtree.git master
'master' needs to be a valid remote ref and can be a different branch
name
You can omit the --squash flag, but doing so will increase the number
of commits that are incldued in your local repository.
We now have a ~/git-extensions/git-subtree directory containing code
from the master branch of git://github.com/apenwarr/git-subtree.git
in our git-extensions repository.
EXAMPLE 2. Extract a subtree using commit, merge and pull
---------------------------------------------------------
Let's use the repository for the git source code as an example.
First, get your own copy of the git.git repository:
$ git clone git://git.kernel.org/pub/scm/git/git.git test-git
$ cd test-git
gitweb (commit 1130ef3) was merged into git as of commit
0a8f4f0, after which it was no longer maintained separately.
But imagine it had been maintained separately, and we wanted to
extract git's changes to gitweb since that time, to share with
the upstream. You could do this:
$ git subtree split --prefix=gitweb --annotate='(split) ' \
0a8f4f0^.. --onto=1130ef3 --rejoin \
--branch gitweb-latest
$ gitk gitweb-latest
$ git push git@github.com:whatever/gitweb.git gitweb-latest:master
(We use '0a8f4f0^..' because that means "all the changes from
0a8f4f0 to the current version, including 0a8f4f0 itself.")
If gitweb had originally been merged using 'git subtree add' (or
a previous split had already been done with --rejoin specified)
then you can do all your splits without having to remember any
weird commit ids:
$ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
--branch gitweb-latest2
And you can merge changes back in from the upstream project just
as easily:
$ git subtree pull --prefix=gitweb \
git@github.com:whatever/gitweb.git master
Or, using '--squash', you can actually rewind to an earlier
version of gitweb:
$ git subtree merge --prefix=gitweb --squash gitweb-latest~10
Then make some changes:
$ date >gitweb/myfile
$ git add gitweb/myfile
$ git commit -m 'created myfile'
And fast forward again:
$ git subtree merge --prefix=gitweb --squash gitweb-latest
And notice that your change is still intact:
$ ls -l gitweb/myfile
And you can split it out and look at your changes versus
the standard gitweb:
git log gitweb-latest..$(git subtree split --prefix=gitweb)
EXAMPLE 3. Extract a subtree using branch
-----------------------------------------
Suppose you have a source directory with many files and
subdirectories, and you want to extract the lib directory to its own
git project. Here's a short way to do it:
First, make the new repository wherever you want:
$ <go to the new location>
$ git init --bare
Back in your original directory:
$ git subtree split --prefix=lib --annotate="(split)" -b split
Then push the new branch onto the new empty repository:
$ git push <new-repo> split:master
AUTHOR
------
Written by Avery Pennarun <apenwarr@gmail.com>
GIT
---
Part of the linkgit:git[1] suite

View File

@ -0,0 +1,69 @@
# Run tests
#
# Copyright (c) 2005 Junio C Hamano
#
-include ../../../config.mak.autogen
-include ../../../config.mak
#GIT_TEST_OPTS=--verbose --debug
SHELL_PATH ?= $(SHELL)
PERL_PATH ?= /usr/bin/perl
TAR ?= $(TAR)
RM ?= rm -f
PROVE ?= prove
DEFAULT_TEST_TARGET ?= test
# Shell quote;
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
all: $(DEFAULT_TEST_TARGET)
test: pre-clean $(TEST_LINT)
$(MAKE) aggregate-results-and-cleanup
prove: pre-clean $(TEST_LINT)
@echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
$(MAKE) clean
$(T):
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
pre-clean:
$(RM) -r test-results
clean:
$(RM) -r 'trash directory'.* test-results
$(RM) -r valgrind/bin
$(RM) .prove
test-lint: test-lint-duplicates test-lint-executable
test-lint-duplicates:
@dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
test -z "$$dups" || { \
echo >&2 "duplicate test numbers:" $$dups; exit 1; }
test-lint-executable:
@bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
test -z "$$bad" || { \
echo >&2 "non-executable tests:" $$bad; exit 1; }
aggregate-results-and-cleanup: $(T)
$(MAKE) aggregate-results
$(MAKE) clean
aggregate-results:
for f in ../../../t/test-results/t*-*.counts; do \
echo "$$f"; \
done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh
valgrind:
$(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
test-results:
mkdir -p test-results
.PHONY: pre-clean $(T) aggregate-results clean valgrind

View File

@ -0,0 +1,508 @@
#!/bin/sh
#
# Copyright (c) 2012 Avery Pennaraum
#
test_description='Basic porcelain support for subtrees
This test verifies the basic operation of the merge, pull, add
and split subcommands of git subtree.
'
export TEST_DIRECTORY=$(pwd)/../../../t
. ../../../t/test-lib.sh
create()
{
echo "$1" >"$1"
git add "$1"
}
check_equal()
{
test_debug 'echo'
test_debug "echo \"check a:\" \"{$1}\""
test_debug "echo \" b:\" \"{$2}\""
if [ "$1" = "$2" ]; then
return 0
else
return 1
fi
}
fixnl()
{
t=""
while read x; do
t="$t$x "
done
echo $t
}
multiline()
{
while read x; do
set -- $x
for d in "$@"; do
echo "$d"
done
done
}
undo()
{
git reset --hard HEAD~
}
last_commit_message()
{
git log --pretty=format:%s -1
}
# 1
test_expect_success 'init subproj' '
test_create_repo subproj
'
# To the subproject!
cd subproj
# 2
test_expect_success 'add sub1' '
create sub1 &&
git commit -m "sub1" &&
git branch sub1 &&
git branch -m master subproj
'
# 3
test_expect_success 'add sub2' '
create sub2 &&
git commit -m "sub2" &&
git branch sub2
'
# 4
test_expect_success 'add sub3' '
create sub3 &&
git commit -m "sub3" &&
git branch sub3
'
# Back to mainline
cd ..
# 5
test_expect_success 'add main4' '
create main4 &&
git commit -m "main4" &&
git branch -m master mainline &&
git branch subdir
'
# 6
test_expect_success 'fetch subproj history' '
git fetch ./subproj sub1 &&
git branch sub1 FETCH_HEAD
'
# 7
test_expect_success 'no subtree exists in main tree' '
test_must_fail git subtree merge --prefix=subdir sub1
'
# 8
test_expect_success 'no pull from non-existant subtree' '
test_must_fail git subtree pull --prefix=subdir ./subproj sub1
'
# 9
test_expect_success 'check if --message works for add' '
git subtree add --prefix=subdir --message="Added subproject" sub1 &&
check_equal ''"$(last_commit_message)"'' "Added subproject" &&
undo
'
# 10
test_expect_success 'check if --message works as -m and --prefix as -P' '
git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
undo
'
# 11
test_expect_success 'check if --message works with squash too' '
git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
undo
'
# 12
test_expect_success 'add subproj to mainline' '
git subtree add --prefix=subdir/ FETCH_HEAD &&
check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
'
# 13
# this shouldn't actually do anything, since FETCH_HEAD is already a parent
test_expect_success 'merge fetched subproj' '
git merge -m "merge -s -ours" -s ours FETCH_HEAD
'
# 14
test_expect_success 'add main-sub5' '
create subdir/main-sub5 &&
git commit -m "main-sub5"
'
# 15
test_expect_success 'add main6' '
create main6 &&
git commit -m "main6 boring"
'
# 16
test_expect_success 'add main-sub7' '
create subdir/main-sub7 &&
git commit -m "main-sub7"
'
# 17
test_expect_success 'fetch new subproj history' '
git fetch ./subproj sub2 &&
git branch sub2 FETCH_HEAD
'
# 18
test_expect_success 'check if --message works for merge' '
git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
undo
'
# 19
test_expect_success 'check if --message for merge works with squash too' '
git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
undo
'
# 20
test_expect_success 'merge new subproj history into subdir' '
git subtree merge --prefix=subdir FETCH_HEAD &&
git branch pre-split &&
check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline"
'
# 21
test_expect_success 'Check that prefix argument is required for split' '
echo "You must provide the --prefix option." > expected &&
test_must_fail git subtree split > actual 2>&1 &&
test_debug "echo -n expected: " &&
test_debug "cat expected" &&
test_debug "echo -n actual: " &&
test_debug "cat actual" &&
test_cmp expected actual &&
rm -f expected actual
'
# 22
test_expect_success 'Check that the <prefix> exists for a split' '
echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected &&
test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 &&
test_debug "echo -n expected: " &&
test_debug "cat expected" &&
test_debug "echo -n actual: " &&
test_debug "cat actual" &&
test_cmp expected actual
# rm -f expected actual
'
# 23
test_expect_success 'check if --message works for split+rejoin' '
spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
git branch spl1 "$spl1" &&
check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
undo
'
# 24
test_expect_success 'check split with --branch' '
spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
undo &&
git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
'
# 25
test_expect_success 'check split with --branch for an existing branch' '
spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
undo &&
git branch splitbr2 sub1 &&
git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
'
# 26
test_expect_success 'check split with --branch for an incompatible branch' '
test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
'
# 27
test_expect_success 'check split+rejoin' '
spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
undo &&
git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
'
# 28
test_expect_success 'add main-sub8' '
create subdir/main-sub8 &&
git commit -m "main-sub8"
'
# To the subproject!
cd ./subproj
# 29
test_expect_success 'merge split into subproj' '
git fetch .. spl1 &&
git branch spl1 FETCH_HEAD &&
git merge FETCH_HEAD
'
# 30
test_expect_success 'add sub9' '
create sub9 &&
git commit -m "sub9"
'
# Back to mainline
cd ..
# 31
test_expect_success 'split for sub8' '
split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"''
git branch split2 "$split2"
'
# 32
test_expect_success 'add main-sub10' '
create subdir/main-sub10 &&
git commit -m "main-sub10"
'
# 33
test_expect_success 'split for sub10' '
spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
git branch spl3 "$spl3"
'
# To the subproject!
cd ./subproj
# 34
test_expect_success 'merge split into subproj' '
git fetch .. spl3 &&
git branch spl3 FETCH_HEAD &&
git merge FETCH_HEAD &&
git branch subproj-merge-spl3
'
chkm="main4 main6"
chkms="main-sub10 main-sub5 main-sub7 main-sub8"
chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
chks="sub1 sub2 sub3 sub9"
chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
# 35
test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
subfiles=''"$(git ls-files | fixnl)"'' &&
check_equal "$subfiles" "$chkms $chks"
'
# 36
test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
check_equal "$allchanges" "$chkms $chks"
'
# Back to mainline
cd ..
# 37
test_expect_success 'pull from subproj' '
git fetch ./subproj subproj-merge-spl3 &&
git branch subproj-merge-spl3 FETCH_HEAD &&
git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
'
# 38
test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
mainfiles=''"$(git ls-files | fixnl)"'' &&
check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
'
# 39
test_expect_success 'make sure each filename changed exactly once in the entire history' '
# main-sub?? and /subdir/main-sub?? both change, because those are the
# changes that were split into their own history. And subdir/sub?? never
# change, since they were *only* changed in the subtree branch.
allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
'
# 40
test_expect_success 'make sure the --rejoin commits never make it into subproj' '
check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' ""
'
# 41
test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
# They are meaningless to subproj since one side of the merge refers to the mainline
check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' ""
'
# prepare second pair of repositories
mkdir test2
cd test2
# 42
test_expect_success 'init main' '
test_create_repo main
'
cd main
# 43
test_expect_success 'add main1' '
create main1 &&
git commit -m "main1"
'
cd ..
# 44
test_expect_success 'init sub' '
test_create_repo sub
'
cd sub
# 45
test_expect_success 'add sub2' '
create sub2 &&
git commit -m "sub2"
'
cd ../main
# check if split can find proper base without --onto
# 46
test_expect_success 'add sub as subdir in main' '
git fetch ../sub master &&
git branch sub2 FETCH_HEAD &&
git subtree add --prefix subdir sub2
'
cd ../sub
# 47
test_expect_success 'add sub3' '
create sub3 &&
git commit -m "sub3"
'
cd ../main
# 48
test_expect_success 'merge from sub' '
git fetch ../sub master &&
git branch sub3 FETCH_HEAD &&
git subtree merge --prefix subdir sub3
'
# 49
test_expect_success 'add main-sub4' '
create subdir/main-sub4 &&
git commit -m "main-sub4"
'
# 50
test_expect_success 'split for main-sub4 without --onto' '
git subtree split --prefix subdir --branch mainsub4
'
# at this point, the new commit parent should be sub3 if it is not,
# something went wrong (the "newparent" of "master~" commit should
# have been sub3, but it was not, because its cache was not set to
# itself)
# 51
test_expect_success 'check that the commit parent is sub3' '
check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"''
'
# 52
test_expect_success 'add main-sub5' '
mkdir subdir2 &&
create subdir2/main-sub5 &&
git commit -m "main-sub5"
'
# 53
test_expect_success 'split for main-sub5 without --onto' '
# also test that we still can split out an entirely new subtree
# if the parent of the first commit in the tree is not empty,
# then the new subtree has accidently been attached to something
git subtree split --prefix subdir2 --branch mainsub5 &&
check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' ""
'
# make sure no patch changes more than one file. The original set of commits
# changed only one file each. A multi-file change would imply that we pruned
# commits too aggressively.
joincommits()
{
commit=
all=
while read x y; do
#echo "{$x}" >&2
if [ -z "$x" ]; then
continue
elif [ "$x" = "commit:" ]; then
if [ -n "$commit" ]; then
echo "$commit $all"
all=
fi
commit="$y"
else
all="$all $y"
fi
done
echo "$commit $all"
}
# 54
test_expect_success 'verify one file change per commit' '
x= &&
list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' &&
# test_debug "echo HERE" &&
# test_debug "echo ''"$list"''" &&
(git log --pretty=format:'"'commit: %H'"' | joincommits |
( while read commit a b; do
test_debug "echo Verifying commit "''"$commit"''
test_debug "echo a: "''"$a"''
test_debug "echo b: "''"$b"''
check_equal "$b" ""
x=1
done
check_equal "$x" 1
))
'
test_done

50
contrib/subtree/todo Normal file
View File

@ -0,0 +1,50 @@
delete tempdir
'git subtree rejoin' option to do the same as --rejoin, eg. after a
rebase
--prefix doesn't force the subtree correctly in merge/pull:
"-s subtree" should be given an explicit subtree option?
There doesn't seem to be a way to do this. We'd have to
patch git-merge-subtree. Ugh.
(but we could avoid this problem by generating squashes with
exactly the right subtree structure, rather than using
subtree merge...)
add a 'push' subcommand to parallel 'pull'
add a 'log' subcommand to see what's new in a subtree?
add to-submodule and from-submodule commands
automated tests for --squash stuff
"add" command non-obviously requires a commitid; would be easier if
it had a "pull" sort of mode instead
"pull" and "merge" commands should fail if you've never merged
that --prefix before
docs should provide an example of "add"
note that the initial split doesn't *have* to have a commitid
specified... that's just an optimization
if you try to add (or maybe merge?) with an invalid commitid, you
get a misleading "prefix must end with /" message from
one of the other git tools that git-subtree calls. Should
detect this situation and print the *real* problem.
"pull --squash" should do fetch-synthesize-merge, but instead just
does "pull" directly, which doesn't work at all.
make a 'force-update' that does what 'add' does even if the subtree
already exists. That way we can help people who imported
subtrees "incorrectly" (eg. by just copying in the files) in
the past.
guess --prefix automatically if possible based on pwd
make a 'git subtree grafts' that automatically expands --squash'd
commits so you can see the full history if you want it.

View File

@ -52,7 +52,7 @@ static int get_mode(const char *path, int *mode)
} }
static int queue_diff(struct diff_options *o, static int queue_diff(struct diff_options *o,
const char *name1, const char *name2) const char *name1, const char *name2)
{ {
int mode1 = 0, mode2 = 0; int mode1 = 0, mode2 = 0;
@ -63,10 +63,11 @@ static int queue_diff(struct diff_options *o,
return error("file/directory conflict: %s, %s", name1, name2); return error("file/directory conflict: %s, %s", name1, name2);
if (S_ISDIR(mode1) || S_ISDIR(mode2)) { if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
char buffer1[PATH_MAX], buffer2[PATH_MAX]; struct strbuf buffer1 = STRBUF_INIT;
struct strbuf buffer2 = STRBUF_INIT;
struct string_list p1 = STRING_LIST_INIT_DUP; struct string_list p1 = STRING_LIST_INIT_DUP;
struct string_list p2 = STRING_LIST_INIT_DUP; struct string_list p2 = STRING_LIST_INIT_DUP;
int len1 = 0, len2 = 0, i1, i2, ret = 0; int i1, i2, ret = 0;
if (name1 && read_directory(name1, &p1)) if (name1 && read_directory(name1, &p1))
return -1; return -1;
@ -76,19 +77,15 @@ static int queue_diff(struct diff_options *o,
} }
if (name1) { if (name1) {
len1 = strlen(name1); strbuf_addstr(&buffer1, name1);
if (len1 > 0 && name1[len1 - 1] == '/') if (buffer1.len && buffer1.buf[buffer1.len - 1] != '/')
len1--; strbuf_addch(&buffer1, '/');
memcpy(buffer1, name1, len1);
buffer1[len1++] = '/';
} }
if (name2) { if (name2) {
len2 = strlen(name2); strbuf_addstr(&buffer2, name2);
if (len2 > 0 && name2[len2 - 1] == '/') if (buffer2.len && buffer2.buf[buffer2.len - 1] != '/')
len2--; strbuf_addch(&buffer2, '/');
memcpy(buffer2, name2, len2);
buffer2[len2++] = '/';
} }
for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) { for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
@ -100,29 +97,28 @@ static int queue_diff(struct diff_options *o,
else if (i2 == p2.nr) else if (i2 == p2.nr)
comp = -1; comp = -1;
else else
comp = strcmp(p1.items[i1].string, comp = strcmp(p1.items[i1].string, p2.items[i2].string);
p2.items[i2].string);
if (comp > 0) if (comp > 0)
n1 = NULL; n1 = NULL;
else { else {
n1 = buffer1; strbuf_addstr(&buffer1, p1.items[i1++].string);
strncpy(buffer1 + len1, p1.items[i1++].string, n1 = buffer1.buf;
PATH_MAX - len1);
} }
if (comp < 0) if (comp < 0)
n2 = NULL; n2 = NULL;
else { else {
n2 = buffer2; strbuf_addstr(&buffer2, p2.items[i2++].string);
strncpy(buffer2 + len2, p2.items[i2++].string, n2 = buffer2.buf;
PATH_MAX - len2);
} }
ret = queue_diff(o, n1, n2); ret = queue_diff(o, n1, n2);
} }
string_list_clear(&p1, 0); string_list_clear(&p1, 0);
string_list_clear(&p2, 0); string_list_clear(&p2, 0);
strbuf_reset(&buffer1);
strbuf_reset(&buffer2);
return ret; return ret;
} else { } else {

137
diff.c
View File

@ -989,10 +989,74 @@ static void diff_words_flush(struct emit_callback *ecbdata)
diff_words_show(ecbdata->diff_words); diff_words_show(ecbdata->diff_words);
} }
static void diff_filespec_load_driver(struct diff_filespec *one)
{
/* Use already-loaded driver */
if (one->driver)
return;
if (S_ISREG(one->mode))
one->driver = userdiff_find_by_path(one->path);
/* Fallback to default settings */
if (!one->driver)
one->driver = userdiff_find_by_name("default");
}
static const char *userdiff_word_regex(struct diff_filespec *one)
{
diff_filespec_load_driver(one);
return one->driver->word_regex;
}
static void init_diff_words_data(struct emit_callback *ecbdata,
struct diff_options *orig_opts,
struct diff_filespec *one,
struct diff_filespec *two)
{
int i;
struct diff_options *o = xmalloc(sizeof(struct diff_options));
memcpy(o, orig_opts, sizeof(struct diff_options));
ecbdata->diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata->diff_words->type = o->word_diff;
ecbdata->diff_words->opt = o;
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(two);
if (!o->word_regex)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
ecbdata->diff_words->word_regex = (regex_t *)
xmalloc(sizeof(regex_t));
if (regcomp(ecbdata->diff_words->word_regex,
o->word_regex,
REG_EXTENDED | REG_NEWLINE))
die ("Invalid regular expression: %s",
o->word_regex);
}
for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
if (o->word_diff == diff_words_styles[i].type) {
ecbdata->diff_words->style =
&diff_words_styles[i];
break;
}
}
if (want_color(o->use_color)) {
struct diff_words_style *st = ecbdata->diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
}
}
static void free_diff_words_data(struct emit_callback *ecbdata) static void free_diff_words_data(struct emit_callback *ecbdata)
{ {
if (ecbdata->diff_words) { if (ecbdata->diff_words) {
diff_words_flush(ecbdata); diff_words_flush(ecbdata);
free (ecbdata->diff_words->opt);
free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr); free (ecbdata->diff_words->plus.text.ptr);
@ -2061,20 +2125,6 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *pre
emit_binary_diff_body(file, two, one, prefix); emit_binary_diff_body(file, two, one, prefix);
} }
static void diff_filespec_load_driver(struct diff_filespec *one)
{
/* Use already-loaded driver */
if (one->driver)
return;
if (S_ISREG(one->mode))
one->driver = userdiff_find_by_path(one->path);
/* Fallback to default settings */
if (!one->driver)
one->driver = userdiff_find_by_name("default");
}
int diff_filespec_is_binary(struct diff_filespec *one) int diff_filespec_is_binary(struct diff_filespec *one)
{ {
if (one->is_binary == -1) { if (one->is_binary == -1) {
@ -2100,12 +2150,6 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
return one->driver->funcname.pattern ? &one->driver->funcname : NULL; return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
} }
static const char *userdiff_word_regex(struct diff_filespec *one)
{
diff_filespec_load_driver(one);
return one->driver->word_regex;
}
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b) void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{ {
if (!options->a_prefix) if (!options->a_prefix)
@ -2292,42 +2336,8 @@ static void builtin_diff(const char *name_a,
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
else if (!prefixcmp(diffopts, "-u")) else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
if (o->word_diff) { if (o->word_diff)
int i; init_diff_words_data(&ecbdata, o, one, two);
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata.diff_words->type = o->word_diff;
ecbdata.diff_words->opt = o;
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(two);
if (!o->word_regex)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
ecbdata.diff_words->word_regex = (regex_t *)
xmalloc(sizeof(regex_t));
if (regcomp(ecbdata.diff_words->word_regex,
o->word_regex,
REG_EXTENDED | REG_NEWLINE))
die ("Invalid regular expression: %s",
o->word_regex);
}
for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
if (o->word_diff == diff_words_styles[i].type) {
ecbdata.diff_words->style =
&diff_words_styles[i];
break;
}
}
if (want_color(o->use_color)) {
struct diff_words_style *st = ecbdata.diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
}
}
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
&xpp, &xecfg); &xpp, &xecfg);
if (o->word_diff) if (o->word_diff)
@ -3136,6 +3146,7 @@ void diff_setup(struct diff_options *options)
options->rename_limit = -1; options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default; options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3; options->context = 3;
DIFF_OPT_SET(options, RENAME_EMPTY);
options->change = diff_change; options->change = diff_change;
options->add_remove = diff_addremove; options->add_remove = diff_addremove;
@ -3506,6 +3517,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
} }
else if (!strcmp(arg, "--no-renames")) else if (!strcmp(arg, "--no-renames"))
options->detect_rename = 0; options->detect_rename = 0;
else if (!strcmp(arg, "--rename-empty"))
DIFF_OPT_SET(options, RENAME_EMPTY);
else if (!strcmp(arg, "--no-rename-empty"))
DIFF_OPT_CLR(options, RENAME_EMPTY);
else if (!strcmp(arg, "--relative")) else if (!strcmp(arg, "--relative"))
DIFF_OPT_SET(options, RELATIVE_NAME); DIFF_OPT_SET(options, RELATIVE_NAME);
else if (!prefixcmp(arg, "--relative=")) { else if (!prefixcmp(arg, "--relative=")) {
@ -3525,9 +3540,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--ignore-space-at-eol")) else if (!strcmp(arg, "--ignore-space-at-eol"))
DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
else if (!strcmp(arg, "--patience")) else if (!strcmp(arg, "--patience"))
DIFF_XDL_SET(options, PATIENCE_DIFF); options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
else if (!strcmp(arg, "--histogram")) else if (!strcmp(arg, "--histogram"))
DIFF_XDL_SET(options, HISTOGRAM_DIFF); options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
/* flags options */ /* flags options */
else if (!strcmp(arg, "--binary")) { else if (!strcmp(arg, "--binary")) {
@ -4399,6 +4414,12 @@ void diff_flush(struct diff_options *options)
if (output_format & DIFF_FORMAT_PATCH) { if (output_format & DIFF_FORMAT_PATCH) {
if (separator) { if (separator) {
if (options->output_prefix) {
struct strbuf *msg = NULL;
msg = options->output_prefix(options,
options->output_prefix_data);
fwrite(msg->buf, msg->len, 1, stdout);
}
putc(options->line_termination, options->file); putc(options->line_termination, options->file);
if (options->stat_sep) { if (options->stat_sep) {
/* attach patch instead of inline */ /* attach patch instead of inline */

4
diff.h
View File

@ -60,7 +60,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_OPT_SILENT_ON_REMOVE (1 << 5) #define DIFF_OPT_SILENT_ON_REMOVE (1 << 5)
#define DIFF_OPT_FIND_COPIES_HARDER (1 << 6) #define DIFF_OPT_FIND_COPIES_HARDER (1 << 6)
#define DIFF_OPT_FOLLOW_RENAMES (1 << 7) #define DIFF_OPT_FOLLOW_RENAMES (1 << 7)
/* (1 << 8) unused */ #define DIFF_OPT_RENAME_EMPTY (1 << 8)
/* (1 << 9) unused */ /* (1 << 9) unused */
#define DIFF_OPT_HAS_CHANGES (1 << 10) #define DIFF_OPT_HAS_CHANGES (1 << 10)
#define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_QUICK (1 << 11)
@ -91,6 +91,8 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag) #define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag)
#define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag) #define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag)
#define DIFF_WITH_ALG(opts, flag) (((opts)->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | XDF_##flag)
enum diff_words_type { enum diff_words_type {
DIFF_WORDS_NONE = 0, DIFF_WORDS_NONE = 0,
DIFF_WORDS_PORCELAIN, DIFF_WORDS_PORCELAIN,

View File

@ -512,9 +512,15 @@ void diffcore_rename(struct diff_options *options)
else if (options->single_follow && else if (options->single_follow &&
strcmp(options->single_follow, p->two->path)) strcmp(options->single_follow, p->two->path))
continue; /* not interested */ continue; /* not interested */
else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
is_empty_blob_sha1(p->two->sha1))
continue;
else else
locate_rename_dst(p->two, 1); locate_rename_dst(p->two, 1);
} }
else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
is_empty_blob_sha1(p->one->sha1))
continue;
else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) { else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) {
/* /*
* If the source is a broken "delete", and * If the source is a broken "delete", and

35
dir.c
View File

@ -1172,22 +1172,32 @@ int is_empty_dir(const char *path)
return ret; return ret;
} }
int remove_dir_recursively(struct strbuf *path, int flag) static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
{ {
DIR *dir; DIR *dir;
struct dirent *e; struct dirent *e;
int ret = 0, original_len = path->len, len; int ret = 0, original_len = path->len, len, kept_down = 0;
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY); int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
unsigned char submodule_head[20]; unsigned char submodule_head[20];
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) && if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
/* Do not descend and nuke a nested git work tree. */ /* Do not descend and nuke a nested git work tree. */
if (kept_up)
*kept_up = 1;
return 0; return 0;
}
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
dir = opendir(path->buf); dir = opendir(path->buf);
if (!dir) if (!dir) {
return rmdir(path->buf); /* an empty dir could be removed even if it is unreadble */
if (!keep_toplevel)
return rmdir(path->buf);
else
return -1;
}
if (path->buf[original_len - 1] != '/') if (path->buf[original_len - 1] != '/')
strbuf_addch(path, '/'); strbuf_addch(path, '/');
@ -1202,7 +1212,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
if (lstat(path->buf, &st)) if (lstat(path->buf, &st))
; /* fall thru */ ; /* fall thru */
else if (S_ISDIR(st.st_mode)) { else if (S_ISDIR(st.st_mode)) {
if (!remove_dir_recursively(path, only_empty)) if (!remove_dir_recurse(path, flag, &kept_down))
continue; /* happy */ continue; /* happy */
} else if (!only_empty && !unlink(path->buf)) } else if (!only_empty && !unlink(path->buf))
continue; /* happy, too */ continue; /* happy, too */
@ -1214,11 +1224,22 @@ int remove_dir_recursively(struct strbuf *path, int flag)
closedir(dir); closedir(dir);
strbuf_setlen(path, original_len); strbuf_setlen(path, original_len);
if (!ret) if (!ret && !keep_toplevel && !kept_down)
ret = rmdir(path->buf); ret = rmdir(path->buf);
else if (kept_up)
/*
* report the uplevel that it is not an error that we
* did not rmdir() our directory.
*/
*kept_up = !ret;
return ret; return ret;
} }
int remove_dir_recursively(struct strbuf *path, int flag)
{
return remove_dir_recurse(path, flag, NULL);
}
void setup_standard_excludes(struct dir_struct *dir) void setup_standard_excludes(struct dir_struct *dir)
{ {
const char *path; const char *path;

1
dir.h
View File

@ -102,6 +102,7 @@ extern void setup_standard_excludes(struct dir_struct *dir);
#define REMOVE_DIR_EMPTY_ONLY 01 #define REMOVE_DIR_EMPTY_ONLY 01
#define REMOVE_DIR_KEEP_NESTED_GIT 02 #define REMOVE_DIR_KEEP_NESTED_GIT 02
#define REMOVE_DIR_KEEP_TOPLEVEL 04
extern int remove_dir_recursively(struct strbuf *path, int flag); extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */ /* tries to remove the path with empty directories along it, ignores ENOENT */

53
entry.c
View File

@ -120,58 +120,15 @@ static int streaming_write_entry(struct cache_entry *ce, char *path,
const struct checkout *state, int to_tempfile, const struct checkout *state, int to_tempfile,
int *fstat_done, struct stat *statbuf) int *fstat_done, struct stat *statbuf)
{ {
struct git_istream *st;
enum object_type type;
unsigned long sz;
int result = -1; int result = -1;
ssize_t kept = 0; int fd;
int fd = -1;
st = open_istream(ce->sha1, &type, &sz, filter);
if (!st)
return -1;
if (type != OBJ_BLOB)
goto close_and_exit;
fd = open_output_fd(path, ce, to_tempfile); fd = open_output_fd(path, ce, to_tempfile);
if (fd < 0) if (0 <= fd) {
goto close_and_exit; result = stream_blob_to_fd(fd, ce->sha1, filter, 1);
*fstat_done = fstat_output(fd, state, statbuf);
for (;;) {
char buf[1024 * 16];
ssize_t wrote, holeto;
ssize_t readlen = read_istream(st, buf, sizeof(buf));
if (!readlen)
break;
if (sizeof(buf) == readlen) {
for (holeto = 0; holeto < readlen; holeto++)
if (buf[holeto])
break;
if (readlen == holeto) {
kept += holeto;
continue;
}
}
if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
goto close_and_exit;
else
kept = 0;
wrote = write_in_full(fd, buf, readlen);
if (wrote != readlen)
goto close_and_exit;
}
if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
write(fd, "", 1) != 1))
goto close_and_exit;
*fstat_done = fstat_output(fd, state, statbuf);
close_and_exit:
close_istream(st);
if (0 <= fd)
result = close(fd); result = close(fd);
}
if (result && 0 <= fd) if (result && 0 <= fd)
unlink(path); unlink(path);
return result; return result;

View File

@ -52,7 +52,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
enum rebase_setup_type autorebase = AUTOREBASE_NEVER; enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
enum push_default_type push_default = PUSH_DEFAULT_MATCHING; enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
#ifndef OBJECT_CREATION_MODE #ifndef OBJECT_CREATION_MODE
#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
#endif #endif

View File

@ -134,7 +134,7 @@ int execv_git_cmd(const char **argv) {
trace_argv_printf(nargv, "trace: exec:"); trace_argv_printf(nargv, "trace: exec:");
/* execvp() can only ever return if it fails */ /* execvp() can only ever return if it fails */
execvp("git", (char **)nargv); sane_execvp("git", (char **)nargv);
trace_printf("trace: exec failed: %s\n", strerror(errno)); trace_printf("trace: exec failed: %s\n", strerror(errno));

View File

@ -2207,6 +2207,59 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout); return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
} }
/*
* Given a pointer into a string, parse a mark reference:
*
* idnum ::= ':' bigint;
*
* Return the first character after the value in *endptr.
*
* Complain if the following character is not what is expected,
* either a space or end of the string.
*/
static uintmax_t parse_mark_ref(const char *p, char **endptr)
{
uintmax_t mark;
assert(*p == ':');
p++;
mark = strtoumax(p, endptr, 10);
if (*endptr == p)
die("No value after ':' in mark: %s", command_buf.buf);
return mark;
}
/*
* Parse the mark reference, and complain if this is not the end of
* the string.
*/
static uintmax_t parse_mark_ref_eol(const char *p)
{
char *end;
uintmax_t mark;
mark = parse_mark_ref(p, &end);
if (*end != '\0')
die("Garbage after mark: %s", command_buf.buf);
return mark;
}
/*
* Parse the mark reference, demanding a trailing space. Return a
* pointer to the space.
*/
static uintmax_t parse_mark_ref_space(const char **p)
{
uintmax_t mark;
char *end;
mark = parse_mark_ref(*p, &end);
if (*end != ' ')
die("Missing space after mark: %s", command_buf.buf);
*p = end;
return mark;
}
static void file_change_m(struct branch *b) static void file_change_m(struct branch *b)
{ {
const char *p = command_buf.buf + 2; const char *p = command_buf.buf + 2;
@ -2235,21 +2288,21 @@ static void file_change_m(struct branch *b)
} }
if (*p == ':') { if (*p == ':') {
char *x; oe = find_mark(parse_mark_ref_space(&p));
oe = find_mark(strtoumax(p + 1, &x, 10));
hashcpy(sha1, oe->idx.sha1); hashcpy(sha1, oe->idx.sha1);
p = x; } else if (!prefixcmp(p, "inline ")) {
} else if (!prefixcmp(p, "inline")) {
inline_data = 1; inline_data = 1;
p += 6; p += strlen("inline"); /* advance to space */
} else { } else {
if (get_sha1_hex(p, sha1)) if (get_sha1_hex(p, sha1))
die("Invalid SHA1: %s", command_buf.buf); die("Invalid dataref: %s", command_buf.buf);
oe = find_object(sha1); oe = find_object(sha1);
p += 40; p += 40;
if (*p != ' ')
die("Missing space after SHA1: %s", command_buf.buf);
} }
if (*p++ != ' ') assert(*p == ' ');
die("Missing space after SHA1: %s", command_buf.buf); p++; /* skip space */
strbuf_reset(&uq); strbuf_reset(&uq);
if (!unquote_c_style(&uq, p, &endp)) { if (!unquote_c_style(&uq, p, &endp)) {
@ -2407,21 +2460,21 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
/* Now parse the notemodify command. */ /* Now parse the notemodify command. */
/* <dataref> or 'inline' */ /* <dataref> or 'inline' */
if (*p == ':') { if (*p == ':') {
char *x; oe = find_mark(parse_mark_ref_space(&p));
oe = find_mark(strtoumax(p + 1, &x, 10));
hashcpy(sha1, oe->idx.sha1); hashcpy(sha1, oe->idx.sha1);
p = x; } else if (!prefixcmp(p, "inline ")) {
} else if (!prefixcmp(p, "inline")) {
inline_data = 1; inline_data = 1;
p += 6; p += strlen("inline"); /* advance to space */
} else { } else {
if (get_sha1_hex(p, sha1)) if (get_sha1_hex(p, sha1))
die("Invalid SHA1: %s", command_buf.buf); die("Invalid dataref: %s", command_buf.buf);
oe = find_object(sha1); oe = find_object(sha1);
p += 40; p += 40;
if (*p != ' ')
die("Missing space after SHA1: %s", command_buf.buf);
} }
if (*p++ != ' ') assert(*p == ' ');
die("Missing space after SHA1: %s", command_buf.buf); p++; /* skip space */
/* <committish> */ /* <committish> */
s = lookup_branch(p); s = lookup_branch(p);
@ -2430,7 +2483,7 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout)
die("Can't add a note on empty branch."); die("Can't add a note on empty branch.");
hashcpy(commit_sha1, s->sha1); hashcpy(commit_sha1, s->sha1);
} else if (*p == ':') { } else if (*p == ':') {
uintmax_t commit_mark = strtoumax(p + 1, NULL, 10); uintmax_t commit_mark = parse_mark_ref_eol(p);
struct object_entry *commit_oe = find_mark(commit_mark); struct object_entry *commit_oe = find_mark(commit_mark);
if (commit_oe->type != OBJ_COMMIT) if (commit_oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", commit_mark); die("Mark :%" PRIuMAX " not a commit", commit_mark);
@ -2537,7 +2590,7 @@ static int parse_from(struct branch *b)
hashcpy(b->branch_tree.versions[0].sha1, t); hashcpy(b->branch_tree.versions[0].sha1, t);
hashcpy(b->branch_tree.versions[1].sha1, t); hashcpy(b->branch_tree.versions[1].sha1, t);
} else if (*from == ':') { } else if (*from == ':') {
uintmax_t idnum = strtoumax(from + 1, NULL, 10); uintmax_t idnum = parse_mark_ref_eol(from);
struct object_entry *oe = find_mark(idnum); struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT) if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum); die("Mark :%" PRIuMAX " not a commit", idnum);
@ -2572,7 +2625,7 @@ static struct hash_list *parse_merge(unsigned int *count)
if (s) if (s)
hashcpy(n->sha1, s->sha1); hashcpy(n->sha1, s->sha1);
else if (*from == ':') { else if (*from == ':') {
uintmax_t idnum = strtoumax(from + 1, NULL, 10); uintmax_t idnum = parse_mark_ref_eol(from);
struct object_entry *oe = find_mark(idnum); struct object_entry *oe = find_mark(idnum);
if (oe->type != OBJ_COMMIT) if (oe->type != OBJ_COMMIT)
die("Mark :%" PRIuMAX " not a commit", idnum); die("Mark :%" PRIuMAX " not a commit", idnum);
@ -2735,7 +2788,7 @@ static void parse_new_tag(void)
type = OBJ_COMMIT; type = OBJ_COMMIT;
} else if (*from == ':') { } else if (*from == ':') {
struct object_entry *oe; struct object_entry *oe;
from_mark = strtoumax(from + 1, NULL, 10); from_mark = parse_mark_ref_eol(from);
oe = find_mark(from_mark); oe = find_mark(from_mark);
type = oe->type; type = oe->type;
hashcpy(sha1, oe->idx.sha1); hashcpy(sha1, oe->idx.sha1);
@ -2867,18 +2920,13 @@ static void parse_cat_blob(void)
/* cat-blob SP <object> LF */ /* cat-blob SP <object> LF */
p = command_buf.buf + strlen("cat-blob "); p = command_buf.buf + strlen("cat-blob ");
if (*p == ':') { if (*p == ':') {
char *x; oe = find_mark(parse_mark_ref_eol(p));
oe = find_mark(strtoumax(p + 1, &x, 10));
if (x == p + 1)
die("Invalid mark: %s", command_buf.buf);
if (!oe) if (!oe)
die("Unknown mark: %s", command_buf.buf); die("Unknown mark: %s", command_buf.buf);
if (*x)
die("Garbage after mark: %s", command_buf.buf);
hashcpy(sha1, oe->idx.sha1); hashcpy(sha1, oe->idx.sha1);
} else { } else {
if (get_sha1_hex(p, sha1)) if (get_sha1_hex(p, sha1))
die("Invalid SHA1: %s", command_buf.buf); die("Invalid dataref: %s", command_buf.buf);
if (p[40]) if (p[40])
die("Garbage after SHA1: %s", command_buf.buf); die("Garbage after SHA1: %s", command_buf.buf);
oe = find_object(sha1); oe = find_object(sha1);
@ -2944,17 +2992,13 @@ static struct object_entry *parse_treeish_dataref(const char **p)
struct object_entry *e; struct object_entry *e;
if (**p == ':') { /* <mark> */ if (**p == ':') { /* <mark> */
char *endptr; e = find_mark(parse_mark_ref_space(p));
e = find_mark(strtoumax(*p + 1, &endptr, 10));
if (endptr == *p + 1)
die("Invalid mark: %s", command_buf.buf);
if (!e) if (!e)
die("Unknown mark: %s", command_buf.buf); die("Unknown mark: %s", command_buf.buf);
*p = endptr;
hashcpy(sha1, e->idx.sha1); hashcpy(sha1, e->idx.sha1);
} else { /* <sha1> */ } else { /* <sha1> */
if (get_sha1_hex(*p, sha1)) if (get_sha1_hex(*p, sha1))
die("Invalid SHA1: %s", command_buf.buf); die("Invalid dataref: %s", command_buf.buf);
e = find_object(sha1); e = find_object(sha1);
*p += 40; *p += 40;
} }

View File

@ -10,6 +10,7 @@ struct fetch_pack_args {
lock_pack:1, lock_pack:1,
use_thin_pack:1, use_thin_pack:1,
fetch_all:1, fetch_all:1,
stdin_refs:1,
verbose:1, verbose:1,
no_progress:1, no_progress:1,
include_tag:1, include_tag:1,

View File

@ -268,6 +268,7 @@ sub get_empty_tree {
# FILE: is file different from index? # FILE: is file different from index?
# INDEX_ADDDEL: is it add/delete between HEAD and index? # INDEX_ADDDEL: is it add/delete between HEAD and index?
# FILE_ADDDEL: is it add/delete between index and file? # FILE_ADDDEL: is it add/delete between index and file?
# UNMERGED: is the path unmerged
sub list_modified { sub list_modified {
my ($only) = @_; my ($only) = @_;
@ -318,16 +319,10 @@ sub list_modified {
} }
} }
for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) { for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @tracked)) {
if (($add, $del, $file) = if (($add, $del, $file) =
/^([-\d]+) ([-\d]+) (.*)/) { /^([-\d]+) ([-\d]+) (.*)/) {
$file = unquote_path($file); $file = unquote_path($file);
if (!exists $data{$file}) {
$data{$file} = +{
INDEX => 'unchanged',
BINARY => 0,
};
}
my ($change, $bin); my ($change, $bin);
if ($add eq '-' && $del eq '-') { if ($add eq '-' && $del eq '-') {
$change = 'binary'; $change = 'binary';
@ -346,6 +341,18 @@ sub list_modified {
$file = unquote_path($file); $file = unquote_path($file);
$data{$file}{FILE_ADDDEL} = $adddel; $data{$file}{FILE_ADDDEL} = $adddel;
} }
elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
$file = unquote_path($2);
if (!exists $data{$file}) {
$data{$file} = +{
INDEX => 'unchanged',
BINARY => 0,
};
}
if ($1 eq 'U') {
$data{$file}{UNMERGED} = 1;
}
}
} }
for (sort keys %data) { for (sort keys %data) {
@ -1190,6 +1197,10 @@ sub apply_patch_for_checkout_commit {
sub patch_update_cmd { sub patch_update_cmd {
my @all_mods = list_modified($patch_mode_flavour{FILTER}); my @all_mods = list_modified($patch_mode_flavour{FILTER});
error_msg "ignoring unmerged: $_->{VALUE}\n"
for grep { $_->{UNMERGED} } @all_mods;
@all_mods = grep { !$_->{UNMERGED} } @all_mods;
my @mods = grep { !($_->{BINARY}) } @all_mods; my @mods = grep { !($_->{BINARY}) } @all_mods;
my @them; my @them;

View File

@ -24,6 +24,7 @@ ignore-space-change pass it through git-apply
ignore-whitespace pass it through git-apply ignore-whitespace pass it through git-apply
directory= pass it through git-apply directory= pass it through git-apply
exclude= pass it through git-apply exclude= pass it through git-apply
include= pass it through git-apply
C= pass it through git-apply C= pass it through git-apply
p= pass it through git-apply p= pass it through git-apply
patch-format= format the patch(es) are in patch-format= format the patch(es) are in
@ -138,6 +139,12 @@ fall_back_3way () {
say Using index info to reconstruct a base tree... say Using index info to reconstruct a base tree...
cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"' cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
if test -z "$GIT_QUIET"
then
eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD"
fi
cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"' cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
if eval "$cmd" if eval "$cmd"
then then
@ -412,7 +419,7 @@ do
;; ;;
--resolvemsg) --resolvemsg)
shift; resolvemsg=$1 ;; shift; resolvemsg=$1 ;;
--whitespace|--directory|--exclude) --whitespace|--directory|--exclude|--include)
git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;; git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
-C|-p) -C|-p)
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;; git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;

View File

@ -1129,12 +1129,12 @@ class P4Submit(Command, P4UserMap):
print "The following files should be scheduled for deletion with p4 delete:" print "The following files should be scheduled for deletion with p4 delete:"
print " ".join(filesToDelete) print " ".join(filesToDelete)
die("Please resolve and submit the conflict manually and " die("Please resolve and submit the conflict manually and "
+ "continue afterwards with git-p4 submit --continue") + "continue afterwards with git p4 submit --continue")
elif response == "w": elif response == "w":
system(diffcmd + " > patch.txt") system(diffcmd + " > patch.txt")
print "Patch saved to patch.txt in %s !" % self.clientPath print "Patch saved to patch.txt in %s !" % self.clientPath
die("Please resolve and submit the conflict manually and " die("Please resolve and submit the conflict manually and "
"continue afterwards with git-p4 submit --continue") "continue afterwards with git p4 submit --continue")
system(applyPatchCmd) system(applyPatchCmd)
@ -1178,8 +1178,8 @@ class P4Submit(Command, P4UserMap):
if self.checkAuthorship and not self.p4UserIsMe(p4User): if self.checkAuthorship and not self.p4UserIsMe(p4User):
submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail 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 option --preserve-user to modify authorship.\n"
submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n" submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
separatorLine = "######## everything below this line is just the diff #######\n" separatorLine = "######## everything below this line is just the diff #######\n"
@ -2254,7 +2254,7 @@ class P4Sync(Command, P4UserMap):
details["change"] = newestRevision details["change"] = newestRevision
# Use time from top-most change so that all git-p4 clones of # Use time from top-most change so that all git p4 clones of
# the same p4 repo have the same commit SHA1s. # the same p4 repo have the same commit SHA1s.
res = p4CmdList("describe -s %d" % newestRevision) res = p4CmdList("describe -s %d" % newestRevision)
newestTime = None newestTime = None
@ -2474,8 +2474,8 @@ class P4Sync(Command, P4UserMap):
changes.sort() changes.sort()
else: else:
# catch "git-p4 sync" with no new branches, in a repo that # catch "git p4 sync" with no new branches, in a repo that
# does not have any existing git-p4 branches # does not have any existing p4 branches
if len(args) == 0 and not self.p4BranchesInGit: if len(args) == 0 and not self.p4BranchesInGit:
die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here."); die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
if self.verbose: if self.verbose:

View File

@ -672,7 +672,7 @@ rearrange_squash () {
case "$action" in case "$action" in
continue) continue)
# do we have anything to commit? # do we have anything to commit?
if git diff-index --cached --quiet --ignore-submodules HEAD -- if git diff-index --cached --quiet HEAD --
then then
: Nothing to commit -- skip this : Nothing to commit -- skip this
else else
@ -846,6 +846,8 @@ cat >> "$todo" << EOF
# f, fixup = like "squash", but discard this commit's log message # f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell # x, exec = run command (the rest of the line) using shell
# #
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST. # If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted. # However, if you remove everything, the rebase will be aborted.
# #

View File

@ -248,6 +248,10 @@ case $(uname -s) in
find () { find () {
/usr/bin/find "$@" /usr/bin/find "$@"
} }
# git sees Windows-style pwd
pwd () {
builtin pwd -W
}
is_absolute_path () { is_absolute_path () {
case "$1" in case "$1" in
[/\\]* | [A-Za-z]:*) [/\\]* | [A-Za-z]:*)

View File

@ -199,8 +199,8 @@ save_stash () {
# $ git stash save --blah-blah 2>&1 | head -n 2 # $ git stash save --blah-blah 2>&1 | head -n 2
# error: unknown option for 'stash save': --blah-blah # error: unknown option for 'stash save': --blah-blah
# To provide a message, use git stash save -- '--blah-blah' # To provide a message, use git stash save -- '--blah-blah'
eval_gettextln "$("error: unknown option for 'stash save': \$option eval_gettextln "error: unknown option for 'stash save': \$option
To provide a message, use git stash save -- '\$option'")" To provide a message, use git stash save -- '\$option'"
usage usage
;; ;;
*) *)

View File

@ -101,11 +101,12 @@ module_list()
module_name() module_name()
{ {
# Do we have "submodule.<something>.path = $1" defined in .gitmodules file? # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
sm_path="$1"
re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g') re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
test -z "$name" && test -z "$name" &&
die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")" die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
echo "$name" echo "$name"
} }
@ -119,7 +120,7 @@ module_name()
# #
module_clone() module_clone()
{ {
path=$1 sm_path=$1
url=$2 url=$2
reference="$3" reference="$3"
quiet= quiet=
@ -130,8 +131,8 @@ module_clone()
gitdir= gitdir=
gitdir_base= gitdir_base=
name=$(module_name "$path" 2>/dev/null) name=$(module_name "$sm_path" 2>/dev/null)
test -n "$name" || name="$path" test -n "$name" || name="$sm_path"
base_name=$(dirname "$name") base_name=$(dirname "$name")
gitdir=$(git rev-parse --git-dir) gitdir=$(git rev-parse --git-dir)
@ -140,17 +141,17 @@ module_clone()
if test -d "$gitdir" if test -d "$gitdir"
then then
mkdir -p "$path" mkdir -p "$sm_path"
rm -f "$gitdir/index" rm -f "$gitdir/index"
else else
mkdir -p "$gitdir_base" mkdir -p "$gitdir_base"
git clone $quiet -n ${reference:+"$reference"} \ git clone $quiet -n ${reference:+"$reference"} \
--separate-git-dir "$gitdir" "$url" "$path" || --separate-git-dir "$gitdir" "$url" "$sm_path" ||
die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")" die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
fi fi
a=$(cd "$gitdir" && pwd)/ a=$(cd "$gitdir" && pwd)/
b=$(cd "$path" && pwd)/ b=$(cd "$sm_path" && pwd)/
# normalize Windows-style absolute paths to POSIX-style absolute paths # normalize Windows-style absolute paths to POSIX-style absolute paths
case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac
case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac
@ -167,11 +168,12 @@ module_clone()
a=${a%/} a=${a%/}
b=${b%/} b=${b%/}
rel=$(echo $b | sed -e 's|[^/]*|..|g') # Turn each leading "*/" component into "../"
echo "gitdir: $rel/$a" >"$path/.git" rel=$(echo $b | sed -e 's|[^/][^/]*|..|g')
echo "gitdir: $rel/$a" >"$sm_path/.git"
rel=$(echo $a | sed -e 's|[^/]*|..|g') rel=$(echo $a | sed -e 's|[^/][^/]*|..|g')
(clear_local_git_env; cd "$path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b") (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
} }
# #
@ -222,14 +224,14 @@ cmd_add()
done done
repo=$1 repo=$1
path=$2 sm_path=$2
if test -z "$path"; then if test -z "$sm_path"; then
path=$(echo "$repo" | sm_path=$(echo "$repo" |
sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
fi fi
if test -z "$repo" -o -z "$path"; then if test -z "$repo" -o -z "$sm_path"; then
usage usage
fi fi
@ -250,7 +252,7 @@ cmd_add()
# normalize path: # normalize path:
# multiple //; leading ./; /./; /../; trailing / # multiple //; leading ./; /./; /../; trailing /
path=$(printf '%s/\n' "$path" | sm_path=$(printf '%s/\n' "$sm_path" |
sed -e ' sed -e '
s|//*|/|g s|//*|/|g
s|^\(\./\)*|| s|^\(\./\)*||
@ -260,49 +262,49 @@ cmd_add()
tstart tstart
s|/*$|| s|/*$||
') ')
git ls-files --error-unmatch "$path" > /dev/null 2>&1 && git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
die "$(eval_gettext "'\$path' already exists in the index")" die "$(eval_gettext "'\$sm_path' already exists in the index")"
if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1 if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1
then then
eval_gettextln "The following path is ignored by one of your .gitignore files: eval_gettextln "The following path is ignored by one of your .gitignore files:
\$path \$sm_path
Use -f if you really want to add it." >&2 Use -f if you really want to add it." >&2
exit 1 exit 1
fi fi
# perhaps the path exists and is already a git repo, else clone it # perhaps the path exists and is already a git repo, else clone it
if test -e "$path" if test -e "$sm_path"
then then
if test -d "$path"/.git -o -f "$path"/.git if test -d "$sm_path"/.git -o -f "$sm_path"/.git
then then
eval_gettextln "Adding existing repo at '\$path' to the index" eval_gettextln "Adding existing repo at '\$sm_path' to the index"
else else
die "$(eval_gettext "'\$path' already exists and is not a valid git repo")" die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
fi fi
else else
module_clone "$path" "$realrepo" "$reference" || exit module_clone "$sm_path" "$realrepo" "$reference" || exit
( (
clear_local_git_env clear_local_git_env
cd "$path" && cd "$sm_path" &&
# ash fails to wordsplit ${branch:+-b "$branch"...} # ash fails to wordsplit ${branch:+-b "$branch"...}
case "$branch" in case "$branch" in
'') git checkout -f -q ;; '') git checkout -f -q ;;
?*) git checkout -f -q -B "$branch" "origin/$branch" ;; ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
esac esac
) || die "$(eval_gettext "Unable to checkout submodule '\$path'")" ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
fi fi
git config submodule."$path".url "$realrepo" git config submodule."$sm_path".url "$realrepo"
git add $force "$path" || git add $force "$sm_path" ||
die "$(eval_gettext "Failed to add submodule '\$path'")" die "$(eval_gettext "Failed to add submodule '\$sm_path'")"
git config -f .gitmodules submodule."$path".path "$path" && git config -f .gitmodules submodule."$sm_path".path "$sm_path" &&
git config -f .gitmodules submodule."$path".url "$repo" && git config -f .gitmodules submodule."$sm_path".url "$repo" &&
git add --force .gitmodules || git add --force .gitmodules ||
die "$(eval_gettext "Failed to register submodule '\$path'")" die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
} }
# #
@ -340,23 +342,25 @@ cmd_foreach()
exec 3<&0 exec 3<&0
module_list | module_list |
while read mode sha1 stage path while read mode sha1 stage sm_path
do do
if test -e "$path"/.git if test -e "$sm_path"/.git
then then
say "$(eval_gettext "Entering '\$prefix\$path'")" say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
name=$(module_name "$path") name=$(module_name "$sm_path")
( (
prefix="$prefix$path/" prefix="$prefix$sm_path/"
clear_local_git_env clear_local_git_env
cd "$path" && # we make $path available to scripts ...
path=$sm_path
cd "$sm_path" &&
eval "$@" && eval "$@" &&
if test -n "$recursive" if test -n "$recursive"
then then
cmd_foreach "--recursive" "$@" cmd_foreach "--recursive" "$@"
fi fi
) <&3 3<&- || ) <&3 3<&- ||
die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")" die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
fi fi
done done
} }
@ -390,15 +394,15 @@ cmd_init()
done done
module_list "$@" | module_list "$@" |
while read mode sha1 stage path while read mode sha1 stage sm_path
do do
# Skip already registered paths # Skip already registered paths
name=$(module_name "$path") || exit name=$(module_name "$sm_path") || exit
if test -z "$(git config "submodule.$name.url")" if test -z "$(git config "submodule.$name.url")"
then then
url=$(git config -f .gitmodules submodule."$name".url) url=$(git config -f .gitmodules submodule."$name".url)
test -z "$url" && test -z "$url" &&
die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")" die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
# Possibly a url relative to parent # Possibly a url relative to parent
case "$url" in case "$url" in
@ -407,7 +411,7 @@ cmd_init()
;; ;;
esac esac
git config submodule."$name".url "$url" || git config submodule."$name".url "$url" ||
die "$(eval_gettext "Failed to register url for submodule path '\$path'")" die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
fi fi
# Copy "update" setting when it is not set yet # Copy "update" setting when it is not set yet
@ -415,9 +419,9 @@ cmd_init()
test -z "$upd" || test -z "$upd" ||
test -n "$(git config submodule."$name".update)" || test -n "$(git config submodule."$name".update)" ||
git config submodule."$name".update "$upd" || git config submodule."$name".update "$upd" ||
die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")" die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")" say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
done done
} }
@ -489,14 +493,14 @@ cmd_update()
cloned_modules= cloned_modules=
module_list "$@" | { module_list "$@" | {
err= err=
while read mode sha1 stage path while read mode sha1 stage sm_path
do do
if test "$stage" = U if test "$stage" = U
then then
echo >&2 "Skipping unmerged submodule $path" echo >&2 "Skipping unmerged submodule $sm_path"
continue continue
fi fi
name=$(module_name "$path") || exit name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url) url=$(git config submodule."$name".url)
if ! test -z "$update" if ! test -z "$update"
then then
@ -507,7 +511,7 @@ cmd_update()
if test "$update_module" = "none" if test "$update_module" = "none"
then then
echo "Skipping submodule '$path'" echo "Skipping submodule '$sm_path'"
continue continue
fi fi
@ -516,20 +520,20 @@ cmd_update()
# Only mention uninitialized submodules when its # Only mention uninitialized submodules when its
# path have been specified # path have been specified
test "$#" != "0" && test "$#" != "0" &&
say "$(eval_gettext "Submodule path '\$path' not initialized say "$(eval_gettext "Submodule path '\$sm_path' not initialized
Maybe you want to use 'update --init'?")" Maybe you want to use 'update --init'?")"
continue continue
fi fi
if ! test -d "$path"/.git -o -f "$path"/.git if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then then
module_clone "$path" "$url" "$reference"|| exit module_clone "$sm_path" "$url" "$reference"|| exit
cloned_modules="$cloned_modules;$name" cloned_modules="$cloned_modules;$name"
subsha1= subsha1=
else else
subsha1=$(clear_local_git_env; cd "$path" && subsha1=$(clear_local_git_env; cd "$sm_path" &&
git rev-parse --verify HEAD) || git rev-parse --verify HEAD) ||
die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")" die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
fi fi
if test "$subsha1" != "$sha1" if test "$subsha1" != "$sha1"
@ -545,10 +549,10 @@ Maybe you want to use 'update --init'?")"
then then
# Run fetch only if $sha1 isn't present or it # Run fetch only if $sha1 isn't present or it
# is not reachable from a ref. # is not reachable from a ref.
(clear_local_git_env; cd "$path" && (clear_local_git_env; cd "$sm_path" &&
( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) && ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
test -z "$rev") || git-fetch)) || test -z "$rev") || git-fetch)) ||
die "$(eval_gettext "Unable to fetch in submodule path '\$path'")" die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
fi fi
# Is this something we just cloned? # Is this something we just cloned?
@ -562,24 +566,24 @@ Maybe you want to use 'update --init'?")"
case "$update_module" in case "$update_module" in
rebase) rebase)
command="git rebase" command="git rebase"
die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")" die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"
say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")" say_msg="$(eval_gettext "Submodule path '\$sm_path': rebased into '\$sha1'")"
must_die_on_failure=yes must_die_on_failure=yes
;; ;;
merge) merge)
command="git merge" command="git merge"
die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")" die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$sm_path'")"
say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")" say_msg="$(eval_gettext "Submodule path '\$sm_path': merged in '\$sha1'")"
must_die_on_failure=yes must_die_on_failure=yes
;; ;;
*) *)
command="git checkout $subforce -q" command="git checkout $subforce -q"
die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")" die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$sm_path'")"
say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")" say_msg="$(eval_gettext "Submodule path '\$sm_path': checked out '\$sha1'")"
;; ;;
esac esac
if (clear_local_git_env; cd "$path" && $command "$sha1") if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
then then
say "$say_msg" say "$say_msg"
elif test -n "$must_die_on_failure" elif test -n "$must_die_on_failure"
@ -593,11 +597,11 @@ Maybe you want to use 'update --init'?")"
if test -n "$recursive" if test -n "$recursive"
then then
(clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") (clear_local_git_env; cd "$sm_path" && eval cmd_update "$orig_flags")
res=$? res=$?
if test $res -gt 0 if test $res -gt 0
then then
die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")" die_msg="$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
if test $res -eq 1 if test $res -eq 1
then then
err="${err};$die_msg" err="${err};$die_msg"
@ -884,30 +888,30 @@ cmd_status()
done done
module_list "$@" | module_list "$@" |
while read mode sha1 stage path while read mode sha1 stage sm_path
do do
name=$(module_name "$path") || exit name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url) url=$(git config submodule."$name".url)
displaypath="$prefix$path" displaypath="$prefix$sm_path"
if test "$stage" = U if test "$stage" = U
then then
say "U$sha1 $displaypath" say "U$sha1 $displaypath"
continue continue
fi fi
if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git if test -z "$url" || ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
then then
say "-$sha1 $displaypath" say "-$sha1 $displaypath"
continue; continue;
fi fi
set_name_rev "$path" "$sha1" set_name_rev "$sm_path" "$sha1"
if git diff-files --ignore-submodules=dirty --quiet -- "$path" if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
then then
say " $sha1 $displaypath$revname" say " $sha1 $displaypath$revname"
else else
if test -z "$cached" if test -z "$cached"
then then
sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD) sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
set_name_rev "$path" "$sha1" set_name_rev "$sm_path" "$sha1"
fi fi
say "+$sha1 $displaypath$revname" say "+$sha1 $displaypath$revname"
fi fi
@ -917,10 +921,10 @@ cmd_status()
( (
prefix="$displaypath/" prefix="$displaypath/"
clear_local_git_env clear_local_git_env
cd "$path" && cd "$sm_path" &&
eval cmd_status "$orig_args" eval cmd_status "$orig_args"
) || ) ||
die "$(eval_gettext "Failed to recurse into submodule path '\$path'")" die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
fi fi
done done
} }
@ -952,9 +956,9 @@ cmd_sync()
done done
cd_to_toplevel cd_to_toplevel
module_list "$@" | module_list "$@" |
while read mode sha1 stage path while read mode sha1 stage sm_path
do do
name=$(module_name "$path") name=$(module_name "$sm_path")
url=$(git config -f .gitmodules --get submodule."$name".url) url=$(git config -f .gitmodules --get submodule."$name".url)
# Possibly a url relative to parent # Possibly a url relative to parent
@ -969,11 +973,11 @@ cmd_sync()
say "$(eval_gettext "Synchronizing submodule url for '\$name'")" say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
git config submodule."$name".url "$url" git config submodule."$name".url "$url"
if test -e "$path"/.git if test -e "$sm_path"/.git
then then
( (
clear_local_git_env clear_local_git_env
cd "$path" cd "$sm_path"
remote=$(get_default_remote) remote=$(get_default_remote)
git config remote."$remote".url "$url" git config remote."$remote".url "$url"
) )

View File

@ -36,6 +36,11 @@ $ENV{TZ} = 'UTC';
$| = 1; # unbuffer STDOUT $| = 1; # unbuffer STDOUT
sub fatal (@) { print STDERR "@_\n"; exit 1 } sub fatal (@) { print STDERR "@_\n"; exit 1 }
# All SVN commands do it. Otherwise we may die on SIGPIPE when the remote
# repository decides to close the connection which we expect to be kept alive.
$SIG{PIPE} = 'IGNORE';
sub _req_svn { sub _req_svn {
require SVN::Core; # use()-ing this causes segfaults for me... *shrug* require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
require SVN::Ra; require SVN::Ra;
@ -2031,6 +2036,7 @@ use IPC::Open3;
use Time::Local; use Time::Local;
use Memoize; # core since 5.8.0, Jul 2002 use Memoize; # core since 5.8.0, Jul 2002
use Memoize::Storable; use Memoize::Storable;
use POSIX qw(:signal_h);
my ($_gc_nr, $_gc_period); my ($_gc_nr, $_gc_period);
@ -4059,11 +4065,14 @@ sub rev_map_set {
length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
my $db = $self->map_path($uuid); my $db = $self->map_path($uuid);
my $db_lock = "$db.lock"; my $db_lock = "$db.lock";
my $sig; my $sigmask;
$update_ref ||= 0; $update_ref ||= 0;
if ($update_ref) { if ($update_ref) {
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = $sigmask = POSIX::SigSet->new();
$SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; my $signew = POSIX::SigSet->new(SIGINT, SIGHUP, SIGTERM,
SIGALRM, SIGUSR1, SIGUSR2);
sigprocmask(SIG_BLOCK, $signew, $sigmask) or
croak "Can't block signals: $!";
} }
mkfile($db); mkfile($db);
@ -4102,9 +4111,8 @@ sub rev_map_set {
"$db_lock => $db ($!)\n"; "$db_lock => $db ($!)\n";
delete $LOCKFILES{$db_lock}; delete $LOCKFILES{$db_lock};
if ($update_ref) { if ($update_ref) {
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = sigprocmask(SIG_SETMASK, $sigmask) or
$SIG{USR1} = $SIG{USR2} = 'DEFAULT'; croak "Can't restore signal mask: $!";
kill $sig, $$ if defined $sig;
} }
} }

View File

@ -1732,20 +1732,29 @@ sub chop_and_escape_str {
# '<span class="mark">foo</span>bar' # '<span class="mark">foo</span>bar'
sub esc_html_hl_regions { sub esc_html_hl_regions {
my ($str, $css_class, @sel) = @_; my ($str, $css_class, @sel) = @_;
return esc_html($str) unless @sel; my %opts = grep { ref($_) ne 'ARRAY' } @sel;
@sel = grep { ref($_) eq 'ARRAY' } @sel;
return esc_html($str, %opts) unless @sel;
my $out = ''; my $out = '';
my $pos = 0; my $pos = 0;
for my $s (@sel) { for my $s (@sel) {
$out .= esc_html(substr($str, $pos, $s->[0] - $pos)) my ($begin, $end) = @$s;
if ($s->[0] - $pos > 0);
$out .= $cgi->span({-class => $css_class},
esc_html(substr($str, $s->[0], $s->[1] - $s->[0])));
$pos = $s->[1]; # Don't create empty <span> elements.
next if $end <= $begin;
my $escaped = esc_html(substr($str, $begin, $end - $begin),
%opts);
$out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
if ($begin - $pos > 0);
$out .= $cgi->span({-class => $css_class}, $escaped);
$pos = $end;
} }
$out .= esc_html(substr($str, $pos)) $out .= esc_html(substr($str, $pos), %opts)
if ($pos < length($str)); if ($pos < length($str));
return $out; return $out;
@ -2421,26 +2430,32 @@ sub format_cc_diff_chunk_header {
} }
# process patch (diff) line (not to be used for diff headers), # process patch (diff) line (not to be used for diff headers),
# returning class and HTML-formatted (but not wrapped) line # returning HTML-formatted (but not wrapped) line.
sub process_diff_line { # If the line is passed as a reference, it is treated as HTML and not
my $line = shift; # esc_html()'ed.
my ($from, $to) = @_; sub format_diff_line {
my ($line, $diff_class, $from, $to) = @_;
my $diff_class = diff_line_class($line, $from, $to); if (ref($line)) {
$line = $$line;
chomp $line; } else {
$line = untabify($line); chomp $line;
$line = untabify($line);
if ($from && $to && $line =~ m/^\@{2} /) {
$line = format_unidiff_chunk_header($line, $from, $to);
return $diff_class, $line;
} elsif ($from && $to && $line =~ m/^\@{3}/) {
$line = format_cc_diff_chunk_header($line, $from, $to);
return $diff_class, $line;
if ($from && $to && $line =~ m/^\@{2} /) {
$line = format_unidiff_chunk_header($line, $from, $to);
} elsif ($from && $to && $line =~ m/^\@{3}/) {
$line = format_cc_diff_chunk_header($line, $from, $to);
} else {
$line = esc_html($line, -nbsp=>1);
}
} }
return $diff_class, esc_html($line, -nbsp=>1);
my $diff_classes = "diff";
$diff_classes .= " $diff_class" if ($diff_class);
$line = "<div class=\"$diff_classes\">$line</div>\n";
return $line;
} }
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)", # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@ -3886,6 +3901,7 @@ sub print_feed_meta {
'-type' => "application/$type+xml" '-type' => "application/$type+xml"
); );
$href_params{'extra_options'} = undef;
$href_params{'action'} = $type; $href_params{'action'} = $type;
$link_attr{'-href'} = href(%href_params); $link_attr{'-href'} = href(%href_params);
print "<link ". print "<link ".
@ -4993,10 +5009,186 @@ sub git_difftree_body {
print "</table>\n"; print "</table>\n";
} }
sub print_sidebyside_diff_chunk { # Print context lines and then rem/add lines in a side-by-side manner.
my @chunk = @_; sub print_sidebyside_diff_lines {
my ($ctx, $rem, $add) = @_;
# print context block before add/rem block
if (@$ctx) {
print join '',
'<div class="chunk_block ctx">',
'<div class="old">',
@$ctx,
'</div>',
'<div class="new">',
@$ctx,
'</div>',
'</div>';
}
if (!@$add) {
# pure removal
print join '',
'<div class="chunk_block rem">',
'<div class="old">',
@$rem,
'</div>',
'</div>';
} elsif (!@$rem) {
# pure addition
print join '',
'<div class="chunk_block add">',
'<div class="new">',
@$add,
'</div>',
'</div>';
} else {
print join '',
'<div class="chunk_block chg">',
'<div class="old">',
@$rem,
'</div>',
'<div class="new">',
@$add,
'</div>',
'</div>';
}
}
# Print context lines and then rem/add lines in inline manner.
sub print_inline_diff_lines {
my ($ctx, $rem, $add) = @_;
print @$ctx, @$rem, @$add;
}
# Format removed and added line, mark changed part and HTML-format them.
# Implementation is based on contrib/diff-highlight
sub format_rem_add_lines_pair {
my ($rem, $add, $num_parents) = @_;
# We need to untabify lines before split()'ing them;
# otherwise offsets would be invalid.
chomp $rem;
chomp $add;
$rem = untabify($rem);
$add = untabify($add);
my @rem = split(//, $rem);
my @add = split(//, $add);
my ($esc_rem, $esc_add);
# Ignore leading +/- characters for each parent.
my ($prefix_len, $suffix_len) = ($num_parents, 0);
my ($prefix_has_nonspace, $suffix_has_nonspace);
my $shorter = (@rem < @add) ? @rem : @add;
while ($prefix_len < $shorter) {
last if ($rem[$prefix_len] ne $add[$prefix_len]);
$prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
$prefix_len++;
}
while ($prefix_len + $suffix_len < $shorter) {
last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
$suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
$suffix_len++;
}
# Mark lines that are different from each other, but have some common
# part that isn't whitespace. If lines are completely different, don't
# mark them because that would make output unreadable, especially if
# diff consists of multiple lines.
if ($prefix_has_nonspace || $suffix_has_nonspace) {
$esc_rem = esc_html_hl_regions($rem, 'marked',
[$prefix_len, @rem - $suffix_len], -nbsp=>1);
$esc_add = esc_html_hl_regions($add, 'marked',
[$prefix_len, @add - $suffix_len], -nbsp=>1);
} else {
$esc_rem = esc_html($rem, -nbsp=>1);
$esc_add = esc_html($add, -nbsp=>1);
}
return format_diff_line(\$esc_rem, 'rem'),
format_diff_line(\$esc_add, 'add');
}
# HTML-format diff context, removed and added lines.
sub format_ctx_rem_add_lines {
my ($ctx, $rem, $add, $num_parents) = @_;
my (@new_ctx, @new_rem, @new_add);
my $can_highlight = 0;
my $is_combined = ($num_parents > 1);
# Highlight if every removed line has a corresponding added line.
if (@$add > 0 && @$add == @$rem) {
$can_highlight = 1;
# Highlight lines in combined diff only if the chunk contains
# diff between the same version, e.g.
#
# - a
# - b
# + c
# + d
#
# Otherwise the highlightling would be confusing.
if ($is_combined) {
for (my $i = 0; $i < @$add; $i++) {
my $prefix_rem = substr($rem->[$i], 0, $num_parents);
my $prefix_add = substr($add->[$i], 0, $num_parents);
$prefix_rem =~ s/-/+/g;
if ($prefix_rem ne $prefix_add) {
$can_highlight = 0;
last;
}
}
}
}
if ($can_highlight) {
for (my $i = 0; $i < @$add; $i++) {
my ($line_rem, $line_add) = format_rem_add_lines_pair(
$rem->[$i], $add->[$i], $num_parents);
push @new_rem, $line_rem;
push @new_add, $line_add;
}
} else {
@new_rem = map { format_diff_line($_, 'rem') } @$rem;
@new_add = map { format_diff_line($_, 'add') } @$add;
}
@new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
return (\@new_ctx, \@new_rem, \@new_add);
}
# Print context lines and then rem/add lines.
sub print_diff_lines {
my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
my $is_combined = $num_parents > 1;
($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
$num_parents);
if ($diff_style eq 'sidebyside' && !$is_combined) {
print_sidebyside_diff_lines($ctx, $rem, $add);
} else {
# default 'inline' style and unknown styles
print_inline_diff_lines($ctx, $rem, $add);
}
}
sub print_diff_chunk {
my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
my (@ctx, @rem, @add); my (@ctx, @rem, @add);
# The class of the previous line.
my $prev_class = '';
return unless @chunk; return unless @chunk;
# incomplete last line might be among removed or added lines, # incomplete last line might be among removed or added lines,
@ -5015,55 +5207,19 @@ sub print_sidebyside_diff_chunk {
# print chunk headers # print chunk headers
if ($class && $class eq 'chunk_header') { if ($class && $class eq 'chunk_header') {
print $line; print format_diff_line($line, $class, $from, $to);
next; next;
} }
## print from accumulator when type of class of lines change ## print from accumulator when have some add/rem lines or end
# empty contents block on start rem/add block, or end of chunk # of chunk (flush context lines), or when have add and rem
if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) { # lines and new block is reached (otherwise add/rem lines could
print join '', # be reordered)
'<div class="chunk_block ctx">', if (!$class || ((@rem || @add) && $class eq 'ctx') ||
'<div class="old">', (@rem && @add && $class ne $prev_class)) {
@ctx, print_diff_lines(\@ctx, \@rem, \@add,
'</div>', $diff_style, $num_parents);
'<div class="new">', @ctx = @rem = @add = ();
@ctx,
'</div>',
'</div>';
@ctx = ();
}
# empty add/rem block on start context block, or end of chunk
if ((@rem || @add) && (!$class || $class eq 'ctx')) {
if (!@add) {
# pure removal
print join '',
'<div class="chunk_block rem">',
'<div class="old">',
@rem,
'</div>',
'</div>';
} elsif (!@rem) {
# pure addition
print join '',
'<div class="chunk_block add">',
'<div class="new">',
@add,
'</div>',
'</div>';
} else {
# assume that it is change
print join '',
'<div class="chunk_block chg">',
'<div class="old">',
@rem,
'</div>',
'<div class="new">',
@add,
'</div>',
'</div>';
}
@rem = @add = ();
} }
## adding lines to accumulator ## adding lines to accumulator
@ -5079,6 +5235,8 @@ sub print_sidebyside_diff_chunk {
if ($class eq 'ctx') { if ($class eq 'ctx') {
push @ctx, $line; push @ctx, $line;
} }
$prev_class = $class;
} }
} }
@ -5200,27 +5358,19 @@ sub git_patchset_body {
next PATCH if ($patch_line =~ m/^diff /); next PATCH if ($patch_line =~ m/^diff /);
my ($class, $line) = process_diff_line($patch_line, \%from, \%to); my $class = diff_line_class($patch_line, \%from, \%to);
my $diff_classes = "diff";
$diff_classes .= " $class" if ($class);
$line = "<div class=\"$diff_classes\">$line</div>\n";
if ($diff_style eq 'sidebyside' && !$is_combined) { if ($class eq 'chunk_header') {
if ($class eq 'chunk_header') { print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
print_sidebyside_diff_chunk(@chunk); @chunk = ();
@chunk = ( [ $class, $line ] );
} else {
push @chunk, [ $class, $line ];
}
} else {
# default 'inline' style and unknown styles
print $line;
} }
push @chunk, [ $class, $patch_line ];
} }
} continue { } continue {
if (@chunk) { if (@chunk) {
print_sidebyside_diff_chunk(@chunk); print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
@chunk = (); @chunk = ();
} }
print "</div>\n"; # class="patch" print "</div>\n"; # class="patch"
@ -7003,6 +7153,28 @@ sub snapshot_name {
return wantarray ? ($name, $name) : $name; return wantarray ? ($name, $name) : $name;
} }
sub exit_if_unmodified_since {
my ($latest_epoch) = @_;
our $cgi;
my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
if (defined $if_modified) {
my $since;
if (eval { require HTTP::Date; 1; }) {
$since = HTTP::Date::str2time($if_modified);
} elsif (eval { require Time::ParseDate; 1; }) {
$since = Time::ParseDate::parsedate($if_modified, GMT => 1);
}
if (defined $since && $latest_epoch <= $since) {
my %latest_date = parse_date($latest_epoch);
print $cgi->header(
-last_modified => $latest_date{'rfc2822'},
-status => '304 Not Modified');
goto DONE_GITWEB;
}
}
}
sub git_snapshot { sub git_snapshot {
my $format = $input_params{'snapshot_format'}; my $format = $input_params{'snapshot_format'};
if (!@snapshot_fmts) { if (!@snapshot_fmts) {
@ -7029,6 +7201,10 @@ sub git_snapshot {
my ($name, $prefix) = snapshot_name($project, $hash); my ($name, $prefix) = snapshot_name($project, $hash);
my $filename = "$name$known_snapshot_formats{$format}{'suffix'}"; my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
my %co = parse_commit($hash);
exit_if_unmodified_since($co{'committer_epoch'}) if %co;
my $cmd = quote_command( my $cmd = quote_command(
git_cmd(), 'archive', git_cmd(), 'archive',
"--format=$known_snapshot_formats{$format}{'format'}", "--format=$known_snapshot_formats{$format}{'format'}",
@ -7038,9 +7214,15 @@ sub git_snapshot {
} }
$filename =~ s/(["\\])/\\$1/g; $filename =~ s/(["\\])/\\$1/g;
my %latest_date;
if (%co) {
%latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
}
print $cgi->header( print $cgi->header(
-type => $known_snapshot_formats{$format}{'type'}, -type => $known_snapshot_formats{$format}{'type'},
-content_disposition => 'inline; filename="' . $filename . '"', -content_disposition => 'inline; filename="' . $filename . '"',
%co ? (-last_modified => $latest_date{'rfc2822'}) : (),
-status => '200 OK'); -status => '200 OK');
open my $fd, "-|", $cmd open my $fd, "-|", $cmd
@ -7820,33 +8002,14 @@ sub git_feed {
if (defined($commitlist[0])) { if (defined($commitlist[0])) {
%latest_commit = %{$commitlist[0]}; %latest_commit = %{$commitlist[0]};
my $latest_epoch = $latest_commit{'committer_epoch'}; my $latest_epoch = $latest_commit{'committer_epoch'};
%latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'}); exit_if_unmodified_since($latest_epoch);
my $if_modified = $cgi->http('IF_MODIFIED_SINCE'); %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
if (defined $if_modified) {
my $since;
if (eval { require HTTP::Date; 1; }) {
$since = HTTP::Date::str2time($if_modified);
} elsif (eval { require Time::ParseDate; 1; }) {
$since = Time::ParseDate::parsedate($if_modified, GMT => 1);
}
if (defined $since && $latest_epoch <= $since) {
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
-last_modified => $latest_date{'rfc2822'},
-status => '304 Not Modified');
return;
}
}
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
-last_modified => $latest_date{'rfc2822'});
} else {
print $cgi->header(
-type => $content_type,
-charset => 'utf-8');
} }
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
%latest_date ? (-last_modified => $latest_date{'rfc2822'}) : (),
-status => '200 OK');
# Optimization: skip generating the body if client asks only # Optimization: skip generating the body if client asks only
# for Last-Modified date. # for Last-Modified date.

View File

@ -438,6 +438,10 @@ div.diff.add {
color: #008800; color: #008800;
} }
div.diff.add span.marked {
background-color: #aaffaa;
}
div.diff.from_file a.path, div.diff.from_file a.path,
div.diff.from_file { div.diff.from_file {
color: #aa0000; color: #aa0000;
@ -447,6 +451,10 @@ div.diff.rem {
color: #cc0000; color: #cc0000;
} }
div.diff.rem span.marked {
background-color: #ffaaaa;
}
div.diff.chunk_header a, div.diff.chunk_header a,
div.diff.chunk_header { div.diff.chunk_header {
color: #990099; color: #990099;

View File

@ -7,6 +7,7 @@
#include "run-command.h" #include "run-command.h"
#include "string-list.h" #include "string-list.h"
#include "url.h" #include "url.h"
#include "argv-array.h"
static const char content_type[] = "Content-Type"; static const char content_type[] = "Content-Type";
static const char content_length[] = "Content-Length"; static const char content_length[] = "Content-Length";
@ -317,8 +318,7 @@ static void run_service(const char **argv)
const char *encoding = getenv("HTTP_CONTENT_ENCODING"); const char *encoding = getenv("HTTP_CONTENT_ENCODING");
const char *user = getenv("REMOTE_USER"); const char *user = getenv("REMOTE_USER");
const char *host = getenv("REMOTE_ADDR"); const char *host = getenv("REMOTE_ADDR");
char *env[3]; struct argv_array env = ARGV_ARRAY_INIT;
struct strbuf buf = STRBUF_INIT;
int gzipped_request = 0; int gzipped_request = 0;
struct child_process cld; struct child_process cld;
@ -332,17 +332,15 @@ static void run_service(const char **argv)
if (!host || !*host) if (!host || !*host)
host = "(none)"; host = "(none)";
memset(&env, 0, sizeof(env)); if (!getenv("GIT_COMMITTER_NAME"))
strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user); argv_array_pushf(&env, "GIT_COMMITTER_NAME=%s", user);
env[0] = strbuf_detach(&buf, NULL); if (!getenv("GIT_COMMITTER_EMAIL"))
argv_array_pushf(&env, "GIT_COMMITTER_EMAIL=%s@http.%s",
strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host); user, host);
env[1] = strbuf_detach(&buf, NULL);
env[2] = NULL;
memset(&cld, 0, sizeof(cld)); memset(&cld, 0, sizeof(cld));
cld.argv = argv; cld.argv = argv;
cld.env = (const char *const *)env; cld.env = env.argv;
if (gzipped_request) if (gzipped_request)
cld.in = -1; cld.in = -1;
cld.git_cmd = 1; cld.git_cmd = 1;
@ -357,9 +355,7 @@ static void run_service(const char **argv)
if (finish_command(&cld)) if (finish_command(&cld))
exit(1); exit(1);
free(env[0]); argv_array_clear(&env);
free(env[1]);
strbuf_release(&buf);
} }
static int show_text_ref(const char *name, const unsigned char *sha1, static int show_text_ref(const char *name, const unsigned char *sha1,

21
http.c
View File

@ -210,14 +210,23 @@ static int http_options(const char *var, const char *value, void *cb)
static void init_curl_http_auth(CURL *result) static void init_curl_http_auth(CURL *result)
{ {
if (http_auth.username) { if (!http_auth.username)
struct strbuf up = STRBUF_INIT; return;
credential_fill(&http_auth);
credential_fill(&http_auth);
#if LIBCURL_VERSION_NUM >= 0x071301
curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
#else
{
static struct strbuf up = STRBUF_INIT;
strbuf_reset(&up);
strbuf_addf(&up, "%s:%s", strbuf_addf(&up, "%s:%s",
http_auth.username, http_auth.password); http_auth.username, http_auth.password);
curl_easy_setopt(result, CURLOPT_USERPWD, curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
strbuf_detach(&up, NULL));
} }
#endif
} }
static int has_cert_password(void) static int has_cert_password(void)
@ -494,6 +503,8 @@ struct active_request_slot *get_active_slot(void)
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
if (http_auth.password)
init_curl_http_auth(slot->curl);
return slot; return slot;
} }

68
ident.c
View File

@ -220,6 +220,74 @@ static int copy(char *buf, size_t size, int offset, const char *src)
return offset; return offset;
} }
/*
* Reverse of fmt_ident(); given an ident line, split the fields
* to allow the caller to parse it.
* Signal a success by returning 0, but date/tz fields of the result
* can still be NULL if the input line only has the name/email part
* (e.g. reading from a reflog entry).
*/
int split_ident_line(struct ident_split *split, const char *line, int len)
{
const char *cp;
size_t span;
int status = -1;
memset(split, 0, sizeof(*split));
split->name_begin = line;
for (cp = line; *cp && cp < line + len; cp++)
if (*cp == '<') {
split->mail_begin = cp + 1;
break;
}
if (!split->mail_begin)
return status;
for (cp = split->mail_begin - 2; line < cp; cp--)
if (!isspace(*cp)) {
split->name_end = cp + 1;
break;
}
if (!split->name_end)
return status;
for (cp = split->mail_begin; cp < line + len; cp++)
if (*cp == '>') {
split->mail_end = cp;
break;
}
if (!split->mail_end)
return status;
for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++)
;
if (line + len <= cp)
goto person_only;
split->date_begin = cp;
span = strspn(cp, "0123456789");
if (!span)
goto person_only;
split->date_end = split->date_begin + span;
for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
;
if (line + len <= cp || (*cp != '+' && *cp != '-'))
goto person_only;
split->tz_begin = cp;
span = strspn(cp + 1, "0123456789");
if (!span)
goto person_only;
split->tz_end = split->tz_begin + 1 + span;
return 0;
person_only:
split->date_begin = NULL;
split->date_end = NULL;
split->tz_begin = NULL;
split->tz_end = NULL;
return 0;
}
static const char *env_hint = static const char *env_hint =
"\n" "\n"
"*** Please tell me who you are.\n" "*** Please tell me who you are.\n"

View File

@ -711,14 +711,15 @@ int log_tree_diff_flush(struct rev_info *opt)
opt->verbose_header && opt->verbose_header &&
opt->commit_format != CMIT_FMT_ONELINE) { opt->commit_format != CMIT_FMT_ONELINE) {
int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH; int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
if ((pch & opt->diffopt.output_format) == pch)
printf("---");
if (opt->diffopt.output_prefix) { if (opt->diffopt.output_prefix) {
struct strbuf *msg = NULL; struct strbuf *msg = NULL;
msg = opt->diffopt.output_prefix(&opt->diffopt, msg = opt->diffopt.output_prefix(&opt->diffopt,
opt->diffopt.output_prefix_data); opt->diffopt.output_prefix_data);
fwrite(msg->buf, msg->len, 1, stdout); fwrite(msg->buf, msg->len, 1, stdout);
} }
if ((pch & opt->diffopt.output_format) == pch) {
printf("---");
}
putchar('\n'); putchar('\n');
} }
} }

View File

@ -485,6 +485,7 @@ static struct string_list *get_renames(struct merge_options *o,
renames = xcalloc(1, sizeof(struct string_list)); renames = xcalloc(1, sizeof(struct string_list));
diff_setup(&opts); diff_setup(&opts);
DIFF_OPT_SET(&opts, RECURSIVE); DIFF_OPT_SET(&opts, RECURSIVE);
DIFF_OPT_CLR(&opts, RENAME_EMPTY);
opts.detect_rename = DIFF_DETECT_RENAME; opts.detect_rename = DIFF_DETECT_RENAME;
opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit : opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
o->diff_rename_limit >= 0 ? o->diff_rename_limit : o->diff_rename_limit >= 0 ? o->diff_rename_limit :
@ -1914,7 +1915,7 @@ int merge_recursive(struct merge_options *o,
/* if there is no common ancestor, use an empty tree */ /* if there is no common ancestor, use an empty tree */
struct tree *tree; struct tree *tree;
tree = lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
merged_common_ancestors = make_virtual_commit(tree, "ancestor"); merged_common_ancestors = make_virtual_commit(tree, "ancestor");
} }
@ -2068,9 +2069,9 @@ int parse_merge_opt(struct merge_options *o, const char *s)
else if (!prefixcmp(s, "subtree=")) else if (!prefixcmp(s, "subtree="))
o->subtree_shift = s + strlen("subtree="); o->subtree_shift = s + strlen("subtree=");
else if (!strcmp(s, "patience")) else if (!strcmp(s, "patience"))
o->xdl_opts |= XDF_PATIENCE_DIFF; o->xdl_opts = DIFF_WITH_ALG(o, PATIENCE_DIFF);
else if (!strcmp(s, "histogram")) else if (!strcmp(s, "histogram"))
o->xdl_opts |= XDF_HISTOGRAM_DIFF; o->xdl_opts = DIFF_WITH_ALG(o, HISTOGRAM_DIFF);
else if (!strcmp(s, "ignore-space-change")) else if (!strcmp(s, "ignore-space-change"))
o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(s, "ignore-all-space")) else if (!strcmp(s, "ignore-all-space"))

73
mergesort.c Normal file
View File

@ -0,0 +1,73 @@
#include "cache.h"
#include "mergesort.h"
struct mergesort_sublist {
void *ptr;
unsigned long len;
};
static void *get_nth_next(void *list, unsigned long n,
void *(*get_next_fn)(const void *))
{
while (n-- && list)
list = get_next_fn(list);
return list;
}
static void *pop_item(struct mergesort_sublist *l,
void *(*get_next_fn)(const void *))
{
void *p = l->ptr;
l->ptr = get_next_fn(l->ptr);
l->len = l->ptr ? (l->len - 1) : 0;
return p;
}
void *llist_mergesort(void *list,
void *(*get_next_fn)(const void *),
void (*set_next_fn)(void *, void *),
int (*compare_fn)(const void *, const void *))
{
unsigned long l;
if (!list)
return NULL;
for (l = 1; ; l *= 2) {
void *curr;
struct mergesort_sublist p, q;
p.ptr = list;
q.ptr = get_nth_next(p.ptr, l, get_next_fn);
if (!q.ptr)
break;
p.len = q.len = l;
if (compare_fn(p.ptr, q.ptr) > 0)
list = curr = pop_item(&q, get_next_fn);
else
list = curr = pop_item(&p, get_next_fn);
while (p.ptr) {
while (p.len || q.len) {
void *prev = curr;
if (!p.len)
curr = pop_item(&q, get_next_fn);
else if (!q.len)
curr = pop_item(&p, get_next_fn);
else if (compare_fn(p.ptr, q.ptr) > 0)
curr = pop_item(&q, get_next_fn);
else
curr = pop_item(&p, get_next_fn);
set_next_fn(prev, curr);
}
p.ptr = q.ptr;
p.len = l;
q.ptr = get_nth_next(p.ptr, l, get_next_fn);
q.len = q.ptr ? l : 0;
}
set_next_fn(curr, NULL);
}
return list;
}

17
mergesort.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef MERGESORT_H
#define MERGESORT_H
/*
* Sort linked list in place.
* - get_next_fn() returns the next element given an element of a linked list.
* - set_next_fn() takes two elements A and B, and makes B the "next" element
* of A on the list.
* - compare_fn() takes two elements A and B, and returns negative, 0, positive
* as the same sign as "subtracting" B from A.
*/
void *llist_mergesort(void *list,
void *(*get_next_fn)(const void *),
void (*set_next_fn)(void *, void *),
int (*compare_fn)(const void *, const void *));
#endif

View File

@ -267,7 +267,8 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
* Must establish NOTES_MERGE_WORKTREE. * Must establish NOTES_MERGE_WORKTREE.
* Abort if NOTES_MERGE_WORKTREE already exists * Abort if NOTES_MERGE_WORKTREE already exists
*/ */
if (file_exists(git_path(NOTES_MERGE_WORKTREE))) { if (file_exists(git_path(NOTES_MERGE_WORKTREE)) &&
!is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) {
if (advice_resolve_conflict) if (advice_resolve_conflict)
die("You have not concluded your previous " die("You have not concluded your previous "
"notes merge (%s exists).\nPlease, use " "notes merge (%s exists).\nPlease, use "
@ -687,51 +688,60 @@ int notes_merge_commit(struct notes_merge_options *o,
{ {
/* /*
* Iterate through files in .git/NOTES_MERGE_WORKTREE and add all * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all
* found notes to 'partial_tree'. Write the updates notes tree to * found notes to 'partial_tree'. Write the updated notes tree to
* the DB, and commit the resulting tree object while reusing the * the DB, and commit the resulting tree object while reusing the
* commit message and parents from 'partial_commit'. * commit message and parents from 'partial_commit'.
* Finally store the new commit object SHA1 into 'result_sha1'. * Finally store the new commit object SHA1 into 'result_sha1'.
*/ */
struct dir_struct dir; DIR *dir;
char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/")); struct dirent *e;
int path_len = strlen(path), i; struct strbuf path = STRBUF_INIT;
char *msg = strstr(partial_commit->buffer, "\n\n"); char *msg = strstr(partial_commit->buffer, "\n\n");
struct strbuf sb_msg = STRBUF_INIT; struct strbuf sb_msg = STRBUF_INIT;
int baselen;
strbuf_addstr(&path, git_path(NOTES_MERGE_WORKTREE));
if (o->verbosity >= 3) if (o->verbosity >= 3)
printf("Committing notes in notes merge worktree at %.*s\n", printf("Committing notes in notes merge worktree at %s\n",
path_len - 1, path); path.buf);
if (!msg || msg[2] == '\0') if (!msg || msg[2] == '\0')
die("partial notes commit has empty message"); die("partial notes commit has empty message");
msg += 2; msg += 2;
memset(&dir, 0, sizeof(dir)); dir = opendir(path.buf);
read_directory(&dir, path, path_len, NULL); if (!dir)
for (i = 0; i < dir.nr; i++) { die_errno("could not open %s", path.buf);
struct dir_entry *ent = dir.entries[i];
strbuf_addch(&path, '/');
baselen = path.len;
while ((e = readdir(dir)) != NULL) {
struct stat st; struct stat st;
const char *relpath = ent->name + path_len;
unsigned char obj_sha1[20], blob_sha1[20]; unsigned char obj_sha1[20], blob_sha1[20];
if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) { if (is_dot_or_dotdot(e->d_name))
continue;
if (strlen(e->d_name) != 40 || get_sha1_hex(e->d_name, obj_sha1)) {
if (o->verbosity >= 3) if (o->verbosity >= 3)
printf("Skipping non-SHA1 entry '%s'\n", printf("Skipping non-SHA1 entry '%s%s'\n",
ent->name); path.buf, e->d_name);
continue; continue;
} }
strbuf_addstr(&path, e->d_name);
/* write file as blob, and add to partial_tree */ /* write file as blob, and add to partial_tree */
if (stat(ent->name, &st)) if (stat(path.buf, &st))
die_errno("Failed to stat '%s'", ent->name); die_errno("Failed to stat '%s'", path.buf);
if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT)) if (index_path(blob_sha1, path.buf, &st, HASH_WRITE_OBJECT))
die("Failed to write blob object from '%s'", ent->name); die("Failed to write blob object from '%s'", path.buf);
if (add_note(partial_tree, obj_sha1, blob_sha1, NULL)) if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
die("Failed to add resolved note '%s' to notes tree", die("Failed to add resolved note '%s' to notes tree",
ent->name); path.buf);
if (o->verbosity >= 4) if (o->verbosity >= 4)
printf("Added resolved note for object %s: %s\n", printf("Added resolved note for object %s: %s\n",
sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1)); sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
strbuf_setlen(&path, baselen);
} }
strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1); strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
@ -740,20 +750,25 @@ int notes_merge_commit(struct notes_merge_options *o,
if (o->verbosity >= 4) if (o->verbosity >= 4)
printf("Finalized notes merge commit: %s\n", printf("Finalized notes merge commit: %s\n",
sha1_to_hex(result_sha1)); sha1_to_hex(result_sha1));
free(path); strbuf_release(&path);
closedir(dir);
return 0; return 0;
} }
int notes_merge_abort(struct notes_merge_options *o) int notes_merge_abort(struct notes_merge_options *o)
{ {
/* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */ /*
* Remove all files within .git/NOTES_MERGE_WORKTREE. We do not remove
* the .git/NOTES_MERGE_WORKTREE directory itself, since it might be
* the current working directory of the user.
*/
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
int ret; int ret;
strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE)); strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
if (o->verbosity >= 3) if (o->verbosity >= 3)
printf("Removing notes merge worktree at %s\n", buf.buf); printf("Removing notes merge worktree at %s/*\n", buf.buf);
ret = remove_dir_recursively(&buf, 0); ret = remove_dir_recursively(&buf, REMOVE_DIR_KEEP_TOPLEVEL);
strbuf_release(&buf); strbuf_release(&buf);
return ret; return ret;
} }

View File

@ -198,6 +198,17 @@ struct object *parse_object(const unsigned char *sha1)
if (obj && obj->parsed) if (obj && obj->parsed)
return obj; return obj;
if ((obj && obj->type == OBJ_BLOB) ||
(!obj && has_sha1_file(sha1) &&
sha1_object_info(sha1, NULL) == OBJ_BLOB)) {
if (check_sha1_signature(repl, NULL, 0, NULL) < 0) {
error("sha1 mismatch %s\n", sha1_to_hex(repl));
return NULL;
}
parse_blob_buffer(lookup_blob(sha1), NULL, 0);
return lookup_object(sha1);
}
buffer = read_sha1_file(sha1, &type, &size); buffer = read_sha1_file(sha1, &type, &size);
if (buffer) { if (buffer) {
if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) { if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
@ -275,3 +286,14 @@ void object_array_remove_duplicates(struct object_array *array)
array->nr = dst; array->nr = dst;
} }
} }
void clear_object_flags(unsigned flags)
{
int i;
for (i=0; i < obj_hash_size; i++) {
struct object *obj = obj_hash[i];
if (obj)
obj->flags &= ~flags;
}
}

View File

@ -76,4 +76,6 @@ void add_object_array(struct object *obj, const char *name, struct object_array
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode); void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
void object_array_remove_duplicates(struct object_array *); void object_array_remove_duplicates(struct object_array *);
void clear_object_flags(unsigned flags);
#endif /* OBJECT_H */ #endif /* OBJECT_H */

View File

@ -531,41 +531,24 @@ static size_t format_person_part(struct strbuf *sb, char part,
{ {
/* currently all placeholders have same length */ /* currently all placeholders have same length */
const int placeholder_len = 2; const int placeholder_len = 2;
int start, end, tz = 0; int tz;
unsigned long date = 0; unsigned long date = 0;
char *ep;
const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
char person_name[1024]; char person_name[1024];
char person_mail[1024]; char person_mail[1024];
struct ident_split s;
const char *name_start, *name_end, *mail_start, *mail_end;
/* advance 'end' to point to email start delimiter */ if (split_ident_line(&s, msg, len) < 0)
for (end = 0; end < len && msg[end] != '<'; end++)
; /* do nothing */
/*
* When end points at the '<' that we found, it should have
* matching '>' later, which means 'end' must be strictly
* below len - 1.
*/
if (end >= len - 2)
goto skip; goto skip;
/* Seek for both name and email part */ name_start = s.name_begin;
name_start = msg; name_end = s.name_end;
name_end = msg+end; mail_start = s.mail_begin;
while (name_end > name_start && isspace(*(name_end-1))) mail_end = s.mail_end;
name_end--;
mail_start = msg+end+1;
mail_end = mail_start;
while (mail_end < msg_end && *mail_end != '>')
mail_end++;
if (mail_end == msg_end)
goto skip;
end = mail_end-msg;
if (part == 'N' || part == 'E') { /* mailmap lookup */ if (part == 'N' || part == 'E') { /* mailmap lookup */
strlcpy(person_name, name_start, name_end-name_start+1); strlcpy(person_name, name_start, name_end - name_start + 1);
strlcpy(person_mail, mail_start, mail_end-mail_start+1); strlcpy(person_mail, mail_start, mail_end - mail_start + 1);
mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name)); mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
name_start = person_name; name_start = person_name;
name_end = name_start + strlen(person_name); name_end = name_start + strlen(person_name);
@ -581,28 +564,20 @@ static size_t format_person_part(struct strbuf *sb, char part,
return placeholder_len; return placeholder_len;
} }
/* advance 'start' to point to date start delimiter */ if (!s.date_begin)
for (start = end + 1; start < len && isspace(msg[start]); start++)
; /* do nothing */
if (start >= len)
goto skip;
date = strtoul(msg + start, &ep, 10);
if (msg + start == ep)
goto skip; goto skip;
date = strtoul(s.date_begin, NULL, 10);
if (part == 't') { /* date, UNIX timestamp */ if (part == 't') { /* date, UNIX timestamp */
strbuf_add(sb, msg + start, ep - (msg + start)); strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
return placeholder_len; return placeholder_len;
} }
/* parse tz */ /* parse tz */
for (start = ep - msg + 1; start < len && isspace(msg[start]); start++) tz = strtoul(s.tz_begin + 1, NULL, 10);
; /* do nothing */ if (*s.tz_begin == '-')
if (start + 1 < len) { tz = -tz;
tz = strtoul(msg + start + 1, NULL, 10);
if (msg[start] == '-')
tz = -tz;
}
switch (part) { switch (part) {
case 'd': /* date */ case 'd': /* date */
@ -621,8 +596,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
skip: skip:
/* /*
* bogus commit, 'sb' cannot be updated, but we still need to * reading from either a bogus commit, or a reflog entry with
* compute a valid return value. * %gn, %ge, etc.; 'sb' cannot be updated, but we still need
* to compute a valid return value.
*/ */
if (part == 'n' || part == 'e' || part == 't' || part == 'd' if (part == 'n' || part == 'e' || part == 't' || part == 'd'
|| part == 'D' || part == 'r' || part == 'i') || part == 'D' || part == 'r' || part == 'i')

View File

@ -157,16 +157,6 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
return 0; return 0;
} }
static int is_empty_blob_sha1(const unsigned char *sha1)
{
static const unsigned char empty_blob_sha1[20] = {
0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
0x29,0xae,0x77,0x5a,0xd8,0xc2,0xe4,0x8c,0x53,0x91
};
return !hashcmp(sha1, empty_blob_sha1);
}
static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
{ {
unsigned int changed = 0; unsigned int changed = 0;

1037
refs.c

File diff suppressed because it is too large Load Diff

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