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:
commit
69c835701b
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||||
|
@ -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:
|
||||||
|
50
Documentation/RelNotes/1.7.10.1.txt
Normal file
50
Documentation/RelNotes/1.7.10.1.txt
Normal 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.
|
150
Documentation/RelNotes/1.7.11.txt
Normal file
150
Documentation/RelNotes/1.7.11.txt
Normal 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).
|
13
Documentation/RelNotes/1.7.7.7.txt
Normal file
13
Documentation/RelNotes/1.7.7.7.txt
Normal 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.
|
22
Documentation/RelNotes/1.7.8.6.txt
Normal file
22
Documentation/RelNotes/1.7.8.6.txt
Normal 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.
|
13
Documentation/RelNotes/1.7.9.7.txt
Normal file
13
Documentation/RelNotes/1.7.9.7.txt
Normal 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.
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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`
|
||||||
|
@ -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::
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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[]
|
||||||
|
@ -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
|
||||||
-----------
|
-----------
|
||||||
|
@ -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],
|
||||||
|
@ -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
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -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='
|
||||||
'
|
'
|
||||||
|
3
INSTALL
3
INSTALL
@ -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
|
||||||
|
14
Makefile
14
Makefile
@ -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
|
||||||
|
|
||||||
|
6
advice.c
6
advice.c
@ -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 },
|
||||||
|
3
advice.h
3
advice.h
@ -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;
|
||||||
|
9
branch.c
9
branch.c
@ -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)
|
||||||
|
2
branch.h
2
branch.h
@ -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
|
||||||
|
174
builtin/apply.c
174
builtin/apply.c
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 },
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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"));
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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(¬e);
|
strbuf_reset(¬e);
|
||||||
if (ref) {
|
if (ref) {
|
||||||
rc |= update_local_ref(ref, what, ¬e);
|
rc |= update_local_ref(ref, what, rm, ¬e);
|
||||||
free(ref);
|
free(ref);
|
||||||
} else
|
} else
|
||||||
strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD",
|
strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD",
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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;
|
||||||
|
142
builtin/merge.c
142
builtin/merge.c
@ -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; "
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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")) {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
2
bundle.c
2
bundle.c
@ -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
34
cache.h
@ -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 */
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
44
commit.c
44
commit.c
@ -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,
|
||||||
|
1
commit.h
1
commit.h
@ -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);
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
197
configure.ac
197
configure.ac
@ -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))
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -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
|
||||||
"
|
"
|
||||||
;;
|
;;
|
||||||
|
12
contrib/fast-import/git-p4.README
Normal file
12
contrib/fast-import/git-p4.README
Normal 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" %*
|
@ -1 +0,0 @@
|
|||||||
@python "%~d0%~p0git-p4" %*
|
|
5
contrib/subtree/.gitignore
vendored
Normal file
5
contrib/subtree/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*~
|
||||||
|
git-subtree.xml
|
||||||
|
git-subtree.1
|
||||||
|
mainline
|
||||||
|
subproj
|
339
contrib/subtree/COPYING
Normal file
339
contrib/subtree/COPYING
Normal 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
28
contrib/subtree/INSTALL
Normal 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
52
contrib/subtree/Makefile
Normal 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
8
contrib/subtree/README
Normal 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
712
contrib/subtree/git-subtree.sh
Executable 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" "$@"
|
366
contrib/subtree/git-subtree.txt
Normal file
366
contrib/subtree/git-subtree.txt
Normal 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
|
69
contrib/subtree/t/Makefile
Normal file
69
contrib/subtree/t/Makefile
Normal 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
|
508
contrib/subtree/t/t7900-subtree.sh
Executable file
508
contrib/subtree/t/t7900-subtree.sh
Executable 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
50
contrib/subtree/todo
Normal 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.
|
@ -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
137
diff.c
@ -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
4
diff.h
@ -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,
|
||||||
|
@ -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
35
dir.c
@ -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
1
dir.h
@ -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
53
entry.c
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
110
fast-import.c
110
fast-import.c
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 ;;
|
||||||
|
@ -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:
|
@ -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.
|
||||||
#
|
#
|
||||||
|
@ -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]:*)
|
||||||
|
@ -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
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
166
git-submodule.sh
166
git-submodule.sh
@ -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"
|
||||||
)
|
)
|
||||||
|
20
git-svn.perl
20
git-svn.perl
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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
21
http.c
@ -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
68
ident.c
@ -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"
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
73
mergesort.c
Normal 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
17
mergesort.h
Normal 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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
22
object.c
22
object.c
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
object.h
2
object.h
@ -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 */
|
||||||
|
64
pretty.c
64
pretty.c
@ -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')
|
||||||
|
10
read-cache.c
10
read-cache.c
@ -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;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user