aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Documentation/SubmittingPatches7
-rw-r--r--Documentation/config.txt43
-rw-r--r--Documentation/diff-format.txt82
-rw-r--r--Documentation/everyday.txt54
-rw-r--r--Documentation/git-blame.txt39
-rw-r--r--Documentation/git-cherry.txt24
-rw-r--r--Documentation/git-clone.txt1
-rw-r--r--Documentation/git-daemon.txt18
-rw-r--r--Documentation/git-diff-index.txt4
-rw-r--r--Documentation/git-fetch-pack.txt3
-rw-r--r--Documentation/git-for-each-ref.txt185
-rw-r--r--Documentation/git-index-pack.txt52
-rw-r--r--Documentation/git-pack-objects.txt37
-rw-r--r--Documentation/git-pack-refs.txt54
-rw-r--r--Documentation/git-pull.txt2
-rw-r--r--Documentation/git-rebase.txt65
-rw-r--r--Documentation/git-repack.txt14
-rw-r--r--Documentation/git-repo-config.txt31
-rw-r--r--Documentation/git-rev-parse.txt25
-rw-r--r--Documentation/git-send-email.txt9
-rw-r--r--Documentation/git-show-ref.txt156
-rw-r--r--Documentation/git-update-ref.txt10
-rw-r--r--Documentation/git.txt372
-rw-r--r--Documentation/urls.txt8
-rw-r--r--Makefile40
-rw-r--r--archive.h2
-rw-r--r--blame.c458
-rw-r--r--builtin-annotate.c25
-rw-r--r--builtin-apply.c10
-rw-r--r--builtin-archive.c23
-rw-r--r--builtin-branch.c221
-rw-r--r--builtin-fmt-merge-msg.c7
-rw-r--r--builtin-for-each-ref.c896
-rw-r--r--builtin-init-db.c4
-rw-r--r--builtin-log.c113
-rw-r--r--builtin-name-rev.c8
-rw-r--r--builtin-pack-objects.c277
-rw-r--r--builtin-pack-refs.c107
-rw-r--r--builtin-prune-packed.c11
-rw-r--r--builtin-prune.c6
-rw-r--r--builtin-push.c16
-rw-r--r--builtin-repo-config.c13
-rw-r--r--builtin-rev-parse.c10
-rw-r--r--builtin-show-branch.c78
-rw-r--r--builtin-show-ref.c147
-rw-r--r--builtin-symbolic-ref.c18
-rw-r--r--builtin-unpack-objects.c159
-rw-r--r--builtin-update-index.c6
-rw-r--r--builtin-update-ref.c20
-rw-r--r--builtin.h9
-rw-r--r--cache-tree.c2
-rw-r--r--cache.h18
-rwxr-xr-xcheck-builtins.sh34
-rw-r--r--combine-diff.c58
-rw-r--r--config.c11
-rwxr-xr-xcontrib/completion/git-completion.bash183
-rw-r--r--contrib/emacs/git.el44
-rw-r--r--contrib/emacs/vc-git.el7
-rw-r--r--daemon.c6
-rw-r--r--describe.c4
-rw-r--r--diff.h2
-rw-r--r--environment.c1
-rw-r--r--fetch-clone.c276
-rw-r--r--fetch-pack.c125
-rw-r--r--fetch.c6
-rw-r--r--fsck-objects.c15
-rwxr-xr-xgit-annotate.perl708
-rwxr-xr-xgit-branch.sh140
-rwxr-xr-xgit-checkout.sh5
-rwxr-xr-xgit-cherry.sh91
-rwxr-xr-xgit-clone.sh64
-rwxr-xr-xgit-commit.sh10
-rwxr-xr-xgit-cvsimport.perl25
-rwxr-xr-xgit-cvsserver.perl14
-rwxr-xr-xgit-fetch.sh27
-rwxr-xr-xgit-merge-one-file.sh16
-rwxr-xr-xgit-merge.sh2
-rwxr-xr-xgit-repack.sh38
-rwxr-xr-xgit-revert.sh11
-rwxr-xr-xgit-send-email.perl5
-rwxr-xr-xgit-svn.perl20
-rwxr-xr-xgit-svnimport.perl29
-rwxr-xr-xgit-tag.sh18
-rw-r--r--git.c6
-rw-r--r--git.spec.in12
-rw-r--r--gitweb/gitweb.css2
-rwxr-xr-xgitweb/gitweb.perl940
-rw-r--r--http-push.c4
-rw-r--r--imap-send.c2
-rw-r--r--index-pack.c786
-rw-r--r--log-tree.c26
-rw-r--r--merge-recursive.c67
-rw-r--r--object.c68
-rw-r--r--object.h6
-rw-r--r--pack.h3
-rw-r--r--pager.c2
-rw-r--r--path.c2
-rw-r--r--quote.c40
-rw-r--r--quote.h4
-rw-r--r--receive-pack.c266
-rw-r--r--refs.c602
-rw-r--r--refs.h21
-rw-r--r--revision.c20
-rw-r--r--send-pack.c5
-rw-r--r--server-info.c4
-rw-r--r--setup.c2
-rw-r--r--sha1_file.c140
-rw-r--r--sha1_name.c56
-rw-r--r--show-index.c2
-rwxr-xr-xt/t1004-read-tree-m-u-wf.sh53
-rwxr-xr-xt/t1400-update-ref.sh7
-rwxr-xr-xt/t3200-branch.sh9
-rwxr-xr-xt/t3210-pack-refs.sh99
-rwxr-xr-xt/t3401-rebase-partial.sh5
-rwxr-xr-xt/t3402-rebase-merge.sh6
-rwxr-xr-xt/t3403-rebase-skip.sh6
-rwxr-xr-xt/t6021-merge-criss-cross.sh6
-rwxr-xr-xt/t6022-merge-rename.sh171
-rwxr-xr-xt/test-lib.sh3
-rw-r--r--tree-diff.c18
-rw-r--r--unpack-trees.c4
-rw-r--r--upload-pack.c16
-rw-r--r--wt-status.c31
-rw-r--r--xdiff/xemit.c5
125 files changed, 6451 insertions, 3073 deletions
diff --git a/.gitignore b/.gitignore
index 25eb4637a..4c8c8e411 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,7 @@ git-fetch
git-fetch-pack
git-findtags
git-fmt-merge-msg
+git-for-each-ref
git-format-patch
git-fsck-objects
git-get-tar-commit-id
@@ -74,6 +75,7 @@ git-name-rev
git-mv
git-pack-redundant
git-pack-objects
+git-pack-refs
git-parse-remote
git-patch-id
git-peek-remote
@@ -105,6 +107,7 @@ git-shortlog
git-show
git-show-branch
git-show-index
+git-show-ref
git-ssh-fetch
git-ssh-pull
git-ssh-push
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 90722c21f..8a3d31631 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -101,8 +101,13 @@ send it "To:" the mailing list, and optionally "cc:" him. If it
is trivially correct or after the list reached a consensus, send
it "To:" the maintainer and optionally "cc:" the list.
+Also note that your maintainer does not actively involve himself in
+maintaining what are in contrib/ hierarchy. When you send fixes and
+enhancements to them, do not forget to "cc: " the person who primarily
+worked on that hierarchy in contrib/.
-(6) Sign your work
+
+(4) Sign your work
To improve tracking of who did what, we've borrowed the
"sign-off" procedure from the Linux kernel project on patches
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 84e38911e..9d3c71c3b 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -71,12 +71,16 @@ core.preferSymlinkRefs::
expect HEAD to be a symbolic link.
core.logAllRefUpdates::
- If true, `git-update-ref` will append a line to
- "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
- of the update. If the file does not exist it will be
- created automatically. This information can be used to
- determine what commit was the tip of a branch "2 days ago".
- This value is false by default (no logging).
+ Updates to a ref <ref> is logged to the file
+ "$GIT_DIR/logs/<ref>", by appending the new and old
+ SHA1, the date/time and the reason of the update, but
+ only when the file exists. If this configuration
+ variable is set to true, missing "$GIT_DIR/logs/<ref>"
+ file is automatically created for branch heads.
+
+ This information can be used to determine what commit
+ was the tip of a branch "2 days ago". This value is
+ false by default (no automated creation of log files).
core.repositoryFormatVersion::
Internal variable identifying the repository format and layout
@@ -230,6 +234,22 @@ pull.octopus::
pull.twohead::
The default merge strategy to use when pulling a single branch.
+remote.<name>.url::
+ The URL of a remote repository. See gitlink:git-fetch[1] or
+ gitlink:git-push[1].
+
+remote.<name>.fetch::
+ The default set of "refspec" for gitlink:git-fetch[1]. See
+ gitlink:git-fetch[1].
+
+remote.<name>.push::
+ The default set of "refspec" for gitlink:git-push[1]. See
+ gitlink:git-push[1].
+
+repack.usedeltabaseoffset::
+ Allow gitlink:git-repack[1] to create packs that uses
+ delta-base offset. Defaults to false.
+
show.difftree::
The default gitlink:git-diff-tree[1] arguments to be used
for gitlink:git-show[1].
@@ -281,7 +301,16 @@ imap::
The configuration variables in the 'imap' section are described
in gitlink:git-imap-send[1].
-receive.denyNonFastforwads::
+receive.unpackLimit::
+ If the number of objects received in a push is below this
+ limit then the objects will be unpacked into loose object
+ files. However if the number of received objects equals or
+ exceeds this limit then the received pack will be stored as
+ a pack, after adding any missing delta bases. Storing the
+ pack from a push can make the push operation complete faster,
+ especially on slow filesystems.
+
+receive.denyNonFastForwards::
If set to true, git-receive-pack will deny a ref update which is
not a fast forward. Use this to prevent such an update via a push,
even if that push is forced. This configuration variable is
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
index 617d8f526..e4520e28e 100644
--- a/Documentation/diff-format.txt
+++ b/Documentation/diff-format.txt
@@ -144,8 +144,10 @@ the file that rename/copy produces, respectively.
dissimilarity index <number>
index <hash>..<hash> <mode>
-3. TAB, LF, and backslash characters in pathnames are
- represented as `\t`, `\n`, and `\\`, respectively.
+3. TAB, LF, double quote and backslash characters in pathnames
+ are represented as `\t`, `\n`, `\"` and `\\`, respectively.
+ If there is need for such substitution then the whole
+ pathname is put in double quotes.
combined diff format
@@ -156,31 +158,91 @@ to produce 'combined diff', which looks like this:
------------
diff --combined describe.c
-@@@ +98,7 @@@
- return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+index fabadb8,cc95eb0..4866510
+--- a/describe.c
++++ b/describe.c
+@@@ -98,20 -98,12 +98,20 @@@
+ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
}
-
+
- static void describe(char *arg)
-static void describe(struct commit *cmit, int last_one)
++static void describe(char *arg, int last_one)
{
- + unsigned char sha1[20];
- + struct commit *cmit;
+ + unsigned char sha1[20];
+ + struct commit *cmit;
+ struct commit_list *list;
+ static int initialized = 0;
+ struct commit_name *n;
+
+ + if (get_sha1(arg, sha1) < 0)
+ + usage(describe_usage);
+ + cmit = lookup_commit_reference(sha1);
+ + if (!cmit)
+ + usage(describe_usage);
+ +
+ if (!initialized) {
+ initialized = 1;
+ for_each_ref(get_name);
------------
+1. It is preceded with a "git diff" header, that looks like
+ this (when '-c' option is used):
+
+ diff --combined file
++
+or like this (when '--cc' option is used):
+
+ diff --c file
+
+2. It is followed by one or more extended header lines
+ (this example shows a merge with two parents):
+
+ index <hash>,<hash>..<hash>
+ mode <mode>,<mode>..<mode>
+ new file mode <mode>
+ deleted file mode <mode>,<mode>
++
+The `mode <mode>,<mode>..<mode>` line appears only if at least one of
+the <mode> is diferent from the rest. Extended headers with
+information about detected contents movement (renames and
+copying detection) are designed to work with diff of two
+<tree-ish> and are not used by combined diff format.
+
+3. It is followed by two-line from-file/to-file header
+
+ --- a/file
+ +++ b/file
++
+Similar to two-line header for traditional 'unified' diff
+format, `/dev/null` is used to signal created or deleted
+files.
+
+4. Chunk header format is modified to prevent people from
+ accidentally feeding it to `patch -p1`. Combined diff format
+ was created for review of merge commit changes, and was not
+ meant for apply. The change is similar to the change in the
+ extended 'index' header:
+
+ @@@ <from-file-range> <from-file-range> <to-file-range> @@@
++
+There are (number of parents + 1) `@` characters in the chunk
+header for combined diff format.
+
Unlike the traditional 'unified' diff format, which shows two
files A and B with a single column that has `-` (minus --
appears in A but removed in B), `+` (plus -- missing in A but
-added to B), or ` ` (space -- unchanged) prefix, this format
+added to B), or `" "` (space -- unchanged) prefix, this format
compares two or more files file1, file2,... with one file X, and
shows how X differs from each of fileN. One column for each of
fileN is prepended to the output line to note how X's line is
different from it.
A `-` character in the column N means that the line appears in
-fileN but it does not appear in the last file. A `+` character
+fileN but it does not appear in the result. A `+` character
in the column N means that the line appears in the last file,
-and fileN does not have that line.
+and fileN does not have that line (in other words, the line was
+added, from the point of view of that parent).
In the above example output, the function signature was changed
from both files (hence two `-` removals from both file1 and
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
index b935c1808..967767189 100644
--- a/Documentation/everyday.txt
+++ b/Documentation/everyday.txt
@@ -1,22 +1,7 @@
Everyday GIT With 20 Commands Or So
===================================
-GIT suite has over 100 commands, and the manual page for each of
-them discusses what the command does and how it is used in
-detail, but until you know what command should be used in order
-to achieve what you want to do, you cannot tell which manual
-page to look at, and if you know that already you do not need
-the manual.
-
-Does that mean you need to know all of them before you can use
-git? Not at all. Depending on the role you play, the set of
-commands you need to know is slightly different, but in any case
-what you need to learn is far smaller than the full set of
-commands to carry out your day-to-day work. This document is to
-serve as a cheat-sheet and a set of pointers for people playing
-various roles.
-
-<<Basic Repository>> commands are needed by people who has a
+<<Basic Repository>> commands are needed by people who have a
repository --- that is everybody, because every working tree of
git is a repository.
@@ -25,28 +10,27 @@ essential for anybody who makes a commit, even for somebody who
works alone.
If you work with other people, you will need commands listed in
-<<Individual Developer (Participant)>> section as well.
+the <<Individual Developer (Participant)>> section as well.
-People who play <<Integrator>> role need to learn some more
+People who play the <<Integrator>> role need to learn some more
commands in addition to the above.
<<Repository Administration>> commands are for system
-administrators who are responsible to care and feed git
-repositories to support developers.
+administrators who are responsible for the care and feeding
+of git repositories.
Basic Repository[[Basic Repository]]
------------------------------------
-Everybody uses these commands to feed and care git repositories.
+Everybody uses these commands to maintain git repositories.
* gitlink:git-init-db[1] or gitlink:git-clone[1] to create a
new repository.
- * gitlink:git-fsck-objects[1] to validate the repository.
+ * gitlink:git-fsck-objects[1] to check the repository for errors.
- * gitlink:git-prune[1] to garbage collect cruft in the
- repository.
+ * gitlink:git-prune[1] to remove unused objects in the repository.
* gitlink:git-repack[1] to pack loose objects for efficiency.
@@ -78,8 +62,8 @@ $ git repack -a -d <1>
$ git prune
------------
+
-<1> pack all the objects reachable from the refs into one pack
-and remove unneeded other packs
+<1> pack all the objects reachable from the refs into one pack,
+then remove the other packs.
Individual Developer (Standalone)[[Individual Developer (Standalone)]]
@@ -93,9 +77,6 @@ following commands.
* gitlink:git-log[1] to see what happened.
- * gitlink:git-whatchanged[1] to find out where things have
- come from.
-
* gitlink:git-checkout[1] and gitlink:git-branch[1] to switch
branches.
@@ -120,7 +101,7 @@ following commands.
Examples
~~~~~~~~
-Extract a tarball and create a working tree and a new repository to keep track of it.::
+Use a tarball as a starting point for a new repository:
+
------------
$ tar zxf frotz.tar.gz
@@ -203,7 +184,7 @@ $ cd my2.6
$ edit/compile/test; git commit -a -s <1>
$ git format-patch origin <2>
$ git pull <3>
-$ git whatchanged -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
+$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
$ git reset --hard ORIG_HEAD <6>
$ git prune <7>
@@ -372,12 +353,19 @@ example of managing a shared central repository.
Examples
~~~~~~~~
+We assume the following in /etc/services::
++
+------------
+$ grep 9418 /etc/services
+git 9418/tcp # Git Version Control System
+------------
+
Run git-daemon to serve /pub/scm from inetd.::
+
------------
$ grep git /etc/inetd.conf
git stream tcp nowait nobody \
- /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
+ /usr/bin/git-daemon git-daemon --inetd --export-all /pub/scm
------------
+
The actual configuration line should be on one line.
@@ -397,7 +385,7 @@ service git
wait = no
user = nobody
server = /usr/bin/git-daemon
- server_args = --inetd --syslog --export-all --base-path=/pub/scm
+ server_args = --inetd --export-all --base-path=/pub/scm
log_on_failure += USERID
}
------------
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index e1f89444a..9891c1d37 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -7,7 +7,7 @@ git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
-'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>]
+'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] <file> [<rev>]
DESCRIPTION
-----------
@@ -45,10 +45,47 @@ OPTIONS
-S, --rev-file <revs-file>::
Use revs from revs-file instead of calling gitlink:git-rev-list[1].
+-f, --show-name::
+ Show filename in the original commit. By default
+ filename is shown if there is any line that came from a
+ file with different name, due to rename detection.
+
+-n, --show-number::
+ Show line number in the original commit (Default: off).
+
+-p, --porcelain::
+ Show in a format designed for machine consumption.
+
-h, --help::
Show help message.
+THE PORCELAIN FORMAT
+--------------------
+
+In this format, each line is output after a header; the
+header at the minumum has the first line which has:
+
+- 40-byte SHA-1 of the commit the line is attributed to;
+- the line number of the line in the original file;
+- the line number of the line in the final file;
+- on a line that starts a group of line from a different
+ commit than the previous one, the number of lines in this
+ group. On subsequent lines this field is absent.
+
+This header line is followed by the following information
+at least once for each commit:
+
+- author name ("author"), email ("author-mail"), time
+ ("author-time"), and timezone ("author-tz"); similarly
+ for committer.
+- filename in the commit the line is attributed to.
+- the first line of the commit log message ("summary").
+
+The contents of the actual line is output after the above
+header, prefixed by a TAB. This is to allow adding more
+header elements later.
+
SEE ALSO
--------
gitlink:git-annotate[1]
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
index 893baaa6f..27b67b81a 100644
--- a/Documentation/git-cherry.txt
+++ b/Documentation/git-cherry.txt
@@ -7,17 +7,33 @@ git-cherry - Find commits not merged upstream
SYNOPSIS
--------
-'git-cherry' [-v] <upstream> [<head>]
+'git-cherry' [-v] <upstream> [<head>] [<limit>]
DESCRIPTION
-----------
The changeset (or "diff") of each commit between the fork-point and <head>
is compared against each commit between the fork-point and <upstream>.
-Every commit with a changeset that doesn't exist in the other branch
-has its id (sha1) reported, prefixed by a symbol. Those existing only
+Every commit that doesn't exist in the <upstream> branch
+has its id (sha1) reported, prefixed by a symbol. The ones that have
+equivalent change already
in the <upstream> branch are prefixed with a minus (-) sign, and those
-that only exist in the <head> branch are prefixed with a plus (+) symbol.
+that only exist in the <head> branch are prefixed with a plus (+) symbol:
+
+ __*__*__*__*__> <upstream>
+ /
+ fork-point
+ \__+__+__-__+__+__-__+__> <head>
+
+
+If a <limit> has been given then the commits along the <head> branch up
+to and including <limit> are not reported:
+
+ __*__*__*__*__> <upstream>
+ /
+ fork-point
+ \__*__*__<limit>__-__+__> <head>
+
Because git-cherry compares the changeset rather than the commit id
(sha1), you can use git-cherry to find out if a commit you made locally
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index f973c6431..86060472a 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -75,6 +75,7 @@ OPTIONS
this option is used, neither the `origin` branch nor the
default `remotes/origin` file is created.
+--origin <name>::
-o <name>::
Instead of using the branch name 'origin' to keep track
of the upstream repository, use <name> instead. Note
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index d562232e5..993adc7c5 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -37,6 +37,8 @@ from `git-fetch`, `git-ls-remote`, and `git-clone`.
This is ideally suited for read-only updates, i.e., pulling from
git repositories.
+An `upload-archive` also exists to serve `git-archive`.
+
OPTIONS
-------
--strict-paths::
@@ -155,8 +157,18 @@ upload-pack::
disable it by setting `daemon.uploadpack` configuration
item to `false`.
+upload-archive::
+ This serves `git-archive --remote`.
+
EXAMPLES
--------
+We assume the following in /etc/services::
++
+------------
+$ grep 9418 /etc/services
+git 9418/tcp # Git Version Control System
+------------
+
git-daemon as inetd server::
To set up `git-daemon` as an inetd service that handles any
repository under the whitelisted set of directories, /pub/foo
@@ -165,8 +177,7 @@ git-daemon as inetd server::
+
------------------------------------------------
git stream tcp nowait nobody /usr/bin/git-daemon
- git-daemon --inetd --verbose
- --syslog --export-all
+ git-daemon --inetd --verbose --export-all
/pub/foo /pub/bar
------------------------------------------------
@@ -179,8 +190,7 @@ git-daemon as inetd server for virtual hosts::
+
------------------------------------------------
git stream tcp nowait nobody /usr/bin/git-daemon
- git-daemon --inetd --verbose
- --syslog --export-all
+ git-daemon --inetd --verbose --export-all
--interpolated-path=/pub/%H%D
/pub/www.example.org/software
/pub/www.example.com/software
diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt
index 9cd43f105..2df581c2c 100644
--- a/Documentation/git-diff-index.txt
+++ b/Documentation/git-diff-index.txt
@@ -54,7 +54,7 @@ If '--cached' is specified, it allows you to ask:
For example, let's say that you have worked on your working directory, updated
some files in the index and are ready to commit. You want to see exactly
-*what* you are going to commit is without having to write a new tree
+*what* you are going to commit, without having to write a new tree
object and compare it that way, and to do that, you just do
git-diff-index --cached HEAD
@@ -68,7 +68,7 @@ matches my working directory. But doing a "git-diff-index" does:
-100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c
+100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c
-You can trivially see that the above is a rename.
+You can see easily that the above is a rename.
In fact, "git-diff-index --cached" *should* always be entirely equivalent to
actually doing a "git-write-tree" and comparing that. Except this one is much
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index bff9aa693..3e6cd880b 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -32,7 +32,8 @@ OPTIONS
-k::
Do not invoke 'git-unpack-objects' on received data, but
create a single packfile out of it instead, and store it
- in the object database.
+ in the object database. If provided twice then the pack is
+ locked against repacking.
--exec=<git-upload-pack>::
Use this to specify the path to 'git-upload-pack' on the
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
new file mode 100644
index 000000000..2bf6aef73
--- /dev/null
+++ b/Documentation/git-for-each-ref.txt
@@ -0,0 +1,185 @@
+git-for-each-ref(1)
+===================
+
+NAME
+----
+git-for-each-ref - Output information on each ref
+
+SYNOPSIS
+--------
+'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python] [--sort=<key>]\* [--format=<format>] [<pattern>]
+
+DESCRIPTION
+-----------
+
+Iterate over all refs that match `<pattern>` and show them
+according to the given `<format>`, after sorting them according
+to the given set of `<key>`. If `<max>` is given, stop after
+showing that many refs. The interporated values in `<format>`
+can optionally be quoted as string literals in the specified
+host language allowing their direct evaluation in that language.
+
+OPTIONS
+-------
+<count>::
+ By default the command shows all refs that match
+ `<pattern>`. This option makes it stop after showing
+ that many refs.
+
+<key>::
+ A field name to sort on. Prefix `-` to sort in
+ descending order of the value. When unspecified,
+ `refname` is used. More than one sort keys can be
+ given.
+
+<format>::
+ A string that interpolates `%(fieldname)` from the
+ object pointed at by a ref being shown. If `fieldname`
+ is prefixed with an asterisk (`*`) and the ref points
+ at a tag object, the value for the field in the object
+ tag refers is used. When unspecified, defaults to
+ `%(objectname) SPC %(objecttype) TAB %(refname)`.
+ It also interpolates `%%` to `%`, and `%xx` where `xx`
+ are hex digits interpolates to character with hex code
+ `xx`; for example `%00` interpolates to `\0` (NUL),
+ `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+
+<pattern>::
+ If given, the name of the ref is matched against this
+ using fnmatch(3). Refs that do not match the pattern
+ are not shown.
+
+--shell, --perl, --python::
+ If given, strings that substitute `%(fieldname)`
+ placeholders are quoted as string literals suitable for
+ the specified host language. This is meant to produce
+ a scriptlet that can directly be `eval`ed.
+
+
+FIELD NAMES
+-----------
+
+Various values from structured fields in referenced objects can
+be used to interpolate into the resulting output, or as sort
+keys.
+
+For all objects, the following names can be used:
+
+refname::
+ The name of the ref (the part after $GIT_DIR/refs/).
+
+objecttype::
+ The type of the object (`blob`, `tree`, `commit`, `tag`).
+
+objectsize::
+ The size of the object (the same as `git-cat-file -s` reports).
+
+objectname::
+ The object name (aka SHA-1).
+
+In addition to the above, for commit and tag objects, the header
+field names (`tree`, `parent`, `object`, `type`, and `tag`) can
+be used to specify the value in the header field.
+
+Fields that have name-email-date tuple as its value (`author`,
+`committer`, and `tagger`) can be suffixed with `name`, `email`,
+and `date` to extract the named component.
+
+The first line of the message in a commit and tag object is
+`subject`, the remaining lines are `body`. The whole message
+is `contents`.
+
+For sorting purposes, fields with numeric values sort in numeric
+order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
+All other fields are used to sort in their byte-value order.
+
+In any case, a field name that refers to a field inapplicable to
+the object referred by the ref does not cause an error. It
+returns an empty string instead.
+
+
+EXAMPLES
+--------
+
+An example directly producing formatted text. Show the most recent
+3 tagged commits::
+
+------------
+#!/bin/sh
+
+git-for-each-ref --count=3 --sort='-*authordate' \
+--format='From: %(*authorname) %(*authoremail)
+Subject: %(*subject)
+Date: %(*authordate)
+Ref: %(*refname)
+
+%(*body)
+' 'refs/tags'
+------------
+
+
+A simple example showing the use of shell eval on the output,
+demonstrating the use of --shell. List the prefixes of all heads::
+------------
+#!/bin/sh
+
+git-for-each-ref --shell --format="ref=%(refname)" refs/heads | \
+while read entry
+do
+ eval "$entry"
+ echo `dirname $ref`
+done
+------------
+
+
+A bit more elaborate report on tags, demonstrating that the format
+may be an entire script::
+------------
+#!/bin/sh
+
+fmt='
+ r=%(refname)
+ t=%(*objecttype)
+ T=${r#refs/tags/}
+
+ o=%(*objectname)
+ n=%(*authorname)
+ e=%(*authoremail)
+ s=%(*subject)
+ d=%(*authordate)
+ b=%(*body)
+
+ kind=Tag
+ if test "z$t" = z
+ then
+ # could be a lightweight tag
+ t=%(objecttype)
+ kind="Lightweight tag"
+ o=%(objectname)
+ n=%(authorname)
+ e=%(authoremail)
+ s=%(subject)
+ d=%(authordate)
+ b=%(body)
+ fi
+ echo "$kind $T points at a $t object $o"
+ if test "z$t" = zcommit
+ then
+ echo "The commit was authored by $n $e
+at $d, and titled
+
+ $s
+
+Its message reads as:
+"
+ echo "$b" | sed -e "s/^/ /"
+ echo
+ fi
+'
+
+eval=`git-for-each-ref --shell --format="$fmt" \
+ --sort='*objecttype' \
+ --sort=-taggerdate \
+ refs/tags`
+eval "$eval"
+------------
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index 71ce55727..2229ee86b 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -8,7 +8,8 @@ git-index-pack - Build pack index file for an existing packed archive
SYNOPSIS
--------
-'git-index-pack' [-o <index-file>] <pack-file>
+'git-index-pack' [-v] [-o <index-file>] <pack-file>
+'git-index-pack' --stdin [--fix-thin] [--keep] [-v] [-o <index-file>] [<pack-file>]
DESCRIPTION
@@ -21,6 +22,9 @@ objects/pack/ directory of a git repository.
OPTIONS
-------
+-v::
+ Be verbose about what is going on, including progress status.
+
-o <index-file>::
Write the generated pack index into the specified
file. Without this option the name of pack index
@@ -29,6 +33,52 @@ OPTIONS
fails if the name of packed archive does not end
with .pack).
+--stdin::
+ When this flag is provided, the pack is read from stdin
+ instead and a copy is then written to <pack-file>. If
+ <pack-file> is not specified, the pack is written to
+ objects/pack/ directory of the current git repository with
+ a default name determined from the pack content. If
+ <pack-file> is not specified consider using --keep to
+ prevent a race condition between this process and
+ gitlink::git-repack[1] .
+
+--fix-thin::
+ It is possible for gitlink:git-pack-objects[1] to build
+ "thin" pack, which records objects in deltified form based on
+ objects not included in the pack to reduce network traffic.
+ Those objects are expected to be present on the receiving end
+ and they must be included in the pack for that pack to be self
+ contained and indexable. Without this option any attempt to
+ index a thin pack will fail. This option only makes sense in
+ conjunction with --stdin.
+
+--keep::
+ Before moving the index into its final destination
+ create an empty .keep file for the associated pack file.
+ This option is usually necessary with --stdin to prevent a
+ simultaneous gitlink:git-repack[1] process from deleting
+ the newly constructed pack and index before refs can be
+ updated to use objects contained in the pack.
+
+--keep='why'::
+ Like --keep create a .keep file before moving the index into
+ its final destination, but rather than creating an empty file
+ place 'why' followed by an LF into the .keep file. The 'why'
+ message can later be searched for within all .keep files to
+ locate any which have outlived their usefulness.
+
+
+Note
+----
+
+Once the index has been created, the list of object names is sorted
+and the SHA1 hash of that list is printed to stdout. If --stdin was
+also used then this is prefixed by either "pack\t", or "keep\t" if a
+new .keep file was successfully created. This is useful to remove a
+.keep file used as a lock to prevent the race with gitlink:git-repack[1]
+mentioned above.
+
Author
------
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index f52e8fa8b..fdc6f9728 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -9,8 +9,8 @@ git-pack-objects - Create a packed archive of objects
SYNOPSIS
--------
[verse]
-'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
- [--local] [--incremental] [--window=N] [--depth=N]
+'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+ [--local] [--incremental] [--window=N] [--depth=N] [--all-progress]
[--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
@@ -47,9 +47,8 @@ base-name::
<base-name> to determine the name of the created file.
When this option is used, the two files are written in
<base-name>-<SHA1>.{pack,idx} files. <SHA1> is a hash
- of object names (currently in random order so it does
- not have any useful meaning) to make the resulting
- filename reasonably unique, and written to the standard
+ of the sorted object names to make the resulting filename
+ based on the pack content, and written to the standard
output of the command.
--stdout::
@@ -100,6 +99,23 @@ base-name::
Only create a packed archive if it would contain at
least one object.
+--progress::
+ Progress status is reported on the standard error stream
+ by default when it is attached to a terminal, unless -q
+ is specified. This flag forces progress status even if
+ the standard error stream is not directed to a terminal.
+
+--all-progress::
+ When --stdout is specified then progress report is
+ displayed during the object count and deltification phases
+ but inhibited during the write-out phase. The reason is
+ that in some cases the output stream is directly linked
+ to another command which may wish to display progress
+ status of its own as it processes incoming pack data.
+ This flag is like --progress except that it forces progress
+ report for the write-out phase as well even if --stdout is
+ used.
+
-q::
This flag makes the command not to report its progress
on the standard error stream.
@@ -111,6 +127,17 @@ base-name::
This flag tells the command not to reuse existing deltas
but compute them from scratch.
+--delta-base-offset::
+ A packed archive can express base object of a delta as
+ either 20-byte object name or as an offset in the
+ stream, but older version of git does not understand the
+ latter. By default, git-pack-objects only uses the
+ former format for better compatibility. This option
+ allows the command to use the latter format for
+ compactness. Depending on the average delta chain
+ length, this option typically shrinks the resulting
+ packfile by 3-5 per-cent.
+
Author
------
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
new file mode 100644
index 000000000..5da510577
--- /dev/null
+++ b/Documentation/git-pack-refs.txt
@@ -0,0 +1,54 @@
+git-pack-refs(1)
+================
+
+NAME
+----
+git-pack-refs - Pack heads and tags for efficient repository access
+
+SYNOPSIS
+--------
+'git-pack-refs' [--all] [--prune]
+
+DESCRIPTION
+-----------
+
+Traditionally, tips of branches and tags (collectively known as
+'refs') were stored one file per ref under `$GIT_DIR/refs`
+directory. While many branch tips tend to be updated often,
+most tags and some branch tips are never updated. When a
+repository has hundreds or thousands of tags, this
+one-file-per-ref format both wastes storage and hurts
+performance.
+
+This command is used to solve the storage and performance
+problem by stashing the refs in a single file,
+`$GIT_DIR/packed-refs`. When a ref is missing from the
+traditional `$GIT_DIR/refs` hierarchy, it is looked up in this
+file and used if found.
+
+Subsequent updates to branches always creates new file under
+`$GIT_DIR/refs` hierarchy.
+
+OPTIONS
+-------
+
+\--all::
+
+The command by default packs all tags and leaves branch tips
+alone. This is because branches are expected to be actively
+developed and packing their tips does not help performance.
+This option causes branch tips to be packed as well. Useful for
+a repository with many branches of historical interests.
+
+\--prune::
+
+After packing the refs, remove loose refs under `$GIT_DIR/refs`
+hierarchy. This should probably become default.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 51577fcbe..2a5aea73b 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -3,7 +3,7 @@ git-pull(1)
NAME
----
-git-pull - Pull and merge from another repository
+git-pull - Pull and merge from another repository or a local branch
SYNOPSIS
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 10f2924f4..03e867a40 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -51,20 +51,69 @@ would be:
D---E---F---G master
------------
-While, starting from the same point, the result of either of the following
-commands:
+The latter form is just a short-hand of `git checkout topic`
+followed by `git rebase master`.
- git-rebase --onto master~1 master
- git-rebase --onto master~1 master topic
+Here is how you would transplant a topic branch based on one
+branch to another, to pretend that you forked the topic branch
+from the latter branch, using `rebase --onto`.
-would be:
+First let's assume your 'topic' is based on branch 'next'.
+For example feature developed in 'topic' depends on some
+functionality which is found in 'next'.
------------
- A'--B'--C' topic
- /
- D---E---F---G master
+ o---o---o---o---o master
+ \
+ o---o---o---o---o next
+ \
+ o---o---o topic
+------------
+
+We would want to make 'topic' forked from branch 'master',
+for example because the functionality 'topic' branch depend on
+got merged into more stable 'master' branch, like this:
+
+------------
+ o---o---o---o---o master
+ | \
+ | o'--o'--o' topic
+ \
+ o---o---o---o---o next
------------
+We can get this using the following command:
+
+ git-rebase --onto master next topic
+
+
+Another example of --onto option is to rebase part of a
+branch. If we have the following situation:
+
+------------
+ H---I---J topicB
+ /
+ E---F---G topicA
+ /
+ A---B---C---D master
+------------
+
+then the command
+
+ git-rebase --onto master topicA topicB
+
+would result in:
+
+------------
+ H'--I'--J' topicB
+ /
+ | E---F---G topicA
+ |/
+ A---B---C---D master
+------------
+
+This is useful when topicB does not depend on topicA.
+
In case of conflict, git-rebase will stop at the first problematic commit
and leave conflict markers in the tree. You can use git diff to locate
the markers (<<<<<<) and make edits to resolve the conflict. For each
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index d2eaa0995..0fa47e3b0 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -67,6 +67,20 @@ OPTIONS
The default value for both --window and --depth is 10.
+Configuration
+-------------
+
+When configuration variable `repack.UseDeltaBaseOffset` is set
+for the repository, the command passes `--delta-base-offset`
+option to `git-pack-objects`; this typically results in slightly
+smaller packs, but the generated packs are incompatible with
+versions of git older than (and including) v1.4.3; do not set
+the variable in a repository that older version of git needs to
+be able to read (this includes repositories from which packs can
+be copied out over http or rsync, and people who obtained packs
+that way can try to use older git with it).
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index 8a1ab61e9..8199615dd 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -3,19 +3,19 @@ git-repo-config(1)
NAME
----
-git-repo-config - Get and set options in .git/config
+git-repo-config - Get and set repository or global options.
SYNOPSIS
--------
[verse]
-'git-repo-config' [type] name [value [value_regex]]
-'git-repo-config' [type] --replace-all name [value [value_regex]]
-'git-repo-config' [type] --get name [value_regex]
-'git-repo-config' [type] --get-all name [value_regex]
-'git-repo-config' [type] --unset name [value_regex]
-'git-repo-config' [type] --unset-all name [value_regex]
-'git-repo-config' -l | --list
+'git-repo-config' [--global] [type] name [value [value_regex]]
+'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
+'git-repo-config' [--global] [type] --get name [value_regex]
+'git-repo-config' [--global] [type] --get-all name [value_regex]
+'git-repo-config' [--global] [type] --unset name [value_regex]
+'git-repo-config' [--global] [type] --unset-all name [value_regex]
+'git-repo-config' [--global] -l | --list
DESCRIPTION
-----------
@@ -41,8 +41,9 @@ This command will fail if:
. Can not write to .git/config,
. no section was provided,
. the section or key is invalid,
-. you try to unset an option which does not exist, or
-. you try to unset/set an option for which multiple lines match.
+. you try to unset an option which does not exist,
+. you try to unset/set an option for which multiple lines match, or
+. you use --global option without $HOME being properly set.
OPTIONS
@@ -64,14 +65,17 @@ OPTIONS
--get-regexp::
Like --get-all, but interprets the name as a regular expression.
+--global::
+ Use global ~/.gitconfig file rather than the repository .git/config.
+
--unset::
- Remove the line matching the key from .git/config.
+ Remove the line matching the key from config file.
--unset-all::
- Remove all matching lines from .git/config.
+ Remove all matching lines from config file.
-l, --list::
- List all variables set in .git/config.
+ List all variables set in config file.
ENVIRONMENT
@@ -79,6 +83,7 @@ ENVIRONMENT
GIT_CONFIG::
Take the configuration from the given file instead of .git/config.
+ Using the "--global" option forces this to ~/.gitconfig.
GIT_CONFIG_LOCAL::
Currently the same as $GIT_CONFIG; when Git will support global
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index cda80b18f..4eaf5a0d1 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -122,14 +122,30 @@ blobs contained in a commit.
your repository whose object name starts with dae86e.
* An output from `git-describe`; i.e. a closest tag, followed by a
- dash, a 'g', and an abbreviated object name.
+ dash, a `g`, and an abbreviated object name.
* A symbolic ref name. E.g. 'master' typically means the commit
object referenced by $GIT_DIR/refs/heads/master. If you
happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean.
+ When ambiguous, a `<name>` is disambiguated by taking the
+ first match in the following rules:
-* A suffix '@' followed by a date specification enclosed in a brace
+ . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
+ useful only for `HEAD`, `FETCH_HEAD` and `MERGE_HEAD`);
+
+ . otherwise, `$GIT_DIR/refs/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/tags/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/heads/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/remotes/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/remotes/<name>/HEAD` if exists.
+
+* A ref followed by the suffix '@' with a date specification
+ enclosed in a brace
pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
of the ref at a prior point in time. This suffix may only be
@@ -146,8 +162,9 @@ blobs contained in a commit.
* A suffix '{tilde}<n>' to a revision parameter means the commit
object that is the <n>th generation grand-parent of the named
commit object, following only the first parent. I.e. rev~3 is
- equivalent to rev{caret}{caret}{caret} which is equivalent to\
- rev{caret}1{caret}1{caret}1.
+ equivalent to rev{caret}{caret}{caret} which is equivalent to
+ rev{caret}1{caret}1{caret}1. See below for a illustration of
+ the usage of this form.
* A suffix '{caret}' followed by an object type name enclosed in
brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 481b3f50e..4c8d907bd 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -66,8 +66,13 @@ The options available are:
all that is output.
--smtp-server::
- If set, specifies the outgoing SMTP server to use. Defaults to
- localhost.
+ If set, specifies the outgoing SMTP server to use. A full
+ pathname of a sendmail-like program can be specified instead;
+ the program must support the `-i` option. Default value can
+ be specified by the 'sendemail.smtpserver' configuration
+ option; the built-in default is `/usr/sbin/sendmail` or
+ `/usr/lib/sendmail` if such program is available, or
+ `localhost` otherwise.
--subject::
Specify the initial subject of the email thread.
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
new file mode 100644
index 000000000..5973a8251
--- /dev/null
+++ b/Documentation/git-show-ref.txt
@@ -0,0 +1,156 @@
+git-show-ref(1)
+===============
+
+NAME
+----
+git-show-ref - List references in a local repository
+
+SYNOPSIS
+--------
+[verse]
+'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
+ [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>...
+
+DESCRIPTION
+-----------
+
+Displays references available in a local repository along with the associated
+commit IDs. Results can be filtered using a pattern and tags can be
+dereferenced into object IDs. Additionally, it can be used to test whether a
+particular ref exists.
+
+Use of this utility is encouraged in favor of directly accessing files under
+in the `.git` directory.
+
+OPTIONS
+-------
+
+-h, --head::
+
+ Show the HEAD reference.
+
+--tags, --heads::
+
+ Limit to only "refs/heads" and "refs/tags", respectively. These
+ options are not mutually exclusive; when given both, references stored
+ in "refs/heads" and "refs/tags" are displayed.
+
+-d, --dereference::
+
+ Dereference tags into object IDs as well. They will be shown with "^{}"
+ appended.
+
+-s, --hash::
+
+ Only show the SHA1 hash, not the reference name. When also using
+ --dereference the dereferenced tag will still be shown after the SHA1.
+
+--verify::
+
+ Enable stricter reference checking by requiring an exact ref path.
+ Aside from returning an error code of 1, it will also print an error
+ message if '--quiet' was not specified.
+
+--abbrev, --abbrev=len::
+
+ Abbreviate the object name. When using `--hash`, you do
+ not have to say `--hash --abbrev`; `--hash=len` would do.
+
+-q, --quiet::
+
+ Do not print any results to stdout. When combined with '--verify' this
+ can be used to silently check if a reference exists.
+
+<pattern>::
+
+ Show references matching one or more patterns.
+
+OUTPUT
+------
+
+The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'.
+
+-----------------------------------------------------------------------------
+$ git show-ref --head --dereference
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin
+3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c
+6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{}
+055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4
+423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{}
+...
+-----------------------------------------------------------------------------
+
+When using --hash (and not --dereference) the output format is: '<SHA-1 ID>'
+
+-----------------------------------------------------------------------------
+$ git show-ref --heads --hash
+2e3ba0114a1f52b47df29743d6915d056be13278
+185008ae97960c8d551adcd9e23565194651b5d1
+03adf42c988195b50e1a1935ba5fcbc39b2b029b
+...
+-----------------------------------------------------------------------------
+
+EXAMPLE
+-------
+
+To show all references called "master", whether tags or heads or anything
+else, and regardless of how deep in the reference naming hierarchy they are,
+use:
+
+-----------------------------------------------------------------------------
+ git show-ref master
+-----------------------------------------------------------------------------
+
+This will show "refs/heads/master" but also "refs/remote/other-repo/master",
+if such references exists.
+
+When using the '--verify' flag, the command requires an exact path:
+
+-----------------------------------------------------------------------------
+ git show-ref --verify refs/heads/master
+-----------------------------------------------------------------------------
+
+will only match the exact branch called "master".
+
+If nothing matches, gitlink:git-show-ref[1] will return an error code of 1,
+and in the case of verification, it will show an error message.
+
+For scripting, you can ask it to be quiet with the "--quiet" flag, which
+allows you to do things like
+
+-----------------------------------------------------------------------------
+ git-show-ref --quiet --verify -- "refs/heads/$headname" ||
+ echo "$headname is not a valid branch"
+-----------------------------------------------------------------------------
+
+to check whether a particular branch exists or not (notice how we don't
+actually want to show any results, and we want to use the full refname for it
+in order to not trigger the problem with ambiguous partial matches).
+
+To show only tags, or only proper branch heads, use "--tags" and/or "--heads"
+respectively (using both means that it shows tags and heads, but not other
+random references under the refs/ subdirectory).
+
+To do automatic tag object dereferencing, use the "-d" or "--dereference"
+flag, so you can do
+
+-----------------------------------------------------------------------------
+ git show-ref --tags --dereference
+-----------------------------------------------------------------------------
+
+to get a listing of all tags together with what they dereference.
+
+SEE ALSO
+--------
+gitlink:git-ls-remote[1], gitlink:git-peek-remote[1]
+
+AUTHORS
+-------
+Written by Linus Torvalds <torvalds@osdl.org>.
+Man page by Jonas Fonseca <fonseca@diku.dk>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index e062030e9..71bcb7954 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
SYNOPSIS
--------
-'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
DESCRIPTION
-----------
@@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that
the current value of the <ref> matches <oldvalue>.
E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
updates the master branch head to <newvalue> only if its current
-value is <oldvalue>.
+value is <oldvalue>. You can specify 40 "0" or an empty string
+as <oldvalue> to make sure that the ref you are creating does
+not exist.
It also allows a "ref" file to be a symbolic pointer to another
ref file by starting with the four-byte header sequence of
@@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a
ref symlink to some other tree, if you have copied a whole
archive by creating a symlink tree).
+With `-d` flag, it deletes the named <ref> after verifying it
+still contains <oldvalue>.
+
+
Logging Updates
---------------
If config parameter "core.logAllRefUpdates" is true or the file
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 7074e3245..4facf2309 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -72,185 +72,6 @@ GIT COMMANDS
We divide git into high level ("porcelain") commands and low level
("plumbing") commands.
-Low-level commands (plumbing)
------------------------------
-
-Although git includes its
-own porcelain layer, its low-level commands are sufficient to support
-development of alternative porcelains. Developers of such porcelains
-might start by reading about gitlink:git-update-index[1] and
-gitlink:git-read-tree[1].
-
-We divide the low-level commands into commands that manipulate objects (in
-the repository, index, and working tree), commands that interrogate and
-compare objects, and commands that move objects and references between
-repositories.
-
-Manipulation commands
-~~~~~~~~~~~~~~~~~~~~~
-gitlink:git-apply[1]::
- Reads a "diff -up1" or git generated patch file and
- applies it to the working tree.
-
-gitlink:git-checkout-index[1]::
- Copy files from the index to the working tree.
-
-gitlink:git-commit-tree[1]::
- Creates a new commit object.
-
-gitlink:git-hash-object[1]::
- Computes the object ID from a file.
-
-gitlink:git-index-pack[1]::
- Build pack idx file for an existing packed archive.
-
-gitlink:git-init-db[1]::
- Creates an empty git object database, or reinitialize an
- existing one.
-
-gitlink:git-merge-index[1]::
- Runs a merge for files needing merging.
-
-gitlink:git-mktag[1]::
- Creates a tag object.
-
-gitlink:git-mktree[1]::
- Build a tree-object from ls-tree formatted text.
-
-gitlink:git-pack-objects[1]::
- Creates a packed archive of objects.
-
-gitlink:git-prune-packed[1]::
- Remove extra objects that are already in pack files.
-
-gitlink:git-read-tree[1]::
- Reads tree information into the index.
-
-gitlink:git-repo-config[1]::
- Get and set options in .git/config.
-
-gitlink:git-unpack-objects[1]::
- Unpacks objects out of a packed archive.
-
-gitlink:git-update-index[1]::
- Registers files in the working tree to the index.
-
-gitlink:git-write-tree[1]::
- Creates a tree from the index.
-
-
-Interrogation commands
-~~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-cat-file[1]::
- Provide content or type/size information for repository objects.
-
-gitlink:git-describe[1]::
- Show the most recent tag that is reachable from a commit.
-
-gitlink:git-diff-index[1]::
- Compares content and mode of blobs between the index and repository.
-
-gitlink:git-diff-files[1]::
- Compares files in the working tree and the index.
-
-gitlink:git-diff-stages[1]::
- Compares two "merge stages" in the index.
-
-gitlink:git-diff-tree[1]::
- Compares the content and mode of blobs found via two tree objects.
-
-gitlink:git-fsck-objects[1]::
- Verifies the connectivity and validity of the objects in the database.
-
-gitlink:git-ls-files[1]::
- Information about files in the index and the working tree.
-
-gitlink:git-ls-tree[1]::
- Displays a tree object in human readable form.
-
-gitlink:git-merge-base[1]::
- Finds as good common ancestors as possible for a merge.
-
-gitlink:git-name-rev[1]::
- Find symbolic names for given revs.
-
-gitlink:git-pack-redundant[1]::
- Find redundant pack files.
-
-gitlink:git-rev-list[1]::
- Lists commit objects in reverse chronological order.
-
-gitlink:git-show-index[1]::
- Displays contents of a pack idx file.
-
-gitlink:git-tar-tree[1]::
- Creates a tar archive of the files in the named tree object.
-
-gitlink:git-unpack-file[1]::
- Creates a temporary file with a blob's contents.
-
-gitlink:git-var[1]::
- Displays a git logical variable.
-
-gitlink:git-verify-pack[1]::
- Validates packed git archive files.
-
-In general, the interrogate commands do not touch the files in
-the working tree.
-
-
-Synching repositories
-~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-fetch-pack[1]::
- Updates from a remote repository (engine for ssh and
- local transport).
-
-gitlink:git-http-fetch[1]::
- Downloads a remote git repository via HTTP by walking
- commit chain.
-
-gitlink:git-local-fetch[1]::
- Duplicates another git repository on a local system by
- walking commit chain.
-
-gitlink:git-peek-remote[1]::
- Lists references on a remote repository using
- upload-pack protocol (engine for ssh and local
- transport).
-
-gitlink:git-receive-pack[1]::
- Invoked by 'git-send-pack' to receive what is pushed to it.
-
-gitlink:git-send-pack[1]::
- Pushes to a remote repository, intelligently.
-
-gitlink:git-http-push[1]::
- Push missing objects using HTTP/DAV.
-
-gitlink:git-shell[1]::
- Restricted shell for GIT-only SSH access.
-
-gitlink:git-ssh-fetch[1]::
- Pulls from a remote repository over ssh connection by
- walking commit chain.
-
-gitlink:git-ssh-upload[1]::
- Helper "server-side" program used by git-ssh-fetch.
-
-gitlink:git-update-server-info[1]::
- Updates auxiliary information on a dumb server to help
- clients discover references and packs on it.
-
-gitlink:git-upload-archive[1]::
- Invoked by 'git-archive' to send a generated archive.
-
-gitlink:git-upload-pack[1]::
- Invoked by 'git-fetch-pack' to push
- what are asked for.
-
-
High-level commands (porcelain)
-------------------------------
@@ -320,8 +141,11 @@ gitlink:git-merge[1]::
gitlink:git-mv[1]::
Move or rename a file, a directory, or a symlink.
+gitlink:git-pack-refs[1]::
+ Pack heads and tags for efficient repository access.
+
gitlink:git-pull[1]::
- Fetch from and merge with a remote repository.
+ Fetch from and merge with a remote repository or a local branch.
gitlink:git-push[1]::
Update remote refs along with associated objects.
@@ -491,6 +315,191 @@ gitlink:git-stripspace[1]::
Filter out empty lines.
+Low-level commands (plumbing)
+-----------------------------
+
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+development of alternative porcelains. Developers of such porcelains
+might start by reading about gitlink:git-update-index[1] and
+gitlink:git-read-tree[1].
+
+We divide the low-level commands into commands that manipulate objects (in
+the repository, index, and working tree), commands that interrogate and
+compare objects, and commands that move objects and references between
+repositories.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+gitlink:git-apply[1]::
+ Reads a "diff -up1" or git generated patch file and
+ applies it to the working tree.
+
+gitlink:git-checkout-index[1]::
+ Copy files from the index to the working tree.
+
+gitlink:git-commit-tree[1]::
+ Creates a new commit object.
+
+gitlink:git-hash-object[1]::
+ Computes the object ID from a file.
+
+gitlink:git-index-pack[1]::
+ Build pack idx file for an existing packed archive.
+
+gitlink:git-init-db[1]::
+ Creates an empty git object database, or reinitialize an
+ existing one.
+
+gitlink:git-merge-index[1]::
+ Runs a merge for files needing merging.
+
+gitlink:git-mktag[1]::
+ Creates a tag object.
+
+gitlink:git-mktree[1]::
+ Build a tree-object from ls-tree formatted text.
+
+gitlink:git-pack-objects[1]::
+ Creates a packed archive of objects.
+
+gitlink:git-prune-packed[1]::
+ Remove extra objects that are already in pack files.
+
+gitlink:git-read-tree[1]::
+ Reads tree information into the index.
+
+gitlink:git-repo-config[1]::
+ Get and set options in .git/config.
+
+gitlink:git-unpack-objects[1]::
+ Unpacks objects out of a packed archive.
+
+gitlink:git-update-index[1]::
+ Registers files in the working tree to the index.
+
+gitlink:git-write-tree[1]::
+ Creates a tree from the index.
+
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-cat-file[1]::
+ Provide content or type/size information for repository objects.
+
+gitlink:git-describe[1]::
+ Show the most recent tag that is reachable from a commit.
+
+gitlink:git-diff-index[1]::
+ Compares content and mode of blobs between the index and repository.
+
+gitlink:git-diff-files[1]::
+ Compares files in the working tree and the index.
+
+gitlink:git-diff-stages[1]::
+ Compares two "merge stages" in the index.
+
+gitlink:git-diff-tree[1]::
+ Compares the content and mode of blobs found via two tree objects.
+
+gitlink:git-for-each-ref[1]::
+ Output information on each ref.
+
+gitlink:git-fsck-objects[1]::
+ Verifies the connectivity and validity of the objects in the database.
+
+gitlink:git-ls-files[1]::
+ Information about files in the index and the working tree.
+
+gitlink:git-ls-tree[1]::
+ Displays a tree object in human readable form.
+
+gitlink:git-merge-base[1]::
+ Finds as good common ancestors as possible for a merge.
+
+gitlink:git-name-rev[1]::
+ Find symbolic names for given revs.
+
+gitlink:git-pack-redundant[1]::
+ Find redundant pack files.
+
+gitlink:git-rev-list[1]::
+ Lists commit objects in reverse chronological order.
+
+gitlink:git-show-index[1]::
+ Displays contents of a pack idx file.
+
+gitlink:git-show-ref[1]::
+ List references in a local repository.
+
+gitlink:git-tar-tree[1]::
+ Creates a tar archive of the files in the named tree object.
+
+gitlink:git-unpack-file[1]::
+ Creates a temporary file with a blob's contents.
+
+gitlink:git-var[1]::
+ Displays a git logical variable.
+
+gitlink:git-verify-pack[1]::
+ Validates packed git archive files.
+
+In general, the interrogate commands do not touch the files in
+the working tree.
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-fetch-pack[1]::
+ Updates from a remote repository (engine for ssh and
+ local transport).
+
+gitlink:git-http-fetch[1]::
+ Downloads a remote git repository via HTTP by walking
+ commit chain.
+
+gitlink:git-local-fetch[1]::
+ Duplicates another git repository on a local system by
+ walking commit chain.
+
+gitlink:git-peek-remote[1]::
+ Lists references on a remote repository using
+ upload-pack protocol (engine for ssh and local
+ transport).
+
+gitlink:git-receive-pack[1]::
+ Invoked by 'git-send-pack' to receive what is pushed to it.
+
+gitlink:git-send-pack[1]::
+ Pushes to a remote repository, intelligently.
+
+gitlink:git-http-push[1]::
+ Push missing objects using HTTP/DAV.
+
+gitlink:git-shell[1]::
+ Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
+ Pulls from a remote repository over ssh connection by
+ walking commit chain.
+
+gitlink:git-ssh-upload[1]::
+ Helper "server-side" program used by git-ssh-fetch.
+
+gitlink:git-update-server-info[1]::
+ Updates auxiliary information on a dumb server to help
+ clients discover references and packs on it.
+
+gitlink:git-upload-archive[1]::
+ Invoked by 'git-archive' to send a generated archive.
+
+gitlink:git-upload-pack[1]::
+ Invoked by 'git-fetch-pack' to push
+ what are asked for.
+
+
Configuration Mechanism
-----------------------
@@ -565,6 +574,9 @@ HEAD::
a valid head 'name'
(i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
+For a more complete list of ways to spell object names, see
+"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+
File/Directory Structure
------------------------
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index 26ecba53f..670827c32 100644
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
@@ -51,6 +51,14 @@ lines are used for `git-push` and `git-fetch`/`git-pull`,
respectively. Multiple `Push:` and `Pull:` lines may
be specified for additional branch mappings.
+Or, equivalently, in the `$GIT_DIR/config` (note the use
+of `fetch` instead of `Pull:`):
+
+[remote "<remote>"]
+ url = <url>
+ push = <refspec>
+ fetch = <refspec>
+
The name of a file in `$GIT_DIR/branches` directory can be
specified as an older notation short-hand; the named
file should contain a single line, a URL in one of the
diff --git a/Makefile b/Makefile
index 461fef636..a042df4a2 100644
--- a/Makefile
+++ b/Makefile
@@ -132,6 +132,8 @@ GITWEB_HOMETEXT = indextext.html
GITWEB_CSS = gitweb.css
GITWEB_LOGO = git-logo.png
GITWEB_FAVICON = git-favicon.png
+GITWEB_SITE_HEADER =
+GITWEB_SITE_FOOTER =
export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
@@ -156,8 +158,8 @@ BASIC_CFLAGS =
BASIC_LDFLAGS =
SCRIPT_SH = \
- git-bisect.sh git-branch.sh git-checkout.sh \
- git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
+ git-bisect.sh git-checkout.sh \
+ git-clean.sh git-clone.sh git-commit.sh \
git-fetch.sh \
git-ls-remote.sh \
git-merge-one-file.sh git-parse-remote.sh \
@@ -173,7 +175,7 @@ SCRIPT_SH = \
SCRIPT_PERL = \
git-archimport.perl git-cvsimport.perl git-relink.perl \
git-shortlog.perl git-rerere.perl \
- git-annotate.perl git-cvsserver.perl \
+ git-cvsserver.perl \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
@@ -185,15 +187,12 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
git-cherry-pick git-status git-instaweb
-# The ones that do not have to link with lcrypto, lz nor xdiff.
-SIMPLE_PROGRAMS = \
- git-daemon$X
-
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS = \
git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
git-hash-object$X git-index-pack$X git-local-fetch$X \
git-merge-base$X \
+ git-daemon$X \
git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
git-peek-remote$X git-receive-pack$X \
git-send-pack$X git-shell$X \
@@ -210,12 +209,12 @@ PROGRAMS = \
EXTRA_PROGRAMS =
BUILT_INS = \
- git-format-patch$X git-show$X git-whatchanged$X \
+ git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
git-get-tar-commit-id$X \
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \
+ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) \
git-merge-recur$X
# Backward compatibility -- to be removed after 1.0
@@ -258,15 +257,17 @@ LIB_OBJS = \
quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
- fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
+ revision.o pager.o tree-walk.o xdiff-interface.o \
write_or_die.o trace.o list-objects.o grep.o \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
color.o wt-status.o archive-zip.o archive-tar.o
BUILTIN_OBJS = \
builtin-add.o \
+ builtin-annotate.o \
builtin-apply.o \
builtin-archive.o \
+ builtin-branch.o \
builtin-cat-file.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
@@ -278,6 +279,7 @@ BUILTIN_OBJS = \
builtin-diff-stages.o \
builtin-diff-tree.o \
builtin-fmt-merge-msg.o \
+ builtin-for-each-ref.o \
builtin-grep.o \
builtin-init-db.o \
builtin-log.o \
@@ -307,7 +309,9 @@ BUILTIN_OBJS = \
builtin-update-ref.o \
builtin-upload-archive.o \
builtin-verify-pack.o \
- builtin-write-tree.o
+ builtin-write-tree.o \
+ builtin-show-ref.o \
+ builtin-pack-refs.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
EXTLIBS = -lz
@@ -480,11 +484,9 @@ ifdef NEEDS_LIBICONV
endif
ifdef NEEDS_SOCKET
EXTLIBS += -lsocket
- SIMPLE_LIB += -lsocket
endif
ifdef NEEDS_NSL
EXTLIBS += -lnsl
- SIMPLE_LIB += -lnsl
endif
ifdef NO_D_TYPE_IN_DIRENT
BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
@@ -676,6 +678,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
-e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
-e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
-e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
$< >$@+
chmod +x $@+
mv $@+ $@
@@ -729,11 +733,6 @@ endif
git-%$X: %.o $(GITLIBS)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-$(SIMPLE_PROGRAMS) : $(LIB_FILE)
-$(SIMPLE_PROGRAMS) : git-%$X : %.o
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIB_FILE) $(SIMPLE_LIB)
-
ssh-pull.o: ssh-fetch.c
ssh-push.o: ssh-upload.c
git-local-fetch$X: fetch.o
@@ -934,3 +933,8 @@ check-docs::
*) echo "no link: $$v";; \
esac ; \
done | sort
+
+### Make sure built-ins do not have dups and listed in git.c
+#
+check-builtins::
+ ./check-builtins.sh
diff --git a/archive.h b/archive.h
index 16dcdb875..6838dc788 100644
--- a/archive.h
+++ b/archive.h
@@ -25,8 +25,6 @@ struct archiver {
parse_extra_args_fn_t parse_extra;
};
-extern struct archiver archivers[];
-
extern int parse_archive_args(int argc,
const char **argv,
struct archiver *ar);
diff --git a/blame.c b/blame.c
index d0e506c02..1144c8533 100644
--- a/blame.c
+++ b/blame.c
@@ -17,19 +17,26 @@
#include "diffcore.h"
#include "revision.h"
#include "xdiff-interface.h"
+#include "quote.h"
+#ifndef DEBUG
#define DEBUG 0
+#endif
-static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n"
- " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
- " -l, --long Show long commit SHA1 (Default: off)\n"
- " -t, --time Show raw timestamp (Default: off)\n"
- " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n"
- " -h, --help This message";
+static const char blame_usage[] =
+"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] file [commit]\n"
+" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+" -l, --long Show long commit SHA1 (Default: off)\n"
+" -t, --time Show raw timestamp (Default: off)\n"
+" -f, --show-name Show original filename (Default: auto)\n"
+" -n, --show-number Show original linenumber (Default: off)\n"
+" -p, --porcelain Show in a format designed for machine consumption\n"
+" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"
+" -h, --help This message";
static struct commit **blame_lines;
static int num_blame_lines;
-static char* blame_contents;
+static char *blame_contents;
static int blame_len;
struct util_info {
@@ -38,9 +45,10 @@ struct util_info {
char *buf;
unsigned long size;
int num_lines;
- const char* pathname;
+ const char *pathname;
+ unsigned meta_given:1;
- void* topo_data;
+ void *topo_data;
};
struct chunk {
@@ -157,11 +165,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
unsigned mode, int stage);
static unsigned char blob_sha1[20];
-static const char* blame_file;
+static const char *blame_file;
static int get_blob_sha1(struct tree *t, const char *pathname,
unsigned char *sha1)
{
- int i;
const char *pathspec[2];
blame_file = pathname;
pathspec[0] = pathname;
@@ -169,12 +176,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
hashclr(blob_sha1);
read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
- for (i = 0; i < 20; i++) {
- if (blob_sha1[i] != 0)
- break;
- }
-
- if (i == 20)
+ if (is_null_sha1(blob_sha1))
return -1;
hashcpy(sha1, blob_sha1);
@@ -234,6 +236,9 @@ static void print_map(struct commit *cmit, struct commit *other)
util2->num_lines ? util->num_lines : util2->num_lines;
int num;
+ if (print_map == NULL)
+ ; /* to avoid "unused function" warning */
+
for (i = 0; i < max; i++) {
printf("i: %d ", i);
num = -1;
@@ -241,7 +246,8 @@ static void print_map(struct commit *cmit, struct commit *other)
if (i < util->num_lines) {
num = util->line_map[i];
printf("%d\t", num);
- } else
+ }
+ else
printf("\t");
if (i < util2->num_lines) {
@@ -249,7 +255,8 @@ static void print_map(struct commit *cmit, struct commit *other)
printf("%d\t", num2);
if (num != -1 && num2 != num)
printf("---");
- } else
+ }
+ else
printf("\t");
printf("\n");
@@ -268,12 +275,12 @@ static void fill_line_map(struct commit *commit, struct commit *other,
int cur_chunk = 0;
int i1, i2;
- if (p->num && DEBUG)
- print_patch(p);
-
- if (DEBUG)
+ if (DEBUG) {
+ if (p->num)
+ print_patch(p);
printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
util2->num_lines);
+ }
for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
struct chunk *chunk = NULL;
@@ -295,7 +302,8 @@ static void fill_line_map(struct commit *commit, struct commit *other,
i2 += chunk->len2;
cur_chunk++;
- } else {
+ }
+ else {
if (i2 >= util2->num_lines)
break;
@@ -329,19 +337,15 @@ static int map_line(struct commit *commit, int line)
return info->line_map[line];
}
-static struct util_info* get_util(struct commit *commit)
+static struct util_info *get_util(struct commit *commit)
{
struct util_info *util = commit->util;
if (util)
return util;
- util = xmalloc(sizeof(struct util_info));
- util->buf = NULL;
- util->size = 0;
- util->line_map = NULL;
+ util = xcalloc(1, sizeof(struct util_info));
util->num_lines = -1;
- util->pathname = NULL;
commit->util = util;
return util;
}
@@ -371,7 +375,7 @@ static void alloc_line_map(struct commit *commit)
if (util->buf[i] == '\n')
util->num_lines++;
}
- if(util->buf[util->size - 1] != '\n')
+ if (util->buf[util->size - 1] != '\n')
util->num_lines++;
util->line_map = xmalloc(sizeof(int) * util->num_lines);
@@ -380,9 +384,9 @@ static void alloc_line_map(struct commit *commit)
util->line_map[i] = -1;
}
-static void init_first_commit(struct commit* commit, const char* filename)
+static void init_first_commit(struct commit *commit, const char *filename)
{
- struct util_info* util = commit->util;
+ struct util_info *util = commit->util;
int i;
util->pathname = filename;
@@ -397,18 +401,17 @@ static void init_first_commit(struct commit* commit, const char* filename)
util->line_map[i] = i;
}
-
static void process_commits(struct rev_info *rev, const char *path,
- struct commit** initial)
+ struct commit **initial)
{
int i;
- struct util_info* util;
+ struct util_info *util;
int lines_left;
int *blame_p;
int *new_lines;
int new_lines_len;
- struct commit* commit = get_revision(rev);
+ struct commit *commit = get_revision(rev);
assert(commit);
init_first_commit(commit, path);
@@ -444,7 +447,7 @@ static void process_commits(struct rev_info *rev, const char *path,
parents != NULL; parents = parents->next)
num_parents++;
- if(num_parents == 0)
+ if (num_parents == 0)
*initial = commit;
if (fill_util_info(commit))
@@ -505,13 +508,12 @@ static void process_commits(struct rev_info *rev, const char *path,
} while ((commit = get_revision(rev)) != NULL);
}
-
-static int compare_tree_path(struct rev_info* revs,
- struct commit* c1, struct commit* c2)
+static int compare_tree_path(struct rev_info *revs,
+ struct commit *c1, struct commit *c2)
{
int ret;
- const char* paths[2];
- struct util_info* util = c2->util;
+ const char *paths[2];
+ struct util_info *util = c2->util;
paths[0] = util->pathname;
paths[1] = NULL;
@@ -522,12 +524,11 @@ static int compare_tree_path(struct rev_info* revs,
return ret;
}
-
-static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
- const char* path)
+static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1,
+ const char *path)
{
int ret;
- const char* paths[2];
+ const char *paths[2];
paths[0] = path;
paths[1] = NULL;
@@ -538,9 +539,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
return ret;
}
-static const char* find_rename(struct commit* commit, struct commit* parent)
+static const char *find_rename(struct commit *commit, struct commit *parent)
{
- struct util_info* cutil = commit->util;
+ struct util_info *cutil = commit->util;
struct diff_options diff_opts;
const char *paths[1];
int i;
@@ -566,9 +567,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent)
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
- if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
+ if (p->status == 'R' &&
+ !strcmp(p->one->path, cutil->pathname)) {
if (DEBUG)
- printf("rename %s -> %s\n", p->one->path, p->two->path);
+ printf("rename %s -> %s\n",
+ p->one->path, p->two->path);
return p->two->path;
}
}
@@ -584,7 +587,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
return;
if (!commit->parents) {
- struct util_info* util = commit->util;
+ struct util_info *util = commit->util;
if (!same_tree_as_empty_path(revs, commit->tree,
util->pathname))
commit->object.flags |= TREECHANGE;
@@ -610,17 +613,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
case REV_TREE_NEW:
{
-
- struct util_info* util = commit->util;
+ struct util_info *util = commit->util;
if (revs->remove_empty_trees &&
same_tree_as_empty_path(revs, p->tree,
util->pathname)) {
- const char* new_name = find_rename(commit, p);
+ const char *new_name = find_rename(commit, p);
if (new_name) {
- struct util_info* putil = get_util(p);
+ struct util_info *putil = get_util(p);
if (!putil->pathname)
putil->pathname = xstrdup(new_name);
- } else {
+ }
+ else {
*pp = parent->next;
continue;
}
@@ -641,47 +644,106 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREECHANGE;
}
-
struct commit_info
{
- char* author;
- char* author_mail;
+ char *author;
+ char *author_mail;
unsigned long author_time;
- char* author_tz;
+ char *author_tz;
+
+ /* filled only when asked for details */
+ char *committer;
+ char *committer_mail;
+ unsigned long committer_time;
+ char *committer_tz;
+
+ char *summary;
};
-static void get_commit_info(struct commit* commit, struct commit_info* ret)
+static void get_ac_line(const char *inbuf, const char *what,
+ int bufsz, char *person, char **mail,
+ unsigned long *time, char **tz)
{
int len;
- char* tmp;
- static char author_buf[1024];
-
- tmp = strstr(commit->buffer, "\nauthor ") + 8;
- len = strchr(tmp, '\n') - tmp;
- ret->author = author_buf;
- memcpy(ret->author, tmp, len);
+ char *tmp, *endp;
+
+ tmp = strstr(inbuf, what);
+ if (!tmp)
+ goto error_out;
+ tmp += strlen(what);
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ len = strlen(tmp);
+ else
+ len = endp - tmp;
+ if (bufsz <= len) {
+ error_out:
+ /* Ugh */
+ person = *mail = *tz = "(unknown)";
+ *time = 0;
+ return;
+ }
+ memcpy(person, tmp, len);
- tmp = ret->author;
+ tmp = person;
tmp += len;
*tmp = 0;
- while(*tmp != ' ')
+ while (*tmp != ' ')
tmp--;
- ret->author_tz = tmp+1;
+ *tz = tmp+1;
*tmp = 0;
- while(*tmp != ' ')
+ while (*tmp != ' ')
tmp--;
- ret->author_time = strtoul(tmp, NULL, 10);
+ *time = strtoul(tmp, NULL, 10);
*tmp = 0;
- while(*tmp != ' ')
+ while (*tmp != ' ')
tmp--;
- ret->author_mail = tmp + 1;
-
+ *mail = tmp + 1;
*tmp = 0;
}
-static const char* format_time(unsigned long time, const char* tz_str,
+static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed)
+{
+ int len;
+ char *tmp, *endp;
+ static char author_buf[1024];
+ static char committer_buf[1024];
+ static char summary_buf[1024];
+
+ ret->author = author_buf;
+ get_ac_line(commit->buffer, "\nauthor ",
+ sizeof(author_buf), author_buf, &ret->author_mail,
+ &ret->author_time, &ret->author_tz);
+
+ if (!detailed)
+ return;
+
+ ret->committer = committer_buf;
+ get_ac_line(commit->buffer, "\ncommitter ",
+ sizeof(committer_buf), committer_buf, &ret->committer_mail,
+ &ret->committer_time, &ret->committer_tz);
+
+ ret->summary = summary_buf;
+ tmp = strstr(commit->buffer, "\n\n");
+ if (!tmp) {
+ error_out:
+ sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+ return;
+ }
+ tmp += 2;
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ goto error_out;
+ len = endp - tmp;
+ if (len >= sizeof(summary_buf))
+ goto error_out;
+ memcpy(summary_buf, tmp, len);
+ summary_buf[len] = 0;
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
int show_raw_time)
{
static char time_buf[128];
@@ -706,15 +768,15 @@ static const char* format_time(unsigned long time, const char* tz_str,
return time_buf;
}
-static void topo_setter(struct commit* c, void* data)
+static void topo_setter(struct commit *c, void *data)
{
- struct util_info* util = c->util;
+ struct util_info *util = c->util;
util->topo_data = data;
}
-static void* topo_getter(struct commit* c)
+static void *topo_getter(struct commit *c)
{
- struct util_info* util = c->util;
+ struct util_info *util = c->util;
return util->topo_data;
}
@@ -737,6 +799,101 @@ static int read_ancestry(const char *graft_file,
return 0;
}
+static int lineno_width(int lines)
+{
+ int i, width;
+
+ for (width = 1, i = 10; i <= lines + 1; width++)
+ i *= 10;
+ return width;
+}
+
+static int find_orig_linenum(struct util_info *u, int lineno)
+{
+ int i;
+
+ for (i = 0; i < u->num_lines; i++)
+ if (lineno == u->line_map[i])
+ return i + 1;
+ return 0;
+}
+
+static void emit_meta(struct commit *c, int lno,
+ int sha1_len, int compatibility, int porcelain,
+ int show_name, int show_number, int show_raw_time,
+ int longest_file, int longest_author,
+ int max_digits, int max_orig_digits)
+{
+ struct util_info *u;
+ int lineno;
+ struct commit_info ci;
+
+ u = c->util;
+ lineno = find_orig_linenum(u, lno);
+
+ if (porcelain) {
+ int group_size = -1;
+ struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1];
+ if (cc != c) {
+ /* This is the beginning of this group */
+ int i;
+ for (i = lno + 1; i < num_blame_lines; i++)
+ if (blame_lines[i] != c)
+ break;
+ group_size = i - lno;
+ }
+ if (0 < group_size)
+ printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1),
+ lineno, lno + 1, group_size);
+ else
+ printf("%s %d %d\n", sha1_to_hex(c->object.sha1),
+ lineno, lno + 1);
+ if (!u->meta_given) {
+ get_commit_info(c, &ci, 1);
+ printf("author %s\n", ci.author);
+ printf("author-mail %s\n", ci.author_mail);
+ printf("author-time %lu\n", ci.author_time);
+ printf("author-tz %s\n", ci.author_tz);
+ printf("committer %s\n", ci.committer);
+ printf("committer-mail %s\n", ci.committer_mail);
+ printf("committer-time %lu\n", ci.committer_time);
+ printf("committer-tz %s\n", ci.committer_tz);
+ printf("filename ");
+ if (quote_c_style(u->pathname, NULL, NULL, 0))
+ quote_c_style(u->pathname, NULL, stdout, 0);
+ else
+ fputs(u->pathname, stdout);
+ printf("\nsummary %s\n", ci.summary);
+
+ u->meta_given = 1;
+ }
+ putchar('\t');
+ return;
+ }
+
+ get_commit_info(c, &ci, 0);
+ fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+ if (compatibility) {
+ printf("\t(%10s\t%10s\t%d)", ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ lno + 1);
+ }
+ else {
+ if (show_name)
+ printf(" %-*.*s", longest_file, longest_file,
+ u->pathname);
+ if (show_number)
+ printf(" %*d", max_orig_digits,
+ lineno);
+ printf(" (%-*.*s %10s %*d) ",
+ longest_author, longest_author, ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ max_digits, lno + 1);
+ }
+}
+
int main(int argc, const char **argv)
{
int i;
@@ -749,38 +906,43 @@ int main(int argc, const char **argv)
int compatibility = 0;
int show_raw_time = 0;
int options = 1;
- struct commit* start_commit;
+ struct commit *start_commit;
- const char* args[10];
+ const char *args[10];
struct rev_info rev;
struct commit_info ci;
const char *buf;
- int max_digits;
- int longest_file, longest_author;
- int found_rename;
+ int max_digits, max_orig_digits;
+ int longest_file, longest_author, longest_file_lines;
+ int show_name = 0;
+ int show_number = 0;
+ int porcelain = 0;
- const char* prefix = setup_git_directory();
+ const char *prefix = setup_git_directory();
git_config(git_default_config);
- for(i = 1; i < argc; i++) {
- if(options) {
- if(!strcmp(argv[i], "-h") ||
+ for (i = 1; i < argc; i++) {
+ if (options) {
+ if (!strcmp(argv[i], "-h") ||
!strcmp(argv[i], "--help"))
usage(blame_usage);
- else if(!strcmp(argv[i], "-l") ||
- !strcmp(argv[i], "--long")) {
+ if (!strcmp(argv[i], "-l") ||
+ !strcmp(argv[i], "--long")) {
sha1_len = 40;
continue;
- } else if(!strcmp(argv[i], "-c") ||
- !strcmp(argv[i], "--compatibility")) {
+ }
+ if (!strcmp(argv[i], "-c") ||
+ !strcmp(argv[i], "--compatibility")) {
compatibility = 1;
continue;
- } else if(!strcmp(argv[i], "-t") ||
- !strcmp(argv[i], "--time")) {
+ }
+ if (!strcmp(argv[i], "-t") ||
+ !strcmp(argv[i], "--time")) {
show_raw_time = 1;
continue;
- } else if(!strcmp(argv[i], "-S")) {
+ }
+ if (!strcmp(argv[i], "-S")) {
if (i + 1 < argc &&
!read_ancestry(argv[i + 1], &sha1_p)) {
compatibility = 1;
@@ -788,33 +950,51 @@ int main(int argc, const char **argv)
continue;
}
usage(blame_usage);
- } else if(!strcmp(argv[i], "--")) {
+ }
+ if (!strcmp(argv[i], "-f") ||
+ !strcmp(argv[i], "--show-name")) {
+ show_name = 1;
+ continue;
+ }
+ if (!strcmp(argv[i], "-n") ||
+ !strcmp(argv[i], "--show-number")) {
+ show_number = 1;
+ continue;
+ }
+ if (!strcmp(argv[i], "-p") ||
+ !strcmp(argv[i], "--porcelain")) {
+ porcelain = 1;
+ sha1_len = 40;
+ show_raw_time = 1;
+ continue;
+ }
+ if (!strcmp(argv[i], "--")) {
options = 0;
continue;
- } else if(argv[i][0] == '-')
+ }
+ if (argv[i][0] == '-')
usage(blame_usage);
- else
- options = 0;
+ options = 0;
}
- if(!options) {
- if(!filename)
+ if (!options) {
+ if (!filename)
filename = argv[i];
- else if(!commit)
+ else if (!commit)
commit = argv[i];
else
usage(blame_usage);
}
}
- if(!filename)
+ if (!filename)
usage(blame_usage);
if (commit && sha1_p)
usage(blame_usage);
- else if(!commit)
+ else if (!commit)
commit = "HEAD";
- if(prefix)
+ if (prefix)
sprintf(filename_buf, "%s%s", prefix, filename);
else
strcpy(filename_buf, filename);
@@ -832,7 +1012,6 @@ int main(int argc, const char **argv)
return 1;
}
-
init_revisions(&rev, setup_git_directory());
rev.remove_empty_trees = 1;
rev.topo_order = 1;
@@ -850,62 +1029,49 @@ int main(int argc, const char **argv)
prepare_revision_walk(&rev);
process_commits(&rev, filename, &initial);
+ for (i = 0; i < num_blame_lines; i++)
+ if (!blame_lines[i])
+ blame_lines[i] = initial;
+
buf = blame_contents;
- for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
- i *= 10;
+ max_digits = lineno_width(num_blame_lines);
longest_file = 0;
longest_author = 0;
- found_rename = 0;
+ longest_file_lines = 0;
for (i = 0; i < num_blame_lines; i++) {
struct commit *c = blame_lines[i];
- struct util_info* u;
- if (!c)
- c = initial;
+ struct util_info *u;
u = c->util;
- if (!found_rename && strcmp(filename, u->pathname))
- found_rename = 1;
+ if (!show_name && strcmp(filename, u->pathname))
+ show_name = 1;
if (longest_file < strlen(u->pathname))
longest_file = strlen(u->pathname);
- get_commit_info(c, &ci);
+ if (longest_file_lines < u->num_lines)
+ longest_file_lines = u->num_lines;
+ get_commit_info(c, &ci, 0);
if (longest_author < strlen(ci.author))
longest_author = strlen(ci.author);
}
- for (i = 0; i < num_blame_lines; i++) {
- struct commit *c = blame_lines[i];
- struct util_info* u;
+ max_orig_digits = lineno_width(longest_file_lines);
- if (!c)
- c = initial;
-
- u = c->util;
- get_commit_info(c, &ci);
- fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
- if(compatibility) {
- printf("\t(%10s\t%10s\t%d)", ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- i+1);
- } else {
- if (found_rename)
- printf(" %-*.*s", longest_file, longest_file,
- u->pathname);
- printf(" (%-*.*s %10s %*d) ",
- longest_author, longest_author, ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- max_digits, i+1);
- }
+ for (i = 0; i < num_blame_lines; i++) {
+ emit_meta(blame_lines[i], i,
+ sha1_len, compatibility, porcelain,
+ show_name, show_number, show_raw_time,
+ longest_file, longest_author,
+ max_digits, max_orig_digits);
- if(i == num_blame_lines - 1) {
+ if (i == num_blame_lines - 1) {
fwrite(buf, blame_len - (buf - blame_contents),
1, stdout);
- if(blame_contents[blame_len-1] != '\n')
+ if (blame_contents[blame_len-1] != '\n')
putc('\n', stdout);
- } else {
- char* next_buf = strchr(buf, '\n') + 1;
+ }
+ else {
+ char *next_buf = strchr(buf, '\n') + 1;
fwrite(buf, next_buf - buf, 1, stdout);
buf = next_buf;
}
diff --git a/builtin-annotate.c b/builtin-annotate.c
new file mode 100644
index 000000000..25ad47371
--- /dev/null
+++ b/builtin-annotate.c
@@ -0,0 +1,25 @@
+/*
+ * "git annotate" builtin alias
+ *
+ * Copyright (C) 2006 Ryan Anderson
+ */
+#include "git-compat-util.h"
+#include "exec_cmd.h"
+
+int cmd_annotate(int argc, const char **argv, const char *prefix)
+{
+ const char **nargv;
+ int i;
+ nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+ nargv[0] = "blame";
+ nargv[1] = "-c";
+
+ for (i = 1; i < argc; i++) {
+ nargv[i+1] = argv[i];
+ }
+ nargv[argc + 1] = NULL;
+
+ return execv_git_cmd(nargv);
+}
+
diff --git a/builtin-apply.c b/builtin-apply.c
index 11397f550..aad55261f 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -43,7 +43,7 @@ static int apply_verbosely;
static int no_add;
static int show_index_info;
static int line_termination = '\n';
-static unsigned long p_context = -1;
+static unsigned long p_context = ULONG_MAX;
static const char apply_usage[] =
"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
@@ -1043,10 +1043,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
* then not having oldlines means the patch is creation,
* and not having newlines means the patch is deletion.
*/
- if (patch->is_new < 0 && !oldlines)
+ if (patch->is_new < 0 && !oldlines) {
patch->is_new = 1;
- if (patch->is_delete < 0 && !newlines)
+ patch->old_name = NULL;
+ }
+ if (patch->is_delete < 0 && !newlines) {
patch->is_delete = 1;
+ patch->new_name = NULL;
+ }
}
if (0 < patch->is_new && oldlines)
diff --git a/builtin-archive.c b/builtin-archive.c
index 917737912..2df1a84b8 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -15,16 +15,14 @@
static const char archive_usage[] = \
"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
-struct archiver archivers[] = {
- {
- .name = "tar",
- .write_archive = write_tar_archive,
- },
- {
- .name = "zip",
- .write_archive = write_zip_archive,
- .parse_extra = parse_extra_zip_args,
- },
+static struct archiver_desc
+{
+ const char *name;
+ write_archive_fn_t write_archive;
+ parse_extra_args_fn_t parse_extra;
+} archivers[] = {
+ { "tar", write_tar_archive, NULL },
+ { "zip", write_zip_archive, parse_extra_zip_args },
};
static int run_remote_archiver(const char *remote, int argc,
@@ -88,7 +86,10 @@ static int init_archiver(const char *name, struct archiver *ar)
for (i = 0; i < ARRAY_SIZE(archivers); i++) {
if (!strcmp(name, archivers[i].name)) {
- memcpy(ar, &archivers[i], sizeof(struct archiver));
+ memset(ar, 0, sizeof(*ar));
+ ar->name = archivers[i].name;
+ ar->write_archive = archivers[i].write_archive;
+ ar->parse_extra = archivers[i].parse_extra;
rv = 0;
break;
}
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644
index 000000000..368b68ec9
--- /dev/null
+++ b/builtin-branch.c
@@ -0,0 +1,221 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+
+static const char builtin_branch_usage[] =
+"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]";
+
+
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int in_merge_bases(const unsigned char *sha1,
+ struct commit *rev1,
+ struct commit *rev2)
+{
+ struct commit_list *bases, *b;
+ int ret = 0;
+
+ bases = get_merge_bases(rev1, rev2, 1);
+ for (b = bases; b; b = b->next) {
+ if (!hashcmp(sha1, b->item->object.sha1)) {
+ ret = 1;
+ break;
+ }
+ }
+
+ free_commit_list(bases);
+ return ret;
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+ struct commit *rev, *head_rev;
+ unsigned char sha1[20];
+ char *name;
+ int i;
+
+ head_rev = lookup_commit_reference(head_sha1);
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(head, argv[i]))
+ die("Cannot delete the branch you are currently on.");
+
+ name = xstrdup(mkpath("refs/heads/%s", argv[i]));
+ if (!resolve_ref(name, sha1, 1, NULL))
+ die("Branch '%s' not found.", argv[i]);
+
+ rev = lookup_commit_reference(sha1);
+ if (!rev || !head_rev)
+ die("Couldn't look up commit objects.");
+
+ /* This checks whether the merge bases of branch and
+ * HEAD contains branch -- which means that the HEAD
+ * contains everything in both.
+ */
+
+ if (!force &&
+ !in_merge_bases(sha1, rev, head_rev)) {
+ fprintf(stderr,
+ "The branch '%s' is not a strict subset of your current HEAD.\n"
+ "If you are sure you want to delete it, run 'git branch -D %s'.\n",
+ argv[i], argv[i]);
+ exit(1);
+ }
+
+ if (delete_ref(name, sha1))
+ printf("Error deleting branch '%s'\n", argv[i]);
+ else
+ printf("Deleted branch %s.\n", argv[i]);
+
+ free(name);
+ }
+}
+
+static int ref_index, ref_alloc;
+static char **ref_list;
+
+static int append_ref(const char *refname, const unsigned char *sha1, int flags,
+ void *cb_data)
+{
+ if (ref_index >= ref_alloc) {
+ ref_alloc = alloc_nr(ref_alloc);
+ ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *));
+ }
+
+ ref_list[ref_index++] = xstrdup(refname);
+
+ return 0;
+}
+
+static int ref_cmp(const void *r1, const void *r2)
+{
+ return strcmp(*(char **)r1, *(char **)r2);
+}
+
+static void print_ref_list(int remote_only)
+{
+ int i;
+ char c;
+
+ if (remote_only)
+ for_each_remote_ref(append_ref, NULL);
+ else
+ for_each_branch_ref(append_ref, NULL);
+
+ qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
+
+ for (i = 0; i < ref_index; i++) {
+ c = ' ';
+ if (!strcmp(ref_list[i], head))
+ c = '*';
+
+ printf("%c %s\n", c, ref_list[i]);
+ }
+}
+
+static void create_branch(const char *name, const char *start,
+ int force, int reflog)
+{
+ struct ref_lock *lock;
+ struct commit *commit;
+ unsigned char sha1[20];
+ char ref[PATH_MAX], msg[PATH_MAX + 20];
+
+ snprintf(ref, sizeof ref, "refs/heads/%s", name);
+ if (check_ref_format(ref))
+ die("'%s' is not a valid branch name.", name);
+
+ if (resolve_ref(ref, sha1, 1, NULL)) {
+ if (!force)
+ die("A branch named '%s' already exists.", name);
+ else if (!strcmp(head, name))
+ die("Cannot force update the current branch.");
+ }
+
+ if (get_sha1(start, sha1) ||
+ (commit = lookup_commit_reference(sha1)) == NULL)
+ die("Not a valid branch point: '%s'.", start);
+ hashcpy(sha1, commit->object.sha1);
+
+ lock = lock_any_ref_for_update(ref, NULL);
+ if (!lock)
+ die("Failed to lock ref for update: %s.", strerror(errno));
+
+ if (reflog) {
+ log_all_ref_updates = 1;
+ snprintf(msg, sizeof msg, "branch: Created from %s", start);
+ }
+
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ die("Failed to write ref: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+ int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+ int reflog = 0;
+ int i;
+
+ git_config(git_default_config);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-d")) {
+ delete = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-D")) {
+ delete = 1;
+ force_delete = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-f")) {
+ force_create = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-r")) {
+ remote_only = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-l")) {
+ reflog = 1;
+ continue;
+ }
+ usage(builtin_branch_usage);
+ }
+
+ head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
+ if (!head)
+ die("Failed to resolve HEAD as a valid ref.");
+ if (strncmp(head, "refs/heads/", 11))
+ die("HEAD not found below refs/heads!");
+ head += 11;
+
+ if (delete)
+ delete_branches(argc - i, argv + i, force_delete);
+ else if (i == argc)
+ print_ref_list(remote_only);
+ else if (i == argc - 1)
+ create_branch(argv[i], head, force_create, reflog);
+ else if (i == argc - 2)
+ create_branch(argv[i], argv[i + 1], force_create, reflog);
+ else
+ usage(builtin_branch_usage);
+
+ return 0;
+}
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index c407c033e..3d3097d29 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -249,7 +249,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
FILE *in = stdin;
const char *sep = "";
unsigned char head_sha1[20];
- const char *head, *current_branch;
+ const char *current_branch;
git_config(fmt_merge_msg_config);
@@ -277,10 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
usage(fmt_merge_msg_usage);
/* get current branch */
- head = xstrdup(git_path("HEAD"));
- current_branch = resolve_ref(head, head_sha1, 1);
- current_branch += strlen(head) - 4;
- free((char *)head);
+ current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
if (!strncmp(current_branch, "refs/heads/", 11))
current_branch += 11;
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
new file mode 100644
index 000000000..173bf3873
--- /dev/null
+++ b/builtin-for-each-ref.c
@@ -0,0 +1,896 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include <fnmatch.h>
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 3
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+ const char *s;
+ unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+ struct ref_sort *next;
+ int atom; /* index into used_atom array */
+ unsigned reverse : 1;
+};
+
+struct refinfo {
+ char *refname;
+ unsigned char objectname[20];
+ struct atom_value *value;
+};
+
+static struct {
+ const char *name;
+ cmp_type cmp_type;
+} valid_atom[] = {
+ { "refname" },
+ { "objecttype" },
+ { "objectsize", FIELD_ULONG },
+ { "objectname" },
+ { "tree" },
+ { "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */
+ { "numparent", FIELD_ULONG },
+ { "object" },
+ { "type" },
+ { "tag" },
+ { "author" },
+ { "authorname" },
+ { "authoremail" },
+ { "authordate", FIELD_TIME },
+ { "committer" },
+ { "committername" },
+ { "committeremail" },
+ { "committerdate", FIELD_TIME },
+ { "tagger" },
+ { "taggername" },
+ { "taggeremail" },
+ { "taggerdate", FIELD_TIME },
+ { "creator" },
+ { "creatordate", FIELD_TIME },
+ { "subject" },
+ { "body" },
+ { "contents" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects. refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+ const char *sp;
+ char *n;
+ int i, at;
+
+ sp = atom;
+ if (*sp == '*' && sp < ep)
+ sp++; /* deref */
+ if (ep <= sp)
+ die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+ /* Do we have the atom already used elsewhere? */
+ for (i = 0; i < used_atom_cnt; i++) {
+ int len = strlen(used_atom[i]);
+ if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+ return i;
+ }
+
+ /* Is the atom a valid one? */
+ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+ int len = strlen(valid_atom[i].name);
+ if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len))
+ break;
+ }
+
+ if (ARRAY_SIZE(valid_atom) <= i)
+ die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+ /* Add it in, including the deref prefix */
+ at = used_atom_cnt;
+ used_atom_cnt++;
+ used_atom = xrealloc(used_atom,
+ (sizeof *used_atom) * used_atom_cnt);
+ used_atom_type = xrealloc(used_atom_type,
+ (sizeof(*used_atom_type) * used_atom_cnt));
+ n = xmalloc(ep - atom + 1);
+ memcpy(n, atom, ep - atom);
+ n[ep-atom] = 0;
+ used_atom[at] = n;
+ used_atom_type[at] = valid_atom[i].cmp_type;
+ return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+ while (*cp) {
+ if (*cp == '%') {
+ /* %( is the start of an atom;
+ * %% is a quoteed per-cent.
+ */
+ if (cp[1] == '(')
+ return cp;
+ else if (cp[1] == '%')
+ cp++; /* skip over two % */
+ /* otherwise this is a singleton, literal % */
+ }
+ cp++;
+ }
+ return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static void verify_format(const char *format)
+{
+ const char *cp, *sp;
+ for (cp = format; *cp && (sp = find_next(cp)); ) {
+ const char *ep = strchr(sp, ')');
+ if (!ep)
+ die("malformatted format string %s", sp);
+ /* sp points at "%(" and ep points at the closing ")" */
+ parse_atom(sp + 2, ep);
+ cp = ep + 1;
+ }
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object". If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+ char type[20];
+ void *buf = read_sha1_file(sha1, type, sz);
+
+ if (buf)
+ *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+ else
+ *obj = NULL;
+ return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "objecttype"))
+ v->s = type_names[obj->type];
+ else if (!strcmp(name, "objectsize")) {
+ char *s = xmalloc(40);
+ sprintf(s, "%lu", sz);
+ v->ul = sz;
+ v->s = s;
+ }
+ else if (!strcmp(name, "objectname")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(obj->sha1));
+ v->s = s;
+ }
+ }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ struct tag *tag = (struct tag *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tag"))
+ v->s = tag->tag;
+ }
+}
+
+static int num_parents(struct commit *commit)
+{
+ struct commit_list *parents;
+ int i;
+
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next)
+ i++;
+ return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ struct commit *commit = (struct commit *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tree")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+ v->s = s;
+ }
+ if (!strcmp(name, "numparent")) {
+ char *s = xmalloc(40);
+ sprintf(s, "%lu", v->ul);
+ v->s = s;
+ v->ul = num_parents(commit);
+ }
+ else if (!strcmp(name, "parent")) {
+ int num = num_parents(commit);
+ int i;
+ struct commit_list *parents;
+ char *s = xmalloc(42 * num);
+ v->s = s;
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next, i = i + 42) {
+ struct commit *parent = parents->item;
+ strcpy(s+i, sha1_to_hex(parent->object.sha1));
+ if (parents->next)
+ s[i+40] = ' ';
+ }
+ }
+ }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+ const char *eol;
+ while (*buf) {
+ if (!strncmp(buf, who, wholen) &&
+ buf[wholen] == ' ')
+ return buf + wholen + 1;
+ eol = strchr(buf, '\n');
+ if (!eol)
+ return "";
+ eol++;
+ if (eol[1] == '\n')
+ return ""; /* end of header */
+ buf = eol;
+ }
+ return "";
+}
+
+static char *copy_line(const char *buf)
+{
+ const char *eol = strchr(buf, '\n');
+ char *line;
+ int len;
+ if (!eol)
+ return "";
+ len = eol - buf;
+ line = xmalloc(len + 1);
+ memcpy(line, buf, len);
+ line[len] = 0;
+ return line;
+}
+
+static char *copy_name(const char *buf)
+{
+ const char *eol = strchr(buf, '\n');
+ const char *eoname = strstr(buf, " <");
+ char *line;
+ int len;
+ if (!(eoname && eol && eoname < eol))
+ return "";
+ len = eoname - buf;
+ line = xmalloc(len + 1);
+ memcpy(line, buf, len);
+ line[len] = 0;
+ return line;
+}
+
+static char *copy_email(const char *buf)
+{
+ const char *email = strchr(buf, '<');
+ const char *eoemail = strchr(email, '>');
+ char *line;
+ int len;
+ if (!email || !eoemail)
+ return "";
+ eoemail++;
+ len = eoemail - email;
+ line = xmalloc(len + 1);
+ memcpy(line, email, len);
+ line[len] = 0;
+ return line;
+}
+
+static void grab_date(const char *buf, struct atom_value *v)
+{
+ const char *eoemail = strstr(buf, "> ");
+ char *zone;
+ unsigned long timestamp;
+ long tz;
+
+ if (!eoemail)
+ goto bad;
+ timestamp = strtoul(eoemail + 2, &zone, 10);
+ if (timestamp == ULONG_MAX)
+ goto bad;
+ tz = strtol(zone, NULL, 10);
+ if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+ goto bad;
+ v->s = xstrdup(show_date(timestamp, tz, 0));
+ v->ul = timestamp;
+ return;
+ bad:
+ v->s = "";
+ v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ int wholen = strlen(who);
+ const char *wholine = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strncmp(who, name, wholen))
+ continue;
+ if (name[wholen] != 0 &&
+ strcmp(name + wholen, "name") &&
+ strcmp(name + wholen, "email") &&
+ strcmp(name + wholen, "date"))
+ continue;
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return; /* no point looking for it */
+ if (name[wholen] == 0)
+ v->s = copy_line(wholine);
+ else if (!strcmp(name + wholen, "name"))
+ v->s = copy_name(wholine);
+ else if (!strcmp(name + wholen, "email"))
+ v->s = copy_email(wholine);
+ else if (!strcmp(name + wholen, "date"))
+ grab_date(wholine, v);
+ }
+
+ /* For a tag or a commit object, if "creator" or "creatordate" is
+ * requested, do something special.
+ */
+ if (strcmp(who, "tagger") && strcmp(who, "committer"))
+ return; /* "author" for commit object is not wanted */
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return;
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+
+ if (!strcmp(name, "creatordate"))
+ grab_date(wholine, v);
+ else if (!strcmp(name, "creator"))
+ v->s = copy_line(wholine);
+ }
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+ while (*buf) {
+ const char *eol = strchr(buf, '\n');
+ if (!eol)
+ return;
+ if (eol[1] == '\n') {
+ buf = eol + 1;
+ break; /* found end of header */
+ }
+ buf = eol + 1;
+ }
+ while (*buf == '\n')
+ buf++;
+ if (!*buf)
+ return;
+ *sub = buf; /* first non-empty line */
+ buf = strchr(buf, '\n');
+ if (!buf)
+ return; /* no body */
+ while (*buf == '\n')
+ buf++; /* skip blank between subject and body */
+ *body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ const char *subpos = NULL, *bodypos = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strcmp(name, "subject") &&
+ strcmp(name, "body") &&
+ strcmp(name, "contents"))
+ continue;
+ if (!subpos)
+ find_subpos(buf, sz, &subpos, &bodypos);
+ if (!subpos)
+ return;
+
+ if (!strcmp(name, "subject"))
+ v->s = copy_line(subpos);
+ else if (!strcmp(name, "body"))
+ v->s = bodypos;
+ else if (!strcmp(name, "contents"))
+ v->s = subpos;
+ }
+}
+
+/* We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+ int i;
+ for (i = 0; i < used_atom_cnt; i++) {
+ struct atom_value *v = &val[i];
+ if (v->s == NULL)
+ v->s = "";
+ }
+}
+
+/*
+ * val is a list of atom_value to hold returned values. Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ grab_common_values(val, deref, obj, buf, sz);
+ switch (obj->type) {
+ case OBJ_TAG:
+ grab_tag_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("tagger", val, deref, obj, buf, sz);
+ break;
+ case OBJ_COMMIT:
+ grab_commit_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("author", val, deref, obj, buf, sz);
+ grab_person("committer", val, deref, obj, buf, sz);
+ break;
+ case OBJ_TREE:
+ // grab_tree_values(val, deref, obj, buf, sz);
+ break;
+ case OBJ_BLOB:
+ // grab_blob_values(val, deref, obj, buf, sz);
+ break;
+ default:
+ die("Eh? Object of type %d?", obj->type);
+ }
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+ void *buf;
+ struct object *obj;
+ int eaten, i;
+ unsigned long size;
+ const unsigned char *tagged;
+
+ ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+ buf = get_obj(ref->objectname, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+
+ /* Fill in specials first */
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &ref->value[i];
+ if (!strcmp(name, "refname"))
+ v->s = ref->refname;
+ else if (!strcmp(name, "*refname")) {
+ int len = strlen(ref->refname);
+ char *s = xmalloc(len + 4);
+ sprintf(s, "%s^{}", ref->refname);
+ v->s = s;
+ }
+ }
+
+ grab_values(ref->value, 0, obj, buf, size);
+ if (!eaten)
+ free(buf);
+
+ /* If there is no atom that wants to know about tagged
+ * object, we are done.
+ */
+ if (!need_tagged || (obj->type != OBJ_TAG))
+ return;
+
+ /* If it is a tag object, see if we use a value that derefs
+ * the object, and if we do grab the object it refers to.
+ */
+ tagged = ((struct tag *)obj)->tagged->sha1;
+
+ /* NEEDSWORK: This derefs tag only once, which
+ * is good to deal with chains of trust, but
+ * is not consistent with what deref_tag() does
+ * which peels the onion to the core.
+ */
+ buf = get_obj(tagged, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ grab_values(ref->value, 1, obj, buf, size);
+ if (!eaten)
+ free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom. This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+ if (!ref->value) {
+ populate_value(ref);
+ fill_missing_values(ref->value);
+ }
+ *v = &ref->value[atom];
+}
+
+struct grab_ref_cbdata {
+ struct refinfo **grab_array;
+ const char **grab_pattern;
+ int grab_cnt;
+};
+
+/*
+ * A call-back given to for_each_ref(). It is unfortunate that we
+ * need to use global variables to pass extra information to this
+ * function.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct grab_ref_cbdata *cb = cb_data;
+ struct refinfo *ref;
+ int cnt;
+
+ if (*cb->grab_pattern) {
+ const char **pattern;
+ int namelen = strlen(refname);
+ for (pattern = cb->grab_pattern; *pattern; pattern++) {
+ const char *p = *pattern;
+ int plen = strlen(p);
+
+ if ((plen <= namelen) &&
+ !strncmp(refname, p, plen) &&
+ (refname[plen] == '\0' ||
+ refname[plen] == '/'))
+ break;
+ if (!fnmatch(p, refname, FNM_PATHNAME))
+ break;
+ }
+ if (!*pattern)
+ return 0;
+ }
+
+ /* We do not open the object yet; sort may only need refname
+ * to do its job and the resulting list may yet to be pruned
+ * by maxcount logic.
+ */
+ ref = xcalloc(1, sizeof(*ref));
+ ref->refname = xstrdup(refname);
+ hashcpy(ref->objectname, sha1);
+
+ cnt = cb->grab_cnt;
+ cb->grab_array = xrealloc(cb->grab_array,
+ sizeof(*cb->grab_array) * (cnt + 1));
+ cb->grab_array[cnt++] = ref;
+ cb->grab_cnt = cnt;
+ return 0;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+ struct atom_value *va, *vb;
+ int cmp;
+ cmp_type cmp_type = used_atom_type[s->atom];
+
+ get_value(a, s->atom, &va);
+ get_value(b, s->atom, &vb);
+ switch (cmp_type) {
+ case FIELD_STR:
+ cmp = strcmp(va->s, vb->s);
+ break;
+ default:
+ if (va->ul < vb->ul)
+ cmp = -1;
+ else if (va->ul == vb->ul)
+ cmp = 0;
+ else
+ cmp = 1;
+ break;
+ }
+ return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+ struct refinfo *a = *((struct refinfo **)a_);
+ struct refinfo *b = *((struct refinfo **)b_);
+ struct ref_sort *s;
+
+ for (s = ref_sort; s; s = s->next) {
+ int cmp = cmp_ref_sort(s, a, b);
+ if (cmp)
+ return cmp;
+ }
+ return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+ ref_sort = sort;
+ qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+ struct atom_value *v;
+ get_value(ref, atom, &v);
+ switch (quote_style) {
+ case QUOTE_NONE:
+ fputs(v->s, stdout);
+ break;
+ case QUOTE_SHELL:
+ sq_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PERL:
+ perl_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PYTHON:
+ python_quote_print(stdout, v->s);
+ break;
+ }
+}
+
+static int hex1(char ch)
+{
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+ else if ('a' <= ch && ch <= 'f')
+ return ch - 'a' + 10;
+ else if ('A' <= ch && ch <= 'F')
+ return ch - 'A' + 10;
+ return -1;
+}
+static int hex2(const char *cp)
+{
+ if (cp[0] && cp[1])
+ return (hex1(cp[0]) << 4) | hex1(cp[1]);
+ else
+ return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+ while (*cp && (!ep || cp < ep)) {
+ if (*cp == '%') {
+ if (cp[1] == '%')
+ cp++;
+ else {
+ int ch = hex2(cp + 1);
+ if (0 <= ch) {
+ putchar(ch);
+ cp += 3;
+ continue;
+ }
+ }
+ }
+ putchar(*cp);
+ cp++;
+ }
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+ const char *cp, *sp, *ep;
+
+ for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+ ep = strchr(sp, ')');
+ if (cp < sp)
+ emit(cp, sp);
+ print_value(info, parse_atom(sp + 2, ep), quote_style);
+ }
+ if (*cp) {
+ sp = cp + strlen(cp);
+ emit(cp, sp);
+ }
+ putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+ static const char cstr_name[] = "refname";
+
+ struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+ sort->next = NULL;
+ sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+ return sort;
+}
+
+int cmd_for_each_ref(int ac, const char **av, char *prefix)
+{
+ int i, num_refs;
+ const char *format = NULL;
+ struct ref_sort *sort = NULL, **sort_tail = &sort;
+ int maxcount = 0;
+ int quote_style = -1; /* unspecified yet */
+ struct refinfo **refs;
+ struct grab_ref_cbdata cbdata;
+
+ for (i = 1; i < ac; i++) {
+ const char *arg = av[i];
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strncmp(arg, "--format=", 9)) {
+ if (format)
+ die("more than one --format?");
+ format = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
+ if (0 <= quote_style)
+ die("more than one quoting style?");
+ quote_style = QUOTE_SHELL;
+ continue;
+ }
+ if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
+ if (0 <= quote_style)
+ die("more than one quoting style?");
+ quote_style = QUOTE_PERL;
+ continue;
+ }
+ if (!strcmp(arg, "--python") ) {
+ if (0 <= quote_style)
+ die("more than one quoting style?");
+ quote_style = QUOTE_PYTHON;
+ continue;
+ }
+ if (!strncmp(arg, "--count=", 8)) {
+ if (maxcount)
+ die("more than one --count?");
+ maxcount = atoi(arg + 8);
+ if (maxcount <= 0)
+ die("The number %s did not parse", arg);
+ continue;
+ }
+ if (!strncmp(arg, "--sort=", 7)) {
+ struct ref_sort *s = xcalloc(1, sizeof(*s));
+ int len;
+
+ s->next = NULL;
+ *sort_tail = s;
+ sort_tail = &s->next;
+
+ arg += 7;
+ if (*arg == '-') {
+ s->reverse = 1;
+ arg++;
+ }
+ len = strlen(arg);
+ sort->atom = parse_atom(arg, arg+len);
+ continue;
+ }
+ break;
+ }
+ if (quote_style < 0)
+ quote_style = QUOTE_NONE;
+
+ if (!sort)
+ sort = default_sort();
+ sort_atom_limit = used_atom_cnt;
+ if (!format)
+ format = "%(objectname) %(objecttype)\t%(refname)";
+
+ verify_format(format);
+
+ memset(&cbdata, 0, sizeof(cbdata));
+ cbdata.grab_pattern = av + i;
+ for_each_ref(grab_single_ref, &cbdata);
+ refs = cbdata.grab_array;
+ num_refs = cbdata.grab_cnt;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ if (used_atom[i][0] == '*') {
+ need_tagged = 1;
+ break;
+ }
+ }
+
+ sort_refs(sort, refs, num_refs);
+
+ if (!maxcount || num_refs < maxcount)
+ maxcount = num_refs;
+ for (i = 0; i < maxcount; i++)
+ show_ref(refs[i], format, quote_style);
+ return 0;
+}
diff --git a/builtin-init-db.c b/builtin-init-db.c
index c3ed1ce49..235a0ee48 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -218,8 +218,8 @@ static void create_default_files(const char *git_dir, const char *template_path)
* branch, if it does not exist yet.
*/
strcpy(path + len, "HEAD");
- if (read_ref(path, sha1) < 0) {
- if (create_symref(path, "refs/heads/master") < 0)
+ if (read_ref("HEAD", sha1) < 0) {
+ if (create_symref("HEAD", "refs/heads/master") < 0)
exit(1);
}
diff --git a/builtin-log.c b/builtin-log.c
index 9d1ceae44..fedb0137b 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -171,8 +171,11 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
static int get_patch_id(struct commit *commit, struct diff_options *options,
unsigned char *sha1)
{
- diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
- "", options);
+ if (commit->parents)
+ diff_tree_sha1(commit->parents->item->object.sha1,
+ commit->object.sha1, "", options);
+ else
+ diff_root_tree_sha1(commit->object.sha1, "", options);
diffcore_std(options);
return diff_flush_patch_id(options, sha1);
}
@@ -437,3 +440,109 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
return 0;
}
+static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
+{
+ unsigned char sha1[20];
+ if (get_sha1(arg, sha1) == 0) {
+ struct commit *commit = lookup_commit_reference(sha1);
+ if (commit) {
+ commit->object.flags |= flags;
+ add_pending_object(revs, &commit->object, arg);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static const char cherry_usage[] =
+"git-cherry [-v] <upstream> [<head>] [<limit>]";
+int cmd_cherry(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct diff_options patch_id_opts;
+ struct commit *commit;
+ struct commit_list *list = NULL;
+ const char *upstream;
+ const char *head = "HEAD";
+ const char *limit = NULL;
+ int verbose = 0;
+
+ if (argc > 1 && !strcmp(argv[1], "-v")) {
+ verbose = 1;
+ argc--;
+ argv++;
+ }
+
+ switch (argc) {
+ case 4:
+ limit = argv[3];
+ /* FALLTHROUGH */
+ case 3:
+ head = argv[2];
+ /* FALLTHROUGH */
+ case 2:
+ upstream = argv[1];
+ break;
+ default:
+ usage(cherry_usage);
+ }
+
+ init_revisions(&revs, prefix);
+ revs.diff = 1;
+ revs.combine_merges = 0;
+ revs.ignore_merges = 1;
+ revs.diffopt.recursive = 1;
+
+ if (add_pending_commit(head, &revs, 0))
+ die("Unknown commit %s", head);
+ if (add_pending_commit(upstream, &revs, UNINTERESTING))
+ die("Unknown commit %s", upstream);
+
+ /* Don't say anything if head and upstream are the same. */
+ if (revs.pending.nr == 2) {
+ struct object_array_entry *o = revs.pending.objects;
+ if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+ return 0;
+ }
+
+ get_patch_ids(&revs, &patch_id_opts, prefix);
+
+ if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
+ die("Unknown commit %s", limit);
+
+ /* reverse the list of commits */
+ prepare_revision_walk(&revs);
+ while ((commit = get_revision(&revs)) != NULL) {
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ commit_list_insert(commit, &list);
+ }
+
+ while (list) {
+ unsigned char sha1[20];
+ char sign = '+';
+
+ commit = list->item;
+ if (!get_patch_id(commit, &patch_id_opts, sha1) &&
+ lookup_object(sha1))
+ sign = '-';
+
+ if (verbose) {
+ static char buf[16384];
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+ buf, sizeof(buf), 0, NULL, NULL, 0);
+ printf("%c %s %s\n", sign,
+ sha1_to_hex(commit->object.sha1), buf);
+ }
+ else {
+ printf("%c %s\n", sign,
+ sha1_to_hex(commit->object.sha1));
+ }
+
+ list = list->next;
+ }
+
+ return 0;
+}
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index 52886b69b..618aa314d 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -75,11 +75,10 @@ copy_data:
}
}
-static int tags_only;
-
-static int name_ref(const char *path, const unsigned char *sha1)
+static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
{
struct object *o = parse_object(sha1);
+ int tags_only = *(int*)cb_data;
int deref = 0;
if (tags_only && strncmp(path, "refs/tags/", 10))
@@ -131,6 +130,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
struct object_array revs = { 0, 0, NULL };
int as_is = 0, all = 0, transform_stdin = 0;
+ int tags_only = 0;
git_config(git_default_config);
@@ -186,7 +186,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
add_object_array((struct object *)commit, *argv, &revs);
}
- for_each_ref(name_ref);
+ for_each_ref(name_ref, &tags_only);
if (transform_stdin) {
char buffer[2048];
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 96c069a81..69e5dd39c 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -15,7 +15,12 @@
#include <sys/time.h>
#include <signal.h>
-static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
+static const char pack_usage[] = "\
+git-pack-objects [{ -q | --progress | --all-progress }] \n\
+ [--local] [--incremental] [--window=N] [--depth=N] \n\
+ [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
+ [--revs [--unpacked | --all]*] [--stdout | base-name] \n\
+ [<ref-list | <object-list]";
struct object_entry {
unsigned char sha1[20];
@@ -29,6 +34,7 @@ struct object_entry {
enum object_type type;
enum object_type in_pack_type; /* could be delta */
unsigned long delta_size; /* delta data size (uncompressed) */
+#define in_pack_header_size delta_size /* only when reusing pack data */
struct object_entry *delta; /* delta base object */
struct packed_git *in_pack; /* already in pack */
unsigned int in_pack_offset;
@@ -60,6 +66,8 @@ static int non_empty;
static int no_reuse_delta;
static int local;
static int incremental;
+static int allow_ofs_delta;
+
static struct object_entry **sorted_by_sha, **sorted_by_type;
static struct object_entry *objects;
static int nr_objects, nr_alloc, nr_result;
@@ -84,17 +92,25 @@ static int object_ix_hashsz;
* Pack index for existing packs give us easy access to the offsets into
* corresponding pack file where each object's data starts, but the entries
* do not store the size of the compressed representation (uncompressed
- * size is easily available by examining the pack entry header). We build
- * a hashtable of existing packs (pack_revindex), and keep reverse index
- * here -- pack index file is sorted by object name mapping to offset; this
- * pack_revindex[].revindex array is an ordered list of offsets, so if you
- * know the offset of an object, next offset is where its packed
- * representation ends.
+ * size is easily available by examining the pack entry header). It is
+ * also rather expensive to find the sha1 for an object given its offset.
+ *
+ * We build a hashtable of existing packs (pack_revindex), and keep reverse
+ * index here -- pack index file is sorted by object name mapping to offset;
+ * this pack_revindex[].revindex array is a list of offset/index_nr pairs
+ * ordered by offset, so if you know the offset of an object, next offset
+ * is where its packed representation ends and the index_nr can be used to
+ * get the object sha1 from the main index.
*/
+struct revindex_entry {
+ unsigned int offset;
+ unsigned int nr;
+};
struct pack_revindex {
struct packed_git *p;
- unsigned long *revindex;
-} *pack_revindex = NULL;
+ struct revindex_entry *revindex;
+};
+static struct pack_revindex *pack_revindex;
static int pack_revindex_hashsz;
/*
@@ -141,14 +157,9 @@ static void prepare_pack_ix(void)
static int cmp_offset(const void *a_, const void *b_)
{
- unsigned long a = *(unsigned long *) a_;
- unsigned long b = *(unsigned long *) b_;
- if (a < b)
- return -1;
- else if (a == b)
- return 0;
- else
- return 1;
+ const struct revindex_entry *a = a_;
+ const struct revindex_entry *b = b_;
+ return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
}
/*
@@ -161,25 +172,27 @@ static void prepare_pack_revindex(struct pack_revindex *rix)
int i;
void *index = p->index_base + 256;
- rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
+ rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
for (i = 0; i < num_ent; i++) {
unsigned int hl = *((unsigned int *)((char *) index + 24*i));
- rix->revindex[i] = ntohl(hl);
+ rix->revindex[i].offset = ntohl(hl);
+ rix->revindex[i].nr = i;
}
/* This knows the pack format -- the 20-byte trailer
* follows immediately after the last object data.
*/
- rix->revindex[num_ent] = p->pack_size - 20;
- qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset);
+ rix->revindex[num_ent].offset = p->pack_size - 20;
+ rix->revindex[num_ent].nr = -1;
+ qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
}
-static unsigned long find_packed_object_size(struct packed_git *p,
- unsigned long ofs)
+static struct revindex_entry * find_packed_object(struct packed_git *p,
+ unsigned int ofs)
{
int num;
int lo, hi;
struct pack_revindex *rix;
- unsigned long *revindex;
+ struct revindex_entry *revindex;
num = pack_revindex_ix(p);
if (num < 0)
die("internal error: pack revindex uninitialized");
@@ -191,10 +204,10 @@ static unsigned long find_packed_object_size(struct packed_git *p,
hi = num_packed_objects(p) + 1;
do {
int mi = (lo + hi) / 2;
- if (revindex[mi] == ofs) {
- return revindex[mi+1] - ofs;
+ if (revindex[mi].offset == ofs) {
+ return revindex + mi;
}
- else if (ofs < revindex[mi])
+ else if (ofs < revindex[mi].offset)
hi = mi;
else
lo = mi + 1;
@@ -202,6 +215,20 @@ static unsigned long find_packed_object_size(struct packed_git *p,
die("internal error: pack revindex corrupt");
}
+static unsigned long find_packed_object_size(struct packed_git *p,
+ unsigned long ofs)
+{
+ struct revindex_entry *entry = find_packed_object(p, ofs);
+ return entry[1].offset - ofs;
+}
+
+static unsigned char *find_packed_object_name(struct packed_git *p,
+ unsigned long ofs)
+{
+ struct revindex_entry *entry = find_packed_object(p, ofs);
+ return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4;
+}
+
static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
{
unsigned long othersize, delta_size;
@@ -232,7 +259,7 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
int n = 1;
unsigned char c;
- if (type < OBJ_COMMIT || type > OBJ_DELTA)
+ if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
die("bad type %d", type);
c = (type << 4) | (size & 15);
@@ -247,6 +274,10 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
return n;
}
+/*
+ * we are going to reuse the existing object data as is. make
+ * sure it is not corrupt.
+ */
static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect)
{
z_stream stream;
@@ -278,32 +309,6 @@ static int check_inflate(unsigned char *data, unsigned long len, unsigned long e
return st;
}
-/*
- * we are going to reuse the existing pack entry data. make
- * sure it is not corrupt.
- */
-static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len)
-{
- enum object_type type;
- unsigned long size, used;
-
- if (pack_to_stdout)
- return 0;
-
- /* the caller has already called use_packed_git() for us,
- * so it is safe to access the pack data from mmapped location.
- * make sure the entry inflates correctly.
- */
- used = unpack_object_header_gently(data, len, &type, &size);
- if (!used)
- return -1;
- if (type == OBJ_DELTA)
- used += 20; /* skip base object name */
- data += used;
- len -= used;
- return check_inflate(data, len, entry->size);
-}
-
static int revalidate_loose_object(struct object_entry *entry,
unsigned char *map,
unsigned long mapsize)
@@ -334,13 +339,10 @@ static unsigned long write_object(struct sha1file *f,
enum object_type obj_type;
int to_reuse = 0;
- if (entry->preferred_base)
- return 0;
-
obj_type = entry->type;
if (! entry->in_pack)
to_reuse = 0; /* can't reuse what we don't have */
- else if (obj_type == OBJ_DELTA)
+ else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
to_reuse = 1; /* check_object() decided it for us */
else if (obj_type != entry->in_pack_type)
to_reuse = 0; /* pack has delta which is unusable */
@@ -380,18 +382,35 @@ static unsigned long write_object(struct sha1file *f,
if (entry->delta) {
buf = delta_against(buf, size, entry);
size = entry->delta_size;
- obj_type = OBJ_DELTA;
+ obj_type = (allow_ofs_delta && entry->delta->offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
}
/*
* The object header is a byte of 'type' followed by zero or
- * more bytes of length. For deltas, the 20 bytes of delta
- * sha1 follows that.
+ * more bytes of length.
*/
hdrlen = encode_header(obj_type, size, header);
sha1write(f, header, hdrlen);
- if (entry->delta) {
- sha1write(f, entry->delta, 20);
+ if (obj_type == OBJ_OFS_DELTA) {
+ /*
+ * Deltas with relative base contain an additional
+ * encoding of the relative offset for the delta
+ * base from this object's position in the pack.
+ */
+ unsigned long ofs = entry->offset - entry->delta->offset;
+ unsigned pos = sizeof(header) - 1;
+ header[pos] = ofs & 127;
+ while (ofs >>= 7)
+ header[--pos] = 128 | (--ofs & 127);
+ sha1write(f, header + pos, sizeof(header) - pos);
+ hdrlen += sizeof(header) - pos;
+ } else if (obj_type == OBJ_REF_DELTA) {
+ /*
+ * Deltas with a base reference contain
+ * an additional 20 bytes for the base sha1.
+ */
+ sha1write(f, entry->delta->sha1, 20);
hdrlen += 20;
}
datalen = sha1write_compressed(f, buf, size);
@@ -399,21 +418,40 @@ static unsigned long write_object(struct sha1file *f,
}
else {
struct packed_git *p = entry->in_pack;
- use_packed_git(p);
- datalen = find_packed_object_size(p, entry->in_pack_offset);
- buf = (char *) p->pack_base + entry->in_pack_offset;
+ if (entry->delta) {
+ obj_type = (allow_ofs_delta && entry->delta->offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ reused_delta++;
+ }
+ hdrlen = encode_header(obj_type, entry->size, header);
+ sha1write(f, header, hdrlen);
+ if (obj_type == OBJ_OFS_DELTA) {
+ unsigned long ofs = entry->offset - entry->delta->offset;
+ unsigned pos = sizeof(header) - 1;
+ header[pos] = ofs & 127;
+ while (ofs >>= 7)
+ header[--pos] = 128 | (--ofs & 127);
+ sha1write(f, header + pos, sizeof(header) - pos);
+ hdrlen += sizeof(header) - pos;
+ } else if (obj_type == OBJ_REF_DELTA) {
+ sha1write(f, entry->delta->sha1, 20);
+ hdrlen += 20;
+ }
- if (revalidate_pack_entry(entry, buf, datalen))
+ use_packed_git(p);
+ buf = (char *) p->pack_base
+ + entry->in_pack_offset
+ + entry->in_pack_header_size;
+ datalen = find_packed_object_size(p, entry->in_pack_offset)
+ - entry->in_pack_header_size;
+ if (!pack_to_stdout && check_inflate(buf, datalen, entry->size))
die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
sha1write(f, buf, datalen);
unuse_packed_git(p);
- hdrlen = 0; /* not really */
- if (obj_type == OBJ_DELTA)
- reused_delta++;
reused++;
}
- if (obj_type == OBJ_DELTA)
+ if (entry->delta)
written_delta++;
written++;
return hdrlen + datalen;
@@ -423,17 +461,16 @@ static unsigned long write_one(struct sha1file *f,
struct object_entry *e,
unsigned long offset)
{
- if (e->offset)
+ if (e->offset || e->preferred_base)
/* offset starts from header size and cannot be zero
* if it is written already.
*/
return offset;
- e->offset = offset;
- offset += write_object(f, e);
- /* if we are deltified, write out its base object. */
+ /* if we are deltified, write out its base object first. */
if (e->delta)
offset = write_one(f, e->delta, offset);
- return offset;
+ e->offset = offset;
+ return offset + write_object(f, e);
}
static void write_pack_file(void)
@@ -443,15 +480,15 @@ static void write_pack_file(void)
unsigned long offset;
struct pack_header hdr;
unsigned last_percent = 999;
- int do_progress = 0;
+ int do_progress = progress;
- if (!base_name)
+ if (!base_name) {
f = sha1fd(1, "<stdout>");
- else {
+ do_progress >>= 1;
+ }
+ else
f = sha1create("%s-%s.%s", base_name,
sha1_to_hex(object_list_sha1), "pack");
- do_progress = progress;
- }
if (do_progress)
fprintf(stderr, "Writing %d objects.\n", nr_result);
@@ -899,26 +936,64 @@ static void check_object(struct object_entry *entry)
char type[20];
if (entry->in_pack && !entry->preferred_base) {
- unsigned char base[20];
- unsigned long size;
- struct object_entry *base_entry;
+ struct packed_git *p = entry->in_pack;
+ unsigned long left = p->pack_size - entry->in_pack_offset;
+ unsigned long size, used;
+ unsigned char *buf;
+ struct object_entry *base_entry = NULL;
+
+ use_packed_git(p);
+ buf = p->pack_base;
+ buf += entry->in_pack_offset;
/* We want in_pack_type even if we do not reuse delta.
* There is no point not reusing non-delta representations.
*/
- check_reuse_pack_delta(entry->in_pack,
- entry->in_pack_offset,
- base, &size,
- &entry->in_pack_type);
+ used = unpack_object_header_gently(buf, left,
+ &entry->in_pack_type, &size);
+ if (!used || left - used <= 20)
+ die("corrupt pack for %s", sha1_to_hex(entry->sha1));
/* Check if it is delta, and the base is also an object
* we are going to pack. If so we will reuse the existing
* delta.
*/
- if (!no_reuse_delta &&
- entry->in_pack_type == OBJ_DELTA &&
- (base_entry = locate_object_entry(base)) &&
- (!base_entry->preferred_base)) {
+ if (!no_reuse_delta) {
+ unsigned char c, *base_name;
+ unsigned long ofs;
+ /* there is at least 20 bytes left in the pack */
+ switch (entry->in_pack_type) {
+ case OBJ_REF_DELTA:
+ base_name = buf + used;
+ used += 20;
+ break;
+ case OBJ_OFS_DELTA:
+ c = buf[used++];
+ ofs = c & 127;
+ while (c & 128) {
+ ofs += 1;
+ if (!ofs || ofs & ~(~0UL >> 7))
+ die("delta base offset overflow in pack for %s",
+ sha1_to_hex(entry->sha1));
+ c = buf[used++];
+ ofs = (ofs << 7) + (c & 127);
+ }
+ if (ofs >= entry->in_pack_offset)
+ die("delta base offset out of bound for %s",
+ sha1_to_hex(entry->sha1));
+ ofs = entry->in_pack_offset - ofs;
+ base_name = find_packed_object_name(p, ofs);
+ break;
+ default:
+ base_name = NULL;
+ }
+ if (base_name)
+ base_entry = locate_object_entry(base_name);
+ }
+ unuse_packed_git(p);
+ entry->in_pack_header_size = used;
+
+ if (base_entry) {
/* Depth value does not matter - find_deltas()
* will never consider reused delta as the
@@ -927,9 +1002,9 @@ static void check_object(struct object_entry *entry)
*/
/* uncompressed size of the delta data */
- entry->size = entry->delta_size = size;
+ entry->size = size;
entry->delta = base_entry;
- entry->type = OBJ_DELTA;
+ entry->type = entry->in_pack_type;
entry->delta_sibling = base_entry->delta_child;
base_entry->delta_child = entry;
@@ -1450,10 +1525,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
local = 1;
continue;
}
- if (!strcmp("--progress", arg)) {
- progress = 1;
- continue;
- }
if (!strcmp("--incremental", arg)) {
incremental = 1;
continue;
@@ -1476,6 +1547,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
progress = 1;
continue;
}
+ if (!strcmp("--all-progress", arg)) {
+ progress = 2;
+ continue;
+ }
if (!strcmp("-q", arg)) {
progress = 0;
continue;
@@ -1484,6 +1559,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
no_reuse_delta = 1;
continue;
}
+ if (!strcmp("--delta-base-offset", arg)) {
+ allow_ofs_delta = 1;
+ continue;
+ }
if (!strcmp("--stdout", arg)) {
pack_to_stdout = 1;
continue;
@@ -1567,7 +1646,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
else {
if (nr_result)
prepare_pack(window, depth);
- if (progress && pack_to_stdout) {
+ if (progress == 1 && pack_to_stdout) {
/* the other end usually displays progress itself */
struct itimerval v = {{0,},};
setitimer(ITIMER_REAL, &v, NULL);
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
new file mode 100644
index 000000000..042d2718f
--- /dev/null
+++ b/builtin-pack-refs.c
@@ -0,0 +1,107 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char builtin_pack_refs_usage[] =
+"git-pack-refs [--all] [--prune]";
+
+struct ref_to_prune {
+ struct ref_to_prune *next;
+ unsigned char sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+struct pack_refs_cb_data {
+ int prune;
+ int all;
+ struct ref_to_prune *ref_to_prune;
+ FILE *refs_file;
+};
+
+static int do_not_prune(int flags)
+{
+ /* If it is already packed or if it is a symref,
+ * do not prune it.
+ */
+ return (flags & (REF_ISSYMREF|REF_ISPACKED));
+}
+
+static int handle_one_ref(const char *path, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct pack_refs_cb_data *cb = cb_data;
+
+ if (!cb->all && strncmp(path, "refs/tags/", 10))
+ return 0;
+ /* Do not pack the symbolic refs */
+ if (!(flags & REF_ISSYMREF))
+ fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+ if (cb->prune && !do_not_prune(flags)) {
+ int namelen = strlen(path) + 1;
+ struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
+ hashcpy(n->sha1, sha1);
+ strcpy(n->name, path);
+ n->next = cb->ref_to_prune;
+ cb->ref_to_prune = n;
+ }
+ return 0;
+}
+
+/* make sure nobody touched the ref, and unlink */
+static void prune_ref(struct ref_to_prune *r)
+{
+ struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+
+ if (lock) {
+ unlink(git_path("%s", r->name));
+ unlock_ref(lock);
+ }
+}
+
+static void prune_refs(struct ref_to_prune *r)
+{
+ while (r) {
+ prune_ref(r);
+ r = r->next;
+ }
+}
+
+static struct lock_file packed;
+
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+ int fd, i;
+ struct pack_refs_cb_data cbdata;
+
+ memset(&cbdata, 0, sizeof(cbdata));
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--prune")) {
+ cbdata.prune = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ cbdata.all = 1;
+ continue;
+ }
+ /* perhaps other parameters later... */
+ break;
+ }
+ if (i != argc)
+ usage(builtin_pack_refs_usage);
+
+ fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
+ cbdata.refs_file = fdopen(fd, "w");
+ if (!cbdata.refs_file)
+ die("unable to create ref-pack file structure (%s)",
+ strerror(errno));
+ for_each_ref(handle_one_ref, &cbdata);
+ fflush(cbdata.refs_file);
+ fsync(fd);
+ fclose(cbdata.refs_file);
+ if (commit_lock_file(&packed) < 0)
+ die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+ if (cbdata.prune)
+ prune_refs(cbdata.ref_to_prune);
+ return 0;
+}
diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c
index 960db4985..24e3b0a8c 100644
--- a/builtin-prune-packed.c
+++ b/builtin-prune-packed.c
@@ -4,9 +4,7 @@
static const char prune_packed_usage[] =
"git-prune-packed [-n]";
-static int dryrun;
-
-static void prune_dir(int i, DIR *dir, char *pathname, int len)
+static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun)
{
struct dirent *de;
char hex[40];
@@ -31,7 +29,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len)
rmdir(pathname);
}
-static void prune_packed_objects(void)
+void prune_packed_objects(int dryrun)
{
int i;
static char pathname[PATH_MAX];
@@ -50,7 +48,7 @@ static void prune_packed_objects(void)
d = opendir(pathname);
if (!d)
continue;
- prune_dir(i, d, pathname, len + 3);
+ prune_dir(i, d, pathname, len + 3, dryrun);
closedir(d);
}
}
@@ -58,6 +56,7 @@ static void prune_packed_objects(void)
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
int i;
+ int dryrun = 0;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -73,6 +72,6 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix)
usage(prune_packed_usage);
}
sync();
- prune_packed_objects();
+ prune_packed_objects(dryrun);
return 0;
}
diff --git a/builtin-prune.c b/builtin-prune.c
index 6228c7907..d853902c5 100644
--- a/builtin-prune.c
+++ b/builtin-prune.c
@@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs)
}
}
-static int add_one_ref(const char *path, const unsigned char *sha1)
+static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *object = parse_object(sha1);
if (!object)
@@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
revs.tree_objects = 1;
/* Add all external refs */
- for_each_ref(add_one_ref);
+ for_each_ref(add_one_ref, NULL);
/* Add all refs from the index file */
add_cache_refs();
@@ -255,5 +255,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
prune_object_dir(get_object_directory());
+ sync();
+ prune_packed_objects(show_only);
return 0;
}
diff --git a/builtin-push.c b/builtin-push.c
index f5150ed82..d23974e70 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -10,7 +10,7 @@
static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]";
-static int all, tags, force, thin = 1;
+static int all, tags, force, thin = 1, verbose;
static const char *execute;
#define BUF_SIZE (2084)
@@ -27,7 +27,7 @@ static void add_refspec(const char *ref)
refspec_nr = nr;
}
-static int expand_one_ref(const char *ref, const unsigned char *sha1)
+static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
{
/* Ignore the "refs/" at the beginning of the refname */
ref += 5;
@@ -51,7 +51,7 @@ static void expand_refspecs(void)
}
if (!tags)
return;
- for_each_ref(expand_one_ref);
+ for_each_ref(expand_one_ref, NULL);
}
static void set_refspecs(const char **refs, int nr)
@@ -248,6 +248,8 @@ static int do_push(const char *repo)
while (dest_refspec_nr--)
argv[dest_argc++] = *dest_refspec++;
argv[dest_argc] = NULL;
+ if (verbose)
+ fprintf(stderr, "Pushing to %s\n", dest);
err = run_command_v(argc, argv);
if (!err)
continue;
@@ -281,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
i++;
break;
}
+ if (!strcmp(arg, "-v")) {
+ verbose=1;
+ continue;
+ }
+ if (!strncmp(arg, "--repo=", 7)) {
+ repo = arg+7;
+ continue;
+ }
if (!strcmp(arg, "--all")) {
all = 1;
continue;
diff --git a/builtin-repo-config.c b/builtin-repo-config.c
index f60cee1dc..7b6e5725a 100644
--- a/builtin-repo-config.c
+++ b/builtin-repo-config.c
@@ -3,7 +3,7 @@
#include <regex.h>
static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
static char *key;
static regex_t *key_regexp;
@@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
type = T_BOOL;
else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
return git_config(show_all_config);
- else
+ else if (!strcmp(argv[1], "--global")) {
+ char *home = getenv("HOME");
+ if (home) {
+ char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+ setenv("GIT_CONFIG", user_config, 1);
+ free(user_config);
+ } else {
+ die("$HOME not set");
+ }
+ } else
break;
argc--;
argv++;
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index fd3ccc854..3b716fba1 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -137,7 +137,7 @@ static void show_default(void)
}
}
-static int show_reference(const char *refname, const unsigned char *sha1)
+static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
show_rev(NORMAL, sha1, refname);
return 0;
@@ -299,19 +299,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--all")) {
- for_each_ref(show_reference);
+ for_each_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--branches")) {
- for_each_branch_ref(show_reference);
+ for_each_branch_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--tags")) {
- for_each_tag_ref(show_reference);
+ for_each_tag_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--remotes")) {
- for_each_remote_ref(show_reference);
+ for_each_remote_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--show-prefix")) {
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
index 578c9fafd..fb1a4000d 100644
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
@@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top)
compare_ref_name);
}
-static int append_ref(const char *refname, const unsigned char *sha1)
+static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
int i;
@@ -369,7 +369,7 @@ static int append_ref(const char *refname, const unsigned char *sha1)
return 0;
}
-static int append_head_ref(const char *refname, const unsigned char *sha1)
+static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
unsigned char tmp[20];
int ofs = 11;
@@ -380,14 +380,14 @@ static int append_head_ref(const char *refname, const unsigned char *sha1)
*/
if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
ofs = 5;
- return append_ref(refname + ofs, sha1);
+ return append_ref(refname + ofs, sha1, flag, cb_data);
}
-static int append_tag_ref(const char *refname, const unsigned char *sha1)
+static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
if (strncmp(refname, "refs/tags/", 10))
return 0;
- return append_ref(refname + 5, sha1);
+ return append_ref(refname + 5, sha1, flag, cb_data);
}
static const char *match_ref_pattern = NULL;
@@ -401,7 +401,7 @@ static int count_slash(const char *s)
return cnt;
}
-static int append_matching_ref(const char *refname, const unsigned char *sha1)
+static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
/* we want to allow pattern hold/<asterisk> to show all
* branches under refs/heads/hold/, and v0.99.9? to show
@@ -417,41 +417,39 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1)
if (fnmatch(match_ref_pattern, tail, 0))
return 0;
if (!strncmp("refs/heads/", refname, 11))
- return append_head_ref(refname, sha1);
+ return append_head_ref(refname, sha1, flag, cb_data);
if (!strncmp("refs/tags/", refname, 10))
- return append_tag_ref(refname, sha1);
- return append_ref(refname, sha1);
+ return append_tag_ref(refname, sha1, flag, cb_data);
+ return append_ref(refname, sha1, flag, cb_data);
}
static void snarf_refs(int head, int tag)
{
if (head) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_head_ref);
+ for_each_ref(append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
if (tag) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_tag_ref);
+ for_each_ref(append_tag_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
}
-static int rev_is_head(char *head_path, int headlen, char *name,
+static int rev_is_head(char *head, int headlen, char *name,
unsigned char *head_sha1, unsigned char *sha1)
{
- int namelen;
- if ((!head_path[0]) ||
+ if ((!head[0]) ||
(head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
return 0;
- namelen = strlen(name);
- if ((headlen < namelen) ||
- memcmp(head_path + headlen - namelen, name, namelen))
- return 0;
- if (headlen == namelen ||
- head_path[headlen - namelen - 1] == '/')
- return 1;
- return 0;
+ if (!strncmp(head, "refs/heads/", 11))
+ head += 11;
+ if (!strncmp(name, "refs/heads/", 11))
+ name += 11;
+ else if (!strncmp(name, "heads/", 6))
+ name += 6;
+ return !strcmp(head, name);
}
static int show_merge_base(struct commit_list *seen, int num_rev)
@@ -495,7 +493,7 @@ static void append_one_rev(const char *av)
{
unsigned char revkey[20];
if (!get_sha1(av, revkey)) {
- append_ref(av, revkey);
+ append_ref(av, revkey, 0, NULL);
return;
}
if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
@@ -503,7 +501,7 @@ static void append_one_rev(const char *av)
int saved_matches = ref_name_cnt;
match_ref_pattern = av;
match_ref_slash = count_slash(av);
- for_each_ref(append_matching_ref);
+ for_each_ref(append_matching_ref, NULL);
if (saved_matches == ref_name_cnt &&
ref_name_cnt < MAX_REVS)
error("no matching refs with %s", av);
@@ -559,9 +557,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int all_heads = 0, all_tags = 0;
int all_mask, all_revs;
int lifo = 1;
- char head_path[128];
- const char *head_path_p;
- int head_path_len;
+ char head[128];
+ const char *head_p;
+ int head_len;
unsigned char head_sha1[20];
int merge_base = 0;
int independent = 0;
@@ -638,31 +636,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
ac--; av++;
}
- head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
- if (head_path_p) {
- head_path_len = strlen(head_path_p);
- memcpy(head_path, head_path_p, head_path_len + 1);
+ head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (head_p) {
+ head_len = strlen(head_p);
+ memcpy(head, head_p, head_len + 1);
}
else {
- head_path_len = 0;
- head_path[0] = 0;
+ head_len = 0;
+ head[0] = 0;
}
- if (with_current_branch && head_path_p) {
+ if (with_current_branch && head_p) {
int has_head = 0;
for (i = 0; !has_head && i < ref_name_cnt; i++) {
/* We are only interested in adding the branch
* HEAD points at.
*/
- if (rev_is_head(head_path,
- head_path_len,
+ if (rev_is_head(head,
+ head_len,
ref_name[i],
head_sha1, NULL))
has_head++;
}
if (!has_head) {
- int pfxlen = strlen(git_path("refs/heads/"));
- append_one_rev(head_path + pfxlen);
+ int pfxlen = strlen("refs/heads/");
+ append_one_rev(head + pfxlen);
}
}
@@ -713,8 +711,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
- int is_head = rev_is_head(head_path,
- head_path_len,
+ int is_head = rev_is_head(head,
+ head_len,
ref_name[i],
head_sha1,
rev[i]->object.sha1);
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
new file mode 100644
index 000000000..06ec400d7
--- /dev/null
+++ b/builtin-show-ref.c
@@ -0,0 +1,147 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]";
+
+static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
+ found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
+static const char **pattern;
+
+static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+ struct object *obj;
+ const char *hex;
+
+ if (tags_only || heads_only) {
+ int match;
+
+ match = heads_only && !strncmp(refname, "refs/heads/", 11);
+ match |= tags_only && !strncmp(refname, "refs/tags/", 10);
+ if (!match)
+ return 0;
+ }
+ if (pattern) {
+ int reflen = strlen(refname);
+ const char **p = pattern, *m;
+ while ((m = *p++) != NULL) {
+ int len = strlen(m);
+ if (len > reflen)
+ continue;
+ if (memcmp(m, refname + reflen - len, len))
+ continue;
+ if (len == reflen)
+ goto match;
+ /* "--verify" requires an exact match */
+ if (verify)
+ continue;
+ if (refname[reflen - len - 1] == '/')
+ goto match;
+ }
+ return 0;
+ }
+
+match:
+ found_match++;
+ obj = parse_object(sha1);
+ if (!obj) {
+ if (quiet)
+ return 0;
+ die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
+ }
+ if (quiet)
+ return 0;
+
+ hex = find_unique_abbrev(sha1, abbrev);
+ if (hash_only)
+ printf("%s\n", hex);
+ else
+ printf("%s %s\n", hex, refname);
+ if (deref_tags && obj->type == OBJ_TAG) {
+ obj = deref_tag(obj, refname, 0);
+ hex = find_unique_abbrev(obj->sha1, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
+ return 0;
+}
+
+int cmd_show_ref(int argc, const char **argv, const char *prefix)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (*arg != '-') {
+ pattern = argv + i;
+ break;
+ }
+ if (!strcmp(arg, "--")) {
+ pattern = argv + i + 1;
+ if (!*pattern)
+ pattern = NULL;
+ break;
+ }
+ if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) {
+ show_head = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) {
+ deref_tags = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) {
+ hash_only = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--hash=", 7) ||
+ (!strncmp(arg, "--abbrev", 8) &&
+ (arg[8] == '=' || arg[8] == '\0'))) {
+ if (arg[3] != 'h' && !arg[8])
+ /* --abbrev only */
+ abbrev = DEFAULT_ABBREV;
+ else {
+ /* --hash= or --abbrev= */
+ char *end;
+ if (arg[3] == 'h') {
+ hash_only = 1;
+ arg += 7;
+ }
+ else
+ arg += 9;
+ abbrev = strtoul(arg, &end, 10);
+ if (*end || abbrev > 40)
+ usage(show_ref_usage);
+ if (abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ }
+ continue;
+ }
+ if (!strcmp(arg, "--verify")) {
+ verify = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--tags")) {
+ tags_only = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--heads")) {
+ heads_only = 1;
+ continue;
+ }
+ usage(show_ref_usage);
+ }
+ if (show_head)
+ head_ref(show_ref, NULL);
+ for_each_ref(show_ref, NULL);
+ if (!found_match) {
+ if (verify && !quiet)
+ die("No match");
+ return 1;
+ }
+ return 0;
+}
diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c
index 1d3a5e229..d8be0527f 100644
--- a/builtin-symbolic-ref.c
+++ b/builtin-symbolic-ref.c
@@ -1,5 +1,6 @@
#include "builtin.h"
#include "cache.h"
+#include "refs.h"
static const char git_symbolic_ref_usage[] =
"git-symbolic-ref name [ref]";
@@ -7,15 +8,14 @@ static const char git_symbolic_ref_usage[] =
static void check_symref(const char *HEAD)
{
unsigned char sha1[20];
- const char *git_HEAD = xstrdup(git_path("%s", HEAD));
- const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
- if (git_refs_heads_master) {
- /* we want to strip the .git/ part */
- int pfxlen = strlen(git_HEAD) - strlen(HEAD);
- puts(git_refs_heads_master + pfxlen);
- }
- else
+ int flag;
+ const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+
+ if (!refs_heads_master)
die("No such ref: %s", HEAD);
+ else if (!(flag & REF_ISSYMREF))
+ die("ref %s is not a symbolic ref", HEAD);
+ puts(refs_heads_master);
}
int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
@@ -26,7 +26,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
check_symref(argv[1]);
break;
case 3:
- create_symref(xstrdup(git_path("%s", argv[1])), argv[2]);
+ create_symref(argv[1], argv[2]);
break;
default:
usage(git_symbolic_ref_usage);
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
index 4f96bcae3..e6d757484 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -15,14 +15,14 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-fil
/* We always read in 4kB chunks. */
static unsigned char buffer[4096];
-static unsigned long offset, len;
+static unsigned long offset, len, consumed_bytes;
static SHA_CTX ctx;
/*
* Make sure at least "min" bytes are available in the buffer, and
* return the pointer to the buffer.
*/
-static void * fill(int min)
+static void *fill(int min)
{
if (min <= len)
return buffer + offset;
@@ -30,7 +30,7 @@ static void * fill(int min)
die("cannot fill %d bytes", min);
if (offset) {
SHA1_Update(&ctx, buffer, offset);
- memcpy(buffer, buffer + offset, len);
+ memmove(buffer, buffer + offset, len);
offset = 0;
}
do {
@@ -51,6 +51,7 @@ static void use(int bytes)
die("used more bytes than were available");
len -= bytes;
offset += bytes;
+ consumed_bytes += bytes;
}
static void *get_data(unsigned long size)
@@ -89,35 +90,49 @@ static void *get_data(unsigned long size)
struct delta_info {
unsigned char base_sha1[20];
+ unsigned long base_offset;
unsigned long size;
void *delta;
+ unsigned nr;
struct delta_info *next;
};
static struct delta_info *delta_list;
-static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+ unsigned long base_offset,
+ void *delta, unsigned long size)
{
struct delta_info *info = xmalloc(sizeof(*info));
hashcpy(info->base_sha1, base_sha1);
+ info->base_offset = base_offset;
info->size = size;
info->delta = delta;
+ info->nr = nr;
info->next = delta_list;
delta_list = info;
}
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+struct obj_info {
+ unsigned long offset;
+ unsigned char sha1[20];
+};
+
+static struct obj_info *obj_list;
+
+static void added_object(unsigned nr, const char *type, void *data,
+ unsigned long size);
-static void write_object(void *buf, unsigned long size, const char *type)
+static void write_object(unsigned nr, void *buf, unsigned long size,
+ const char *type)
{
- unsigned char sha1[20];
- if (write_sha1_file(buf, size, type, sha1) < 0)
+ if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0)
die("failed to write object");
- added_object(sha1, type, buf, size);
+ added_object(nr, type, buf, size);
}
-static void resolve_delta(const char *type,
+static void resolve_delta(unsigned nr, const char *type,
void *base, unsigned long base_size,
void *delta, unsigned long delta_size)
{
@@ -130,20 +145,23 @@ static void resolve_delta(const char *type,
if (!result)
die("failed to apply delta");
free(delta);
- write_object(result, result_size, type);
+ write_object(nr, result, result_size, type);
free(result);
}
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+static void added_object(unsigned nr, const char *type, void *data,
+ unsigned long size)
{
struct delta_info **p = &delta_list;
struct delta_info *info;
while ((info = *p) != NULL) {
- if (!hashcmp(info->base_sha1, sha1)) {
+ if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+ info->base_offset == obj_list[nr].offset) {
*p = info->next;
p = &delta_list;
- resolve_delta(type, data, size, info->delta, info->size);
+ resolve_delta(info->nr, type, data, size,
+ info->delta, info->size);
free(info);
continue;
}
@@ -151,7 +169,8 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
}
}
-static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
+static void unpack_non_delta_entry(enum object_type kind, unsigned long size,
+ unsigned nr)
{
void *buf = get_data(size);
const char *type;
@@ -164,30 +183,80 @@ static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
default: die("bad type %d", kind);
}
if (!dry_run && buf)
- write_object(buf, size, type);
+ write_object(nr, buf, size, type);
free(buf);
}
-static void unpack_delta_entry(unsigned long delta_size)
+static void unpack_delta_entry(enum object_type kind, unsigned long delta_size,
+ unsigned nr)
{
void *delta_data, *base;
unsigned long base_size;
char type[20];
unsigned char base_sha1[20];
- hashcpy(base_sha1, fill(20));
- use(20);
+ if (kind == OBJ_REF_DELTA) {
+ hashcpy(base_sha1, fill(20));
+ use(20);
+ delta_data = get_data(delta_size);
+ if (dry_run || !delta_data) {
+ free(delta_data);
+ return;
+ }
+ if (!has_sha1_file(base_sha1)) {
+ hashcpy(obj_list[nr].sha1, null_sha1);
+ add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+ return;
+ }
+ } else {
+ unsigned base_found = 0;
+ unsigned char *pack, c;
+ unsigned long base_offset;
+ unsigned lo, mid, hi;
- delta_data = get_data(delta_size);
- if (dry_run || !delta_data) {
- free(delta_data);
- return;
- }
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || base_offset & ~(~0UL >> 7))
+ die("offset value overflow for delta base object");
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ base_offset = obj_list[nr].offset - base_offset;
- if (!has_sha1_file(base_sha1)) {
- add_delta_to_list(base_sha1, delta_data, delta_size);
- return;
+ delta_data = get_data(delta_size);
+ if (dry_run || !delta_data) {
+ free(delta_data);
+ return;
+ }
+ lo = 0;
+ hi = nr;
+ while (lo < hi) {
+ mid = (lo + hi)/2;
+ if (base_offset < obj_list[mid].offset) {
+ hi = mid;
+ } else if (base_offset > obj_list[mid].offset) {
+ lo = mid + 1;
+ } else {
+ hashcpy(base_sha1, obj_list[mid].sha1);
+ base_found = !is_null_sha1(base_sha1);
+ break;
+ }
+ }
+ if (!base_found) {
+ /* The delta base object is itself a delta that
+ has not been resolved yet. */
+ hashcpy(obj_list[nr].sha1, null_sha1);
+ add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+ return;
+ }
}
+
base = read_sha1_file(base_sha1, type, &base_size);
if (!base) {
error("failed to read delta-pack base object %s",
@@ -197,7 +266,7 @@ static void unpack_delta_entry(unsigned long delta_size)
has_errors = 1;
return;
}
- resolve_delta(type, base, base_size, delta_data, delta_size);
+ resolve_delta(nr, type, base, base_size, delta_data, delta_size);
free(base);
}
@@ -208,6 +277,8 @@ static void unpack_one(unsigned nr, unsigned total)
unsigned long size;
enum object_type type;
+ obj_list[nr].offset = consumed_bytes;
+
pack = fill(1);
c = *pack;
use(1);
@@ -216,7 +287,7 @@ static void unpack_one(unsigned nr, unsigned total)
shift = 4;
while (c & 0x80) {
pack = fill(1);
- c = *pack++;
+ c = *pack;
use(1);
size += (c & 0x7f) << shift;
shift += 7;
@@ -225,13 +296,14 @@ static void unpack_one(unsigned nr, unsigned total)
static unsigned long last_sec;
static unsigned last_percent;
struct timeval now;
- unsigned percentage = (nr * 100) / total;
+ unsigned percentage = ((nr+1) * 100) / total;
gettimeofday(&now, NULL);
if (percentage != last_percent || now.tv_sec != last_sec) {
last_sec = now.tv_sec;
last_percent = percentage;
- fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+ fprintf(stderr, "%4u%% (%u/%u) done\r",
+ percentage, (nr+1), total);
}
}
switch (type) {
@@ -239,10 +311,11 @@ static void unpack_one(unsigned nr, unsigned total)
case OBJ_TREE:
case OBJ_BLOB:
case OBJ_TAG:
- unpack_non_delta_entry(type, size);
+ unpack_non_delta_entry(type, size, nr);
return;
- case OBJ_DELTA:
- unpack_delta_entry(size);
+ case OBJ_REF_DELTA:
+ case OBJ_OFS_DELTA:
+ unpack_delta_entry(type, size, nr);
return;
default:
error("bad object type %d", type);
@@ -265,9 +338,10 @@ static void unpack_all(void)
die("unknown pack file version %d", ntohl(hdr->hdr_version));
fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+ obj_list = xmalloc(nr_objects * sizeof(*obj_list));
use(sizeof(struct pack_header));
for (i = 0; i < nr_objects; i++)
- unpack_one(i+1, nr_objects);
+ unpack_one(i, nr_objects);
if (delta_list)
die("unresolved deltas left after unpacking");
}
@@ -297,6 +371,21 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
recover = 1;
continue;
}
+ if (!strncmp(arg, "--pack_header=", 14)) {
+ struct pack_header *hdr;
+ char *c;
+
+ hdr = (struct pack_header *)buffer;
+ hdr->hdr_signature = htonl(PACK_SIGNATURE);
+ hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+ if (*c != ',')
+ die("bad %s", arg);
+ hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+ if (*c)
+ die("bad %s", arg);
+ len = sizeof(*hdr);
+ continue;
+ }
usage(unpack_usage);
}
diff --git a/builtin-update-index.c b/builtin-update-index.c
index a3c0a455a..7f9c63846 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -406,9 +406,9 @@ static int unresolve_one(const char *path)
static void read_head_pointers(void)
{
- if (read_ref(git_path("HEAD"), head_sha1))
+ if (read_ref("HEAD", head_sha1))
die("No HEAD -- no initial commit yet?\n");
- if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+ if (read_ref("MERGE_HEAD", merge_head_sha1)) {
fprintf(stderr, "Not in the middle of a merge.\n");
exit(0);
}
@@ -445,7 +445,7 @@ static int do_reupdate(int ac, const char **av,
int has_head = 1;
const char **pathspec = get_pathspec(prefix, av + 1);
- if (read_ref(git_path("HEAD"), head_sha1))
+ if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial
* commit. Update everything in the index.
*/
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
index 90a3da53a..b34e5987d 100644
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
@@ -3,15 +3,16 @@
#include "builtin.h"
static const char git_update_ref_usage[] =
-"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
+"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
struct ref_lock *lock;
unsigned char sha1[20], oldsha1[20];
- int i;
+ int i, delete;
+ delete = 0;
setup_ident();
git_config(git_default_config);
@@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with \\n in message.");
continue;
}
+ if (!strcmp("-d", argv[i])) {
+ delete = 1;
+ continue;
+ }
if (!refname) {
refname = argv[i];
continue;
@@ -44,11 +49,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value);
+
+ if (delete) {
+ if (oldval)
+ usage(git_update_ref_usage);
+ return delete_ref(refname, sha1);
+ }
+
hashclr(oldsha1);
- if (oldval && get_sha1(oldval, oldsha1))
+ if (oldval && *oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
- lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+ lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
if (!lock)
return 1;
if (write_ref_sha1(lock, sha1, msg) < 0)
diff --git a/builtin.h b/builtin.h
index 7451ce64e..24803dfea 100644
--- a/builtin.h
+++ b/builtin.h
@@ -11,13 +11,17 @@ extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const cha
extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip);
extern void stripspace(FILE *in, FILE *out);
extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
+extern void prune_packed_objects(int);
extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
extern int cmd_archive(int argc, const char **argv, const char *prefix);
+extern int cmd_branch(int argc, const char **argv, const char *prefix);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
+extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
@@ -26,6 +30,7 @@ extern int cmd_diff(int argc, const char **argv, const char *prefix);
extern int cmd_diff_stages(int argc, const char **argv, const char *prefix);
extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
+extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
extern int cmd_grep(int argc, const char **argv, const char *prefix);
@@ -49,8 +54,8 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
-extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
+extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
@@ -63,5 +68,7 @@ extern int cmd_version(int argc, const char **argv, const char *prefix);
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
#endif
diff --git a/cache-tree.c b/cache-tree.c
index d388848dd..a80326289 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -2,7 +2,9 @@
#include "tree.h"
#include "cache-tree.h"
+#ifndef DEBUG
#define DEBUG 0
+#endif
struct cache_tree *cache_tree(void)
{
diff --git a/cache.h b/cache.h
index c35470107..f2ec5c8c1 100644
--- a/cache.h
+++ b/cache.h
@@ -179,6 +179,7 @@ struct lock_file {
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
extern int commit_lock_file(struct lock_file *);
extern void rollback_lock_file(struct lock_file *);
+extern int delete_ref(const char *, unsigned char *sha1);
/* Environment bits from configuration mechanism */
extern int use_legacy_headers;
@@ -188,7 +189,6 @@ extern int prefer_symlink_refs;
extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
extern int shared_repository;
-extern int deny_non_fast_forwards;
extern const char *apply_default_whitespace;
extern int zlib_compression_level;
@@ -269,8 +269,9 @@ enum object_type {
OBJ_TREE = 2,
OBJ_BLOB = 3,
OBJ_TAG = 4,
- /* 5/6 for future expansion */
- OBJ_DELTA = 7,
+ /* 5 for future expansion */
+ OBJ_OFS_DELTA = 6,
+ OBJ_REF_DELTA = 7,
OBJ_BAD,
};
@@ -288,9 +289,9 @@ extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);
-extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
-extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
-extern int validate_symref(const char *git_HEAD);
+extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+extern int create_symref(const char *ref, const char *refs_heads_master);
+extern int validate_symref(const char *ref);
extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
@@ -375,6 +376,7 @@ extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
char *idx_path);
extern void prepare_packed_git(void);
+extern void reprepare_packed_git(void);
extern void install_packed_git(struct packed_git *pack);
extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
@@ -414,10 +416,6 @@ extern int copy_fd(int ifd, int ofd);
extern void write_or_die(int fd, const void *buf, size_t count);
extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
-/* Finish off pack transfer receiving end */
-extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int);
-extern int receive_keep_pack(int fd[2], const char *me, int quiet, int);
-
/* pager.c */
extern void setup_pager(void);
extern int pager_in_use;
diff --git a/check-builtins.sh b/check-builtins.sh
new file mode 100755
index 000000000..d6fe6cf17
--- /dev/null
+++ b/check-builtins.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+{
+ cat <<\EOF
+sayIt:
+ $(foreach b,$(BUILT_INS),echo XXX $b YYY;)
+EOF
+ cat Makefile
+} |
+make -f - sayIt 2>/dev/null |
+sed -n -e 's/.*XXX \(.*\) YYY.*/\1/p' |
+sort |
+{
+ bad=0
+ while read builtin
+ do
+ base=`expr "$builtin" : 'git-\(.*\)'`
+ x=`sed -ne 's/.*{ "'$base'", \(cmd_[^, ]*\).*/'$base' \1/p' git.c`
+ if test -z "$x"
+ then
+ echo "$base is builtin but not listed in git.c command list"
+ bad=1
+ fi
+ for sfx in sh perl py
+ do
+ if test -f "$builtin.$sfx"
+ then
+ echo "$base is builtin but $builtin.$sfx still exists"
+ bad=1
+ fi
+ done
+ done
+ exit $bad
+}
diff --git a/combine-diff.c b/combine-diff.c
index 65c786807..29d0c9cf9 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -489,6 +489,16 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long
printf(" -%lu,%lu", l0, l1-l0);
}
+static int hunk_comment_line(const char *bol)
+{
+ int ch;
+
+ if (!bol)
+ return 0;
+ ch = *bol & 0xff;
+ return (isalpha(ch) || ch == '_' || ch == '$');
+}
+
static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
int use_color)
{
@@ -508,8 +518,13 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
struct sline *sl = &sline[lno];
unsigned long hunk_end;
unsigned long rlines;
- while (lno <= cnt && !(sline[lno].flag & mark))
+ const char *hunk_comment = NULL;
+
+ while (lno <= cnt && !(sline[lno].flag & mark)) {
+ if (hunk_comment_line(sline[lno].bol))
+ hunk_comment = sline[lno].bol;
lno++;
+ }
if (cnt < lno)
break;
else {
@@ -526,6 +541,22 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
show_parent_lno(sline, lno, hunk_end, i);
printf(" +%lu,%lu ", lno+1, rlines);
for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+
+ if (hunk_comment) {
+ int comment_end = 0;
+ for (i = 0; i < 40; i++) {
+ int ch = hunk_comment[i] & 0xff;
+ if (!ch || ch == '\n')
+ break;
+ if (!isspace(ch))
+ comment_end = i;
+ }
+ if (comment_end)
+ putchar(' ');
+ for (i = 0; i < comment_end; i++)
+ putchar(hunk_comment[i]);
+ }
+
printf("%s\n", c_reset);
while (lno < hunk_end) {
struct lline *ll;
@@ -707,8 +738,10 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
int use_color = opt->color_diff;
const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+ int added = 0;
+ int deleted = 0;
- if (rev->loginfo)
+ if (rev->loginfo && !rev->no_commit_id)
show_log(rev, opt->msg_sep);
dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
elem->path, c_meta, c_reset);
@@ -722,7 +755,10 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
printf("..%s%s\n", abb, c_reset);
if (mode_differs) {
- int added = !!elem->mode;
+ deleted = !elem->mode;
+
+ /* We say it was added if nobody had it */
+ added = !deleted;
for (i = 0; added && i < num_parent; i++)
if (elem->parent[i].status !=
DIFF_STATUS_ADDED)
@@ -731,7 +767,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
printf("%snew file mode %06o",
c_meta, elem->mode);
else {
- if (!elem->mode)
+ if (deleted)
printf("%sdeleted file ", c_meta);
printf("mode ");
for (i = 0; i < num_parent; i++) {
@@ -743,8 +779,14 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
}
printf("%s\n", c_reset);
}
- dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
- dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
+ if (added)
+ dump_quoted_path("--- /dev/", "null", c_meta, c_reset);
+ else
+ dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
+ if (deleted)
+ dump_quoted_path("+++ /dev/", "null", c_meta, c_reset);
+ else
+ dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
dump_sline(sline, cnt, num_parent, opt->color_diff);
}
free(result);
@@ -777,7 +819,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
if (!line_termination)
inter_name_termination = 0;
- if (rev->loginfo)
+ if (rev->loginfo && !rev->no_commit_id)
show_log(rev, opt->msg_sep);
if (opt->output_format & DIFF_FORMAT_RAW) {
@@ -849,7 +891,7 @@ void diff_tree_combined(const unsigned char *sha1,
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diffopts.recursive = 1;
- show_log_first = !!rev->loginfo;
+ show_log_first = !!rev->loginfo && !rev->no_commit_id;
needsep = 0;
/* find set of paths that everybody touches */
for (i = 0; i < num_parent; i++) {
diff --git a/config.c b/config.c
index e8f0caf7c..3cae3901a 100644
--- a/config.c
+++ b/config.c
@@ -103,6 +103,11 @@ static char *parse_value(void)
}
}
+static inline int iskeychar(int c)
+{
+ return isalnum(c) || c == '-';
+}
+
static int get_value(config_fn_t fn, char *name, unsigned int len)
{
int c;
@@ -113,7 +118,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
c = get_next_char();
if (c == EOF)
break;
- if (!isalnum(c))
+ if (!iskeychar(c))
break;
name[len++] = tolower(c);
if (len >= MAXNAME)
@@ -181,7 +186,7 @@ static int get_base_var(char *name)
return baselen;
if (isspace(c))
return get_extended_base_var(name, baselen, c);
- if (!isalnum(c) && c != '.')
+ if (!iskeychar(c) && c != '.')
return -1;
if (baselen > MAXNAME / 2)
return -1;
@@ -573,7 +578,7 @@ int git_config_set_multivar(const char* key, const char* value,
dot = 1;
/* Leave the extended basename untouched.. */
if (!dot || i > store.baselen) {
- if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+ if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
fprintf(stderr, "invalid key: %s\n", key);
free(store.key);
ret = 1;
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index d9cb17d0b..a43a17716 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -19,15 +19,20 @@
# source ~/.git-completion.sh
#
+__gitdir ()
+{
+ echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}"
+}
+
__git_refs ()
{
- local cmd i is_hash=y
- if [ -d "$1" ]; then
+ local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+ if [ -d "$dir" ]; then
cmd=git-peek-remote
else
cmd=git-ls-remote
fi
- for i in $($cmd "$1" 2>/dev/null); do
+ for i in $($cmd "$dir" 2>/dev/null); do
case "$is_hash,$i" in
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
@@ -40,13 +45,13 @@ __git_refs ()
__git_refs2 ()
{
- local cmd i is_hash=y
- if [ -d "$1" ]; then
+ local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+ if [ -d "$dir" ]; then
cmd=git-peek-remote
else
cmd=git-ls-remote
fi
- for i in $($cmd "$1" 2>/dev/null); do
+ for i in $($cmd "$dir" 2>/dev/null); do
case "$is_hash,$i" in
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
@@ -59,25 +64,34 @@ __git_refs2 ()
__git_remotes ()
{
- local i REVERTGLOB=$(shopt -p nullglob)
+ local i ngoff IFS=$'\n' d="$(__gitdir)"
+ shopt -q nullglob || ngoff=1
shopt -s nullglob
- for i in .git/remotes/*; do
- echo ${i#.git/remotes/}
+ for i in "$d/remotes"/*; do
+ echo ${i#$d/remotes/}
+ done
+ [ "$ngoff" ] && shopt -u nullglob
+ for i in $(git --git-dir="$d" repo-config --list); do
+ case "$i" in
+ remote.*.url=*)
+ i="${i#remote.}"
+ echo "${i/.url=*/}"
+ ;;
+ esac
done
- $REVERTGLOB
}
__git_complete_file ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
?*:*)
- local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')"
- cur="$(echo "$cur" | sed 's,^.*:,,')"
+ ref="${cur%%:*}"
+ cur="${cur#*:}"
case "$cur" in
?*/*)
- pfx="$(echo "$cur" | sed 's,/[^/]*$,,')"
- cur="$(echo "$cur" | sed 's,^.*/,,')"
+ pfx="${cur%/*}"
+ cur="${cur##*/}"
ls="$ref:$pfx"
pfx="$pfx/"
;;
@@ -86,7 +100,7 @@ __git_complete_file ()
;;
esac
COMPREPLY=($(compgen -P "$pfx" \
- -W "$(git-ls-tree "$ls" \
+ -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
| sed '/^100... blob /s,^.* ,,
/^040000 tree /{
s,^.* ,,
@@ -96,15 +110,40 @@ __git_complete_file ()
-- "$cur"))
;;
*)
- COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
;;
esac
}
+__git_aliases ()
+{
+ local i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" repo-config --list); do
+ case "$i" in
+ alias.*)
+ i="${i#alias.}"
+ echo "${i/=*/}"
+ ;;
+ esac
+ done
+}
+
+__git_aliased_command ()
+{
+ local word cmdline=$(git --git-dir="$(__gitdir)" \
+ repo-config --get "alias.$1")
+ for word in $cmdline; do
+ if [ "${word##-*}" ]; then
+ echo $word
+ return
+ fi
+ done
+}
+
_git_branch ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur"))
}
_git_cat_file ()
@@ -126,7 +165,7 @@ _git_cat_file ()
_git_checkout ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur"))
}
_git_diff ()
@@ -137,7 +176,7 @@ _git_diff ()
_git_diff_tree ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur"))
}
_git_fetch ()
@@ -154,8 +193,8 @@ _git_fetch ()
*)
case "$cur" in
*:*)
- cur=$(echo "$cur" | sed 's/^.*://')
- COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ cur="${cur#*:}"
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
;;
*)
local remote
@@ -183,15 +222,20 @@ _git_ls_tree ()
_git_log ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local pfx cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
+ *...*)
+ pfx="${cur%...*}..."
+ cur="${cur#*...}"
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ ;;
*..*)
- local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
- cur=$(echo "$cur" | sed 's/^.*\.\.//')
- COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur"))
+ pfx="${cur%..*}.."
+ cur="${cur#*..}"
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
;;
*)
- COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
;;
esac
}
@@ -199,7 +243,7 @@ _git_log ()
_git_merge_base ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
}
_git_pull ()
@@ -243,54 +287,82 @@ _git_push ()
git-push) remote="${COMP_WORDS[1]}" ;;
git) remote="${COMP_WORDS[2]}" ;;
esac
- cur=$(echo "$cur" | sed 's/^.*://')
+ cur="${cur#*:}"
COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
;;
*)
- COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur"))
;;
esac
;;
esac
}
+_git_reset ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local opt="--mixed --hard --soft"
+ COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur"))
+}
+
_git_show ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
}
_git ()
{
- if [ $COMP_CWORD = 1 ]; then
+ local i c=1 command __git_dir
+
+ while [ $c -lt $COMP_CWORD ]; do
+ i="${COMP_WORDS[c]}"
+ case "$i" in
+ --git-dir=*) __git_dir="${i#--git-dir=}" ;;
+ --bare) __git_dir="." ;;
+ --version|--help|-p|--paginate) ;;
+ *) command="$i"; break ;;
+ esac
+ c=$((++c))
+ done
+
+ if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
COMPREPLY=($(compgen \
- -W "--version $(git help -a|egrep '^ ')" \
+ -W "--git-dir= --version \
+ $(git help -a|egrep '^ ') \
+ $(__git_aliases)" \
-- "${COMP_WORDS[COMP_CWORD]}"))
- else
- case "${COMP_WORDS[1]}" in
- branch) _git_branch ;;
- cat-file) _git_cat_file ;;
- checkout) _git_checkout ;;
- diff) _git_diff ;;
- diff-tree) _git_diff_tree ;;
- fetch) _git_fetch ;;
- log) _git_log ;;
- ls-remote) _git_ls_remote ;;
- ls-tree) _git_ls_tree ;;
- pull) _git_pull ;;
- push) _git_push ;;
- show) _git_show ;;
- show-branch) _git_log ;;
- whatchanged) _git_log ;;
- *) COMPREPLY=() ;;
- esac
+ return;
fi
+
+ local expansion=$(__git_aliased_command "$command")
+ [ "$expansion" ] && command="$expansion"
+
+ case "$command" in
+ branch) _git_branch ;;
+ cat-file) _git_cat_file ;;
+ checkout) _git_checkout ;;
+ diff) _git_diff ;;
+ diff-tree) _git_diff_tree ;;
+ fetch) _git_fetch ;;
+ log) _git_log ;;
+ ls-remote) _git_ls_remote ;;
+ ls-tree) _git_ls_tree ;;
+ merge-base) _git_merge_base ;;
+ pull) _git_pull ;;
+ push) _git_push ;;
+ reset) _git_reset ;;
+ show) _git_show ;;
+ show-branch) _git_log ;;
+ whatchanged) _git_log ;;
+ *) COMPREPLY=() ;;
+ esac
}
_gitk ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur"))
+ COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur"))
}
complete -o default -o nospace -F _git git
@@ -307,13 +379,18 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree
complete -o default -F _git_merge_base git-merge-base
complete -o default -o nospace -F _git_pull git-pull
complete -o default -o nospace -F _git_push git-push
+complete -o default -F _git_reset git-reset
complete -o default -F _git_show git-show
+complete -o default -o nospace -F _git_log git-show-branch
complete -o default -o nospace -F _git_log git-whatchanged
# The following are necessary only for Cygwin, and only are needed
# when the user has tab-completed the executable name and consequently
# included the '.exe' suffix.
#
+if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
+complete -o default -o nospace -F _git git.exe
+complete -o default -F _git_branch git-branch.exe
complete -o default -o nospace -F _git_cat_file git-cat-file.exe
complete -o default -o nospace -F _git_diff git-diff.exe
complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
@@ -321,4 +398,6 @@ complete -o default -o nospace -F _git_log git-log.exe
complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
complete -o default -F _git_merge_base git-merge-base.exe
complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_log git-show-branch.exe
complete -o default -o nospace -F _git_log git-whatchanged.exe
+fi
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 5354cd67b..972c402ea 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -589,6 +589,7 @@ and returns the process output as a string."
(let ((commit (git-commit-tree buffer tree head)))
(git-update-ref "HEAD" commit head)
(condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+ (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
(with-current-buffer buffer (erase-buffer))
(git-set-files-state files 'uptodate)
(when (file-directory-p ".git/rr-cache")
@@ -670,6 +671,32 @@ and returns the process output as a string."
(unless git-status (error "Not in git-status buffer."))
(ewoc-goto-prev git-status n))
+(defun git-next-unmerged-file (&optional n)
+ "Move the selection down N unmerged files."
+ (interactive "p")
+ (unless git-status (error "Not in git-status buffer."))
+ (let* ((last (ewoc-locate git-status))
+ (node (ewoc-next git-status last)))
+ (while (and node (> n 0))
+ (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+ (setq n (1- n))
+ (setq last node))
+ (setq node (ewoc-next git-status node)))
+ (ewoc-goto-node git-status last)))
+
+(defun git-prev-unmerged-file (&optional n)
+ "Move the selection up N unmerged files."
+ (interactive "p")
+ (unless git-status (error "Not in git-status buffer."))
+ (let* ((last (ewoc-locate git-status))
+ (node (ewoc-prev git-status last)))
+ (while (and node (> n 0))
+ (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+ (setq n (1- n))
+ (setq last node))
+ (setq node (ewoc-prev git-status node)))
+ (ewoc-goto-node git-status last)))
+
(defun git-add-file ()
"Add marked file(s) to the index cache."
(interactive)
@@ -862,7 +889,7 @@ and returns the process output as a string."
'face 'git-header-face)
(propertize git-log-msg-separator 'face 'git-separator-face)
"\n")
- (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+ (cond ((file-readable-p ".git/MERGE_MSG")
(insert-file-contents ".git/MERGE_MSG"))
(sign-off
(insert (format "\n\nSigned-off-by: %s <%s>\n"
@@ -873,7 +900,8 @@ and returns the process output as a string."
(2 font-lock-function-name-face))
(,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
(1 font-lock-comment-face)))))
- (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
+ (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+ (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
(defun git-find-file ()
"Visit the current file in its own buffer."
@@ -884,6 +912,15 @@ and returns the process output as a string."
(when (eq 'unmerged (git-fileinfo->state info))
(smerge-mode))))
+(defun git-find-file-other-window ()
+ "Visit the current file in its own buffer in another window."
+ (interactive)
+ (unless git-status (error "Not in git-status buffer."))
+ (let ((info (ewoc-data (ewoc-locate git-status))))
+ (find-file-other-window (git-fileinfo->name info))
+ (when (eq 'unmerged (git-fileinfo->state info))
+ (smerge-mode))))
+
(defun git-find-file-imerge ()
"Visit the current file in interactive merge mode."
(interactive)
@@ -967,7 +1004,10 @@ and returns the process output as a string."
(define-key map "m" 'git-mark-file)
(define-key map "M" 'git-mark-all)
(define-key map "n" 'git-next-file)
+ (define-key map "N" 'git-next-unmerged-file)
+ (define-key map "o" 'git-find-file-other-window)
(define-key map "p" 'git-prev-file)
+ (define-key map "P" 'git-prev-unmerged-file)
(define-key map "q" 'git-status-quit)
(define-key map "r" 'git-remove-file)
(define-key map "R" 'git-resolve-file)
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
index 4189c4ced..8b6361922 100644
--- a/contrib/emacs/vc-git.el
+++ b/contrib/emacs/vc-git.el
@@ -23,13 +23,18 @@
;; system.
;;
;; To install: put this file on the load-path and add GIT to the list
-;; of supported backends in `vc-handled-backends'.
+;; of supported backends in `vc-handled-backends'; the following line,
+;; placed in your ~/.emacs, will accomplish this:
+;;
+;; (add-to-list 'vc-handled-backends 'GIT)
;;
;; TODO
;; - changelog generation
;; - working with revisions other than HEAD
;;
+(eval-when-compile (require 'cl))
+
(defvar git-commits-coding-system 'utf-8
"Default coding system for git commits.")
diff --git a/daemon.c b/daemon.c
index ad8492873..e66bb802d 100644
--- a/daemon.c
+++ b/daemon.c
@@ -450,6 +450,8 @@ void fill_in_extra_table_entries(struct interp *itable)
* Replace literal host with lowercase-ized hostname.
*/
hp = interp_table[INTERP_SLOT_HOST].value;
+ if (!hp)
+ return;
for ( ; *hp; hp++)
*hp = tolower(*hp);
@@ -544,8 +546,10 @@ static int execute(struct sockaddr *addr)
loginfo("Extended attributes (%d bytes) exist <%.*s>",
(int) pktlen - len,
(int) pktlen - len, line + len + 1);
- if (len && line[len-1] == '\n')
+ if (len && line[len-1] == '\n') {
line[--len] = 0;
+ pktlen--;
+ }
/*
* Initialize the path interpolation table for this connection.
diff --git a/describe.c b/describe.c
index ab192f83a..f4029ee74 100644
--- a/describe.c
+++ b/describe.c
@@ -53,7 +53,7 @@ static void add_to_known_names(const char *path,
names = ++idx;
}
-static int get_name(const char *path, const unsigned char *sha1)
+static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
struct object *object;
@@ -113,7 +113,7 @@ static void describe(const char *arg, int last_one)
if (!initialized) {
initialized = 1;
- for_each_ref(get_name);
+ for_each_ref(get_name, NULL);
qsort(name_array, names, sizeof(*name_array), compare_names);
}
diff --git a/diff.h b/diff.h
index 6fda92a7b..101b2b505 100644
--- a/diff.h
+++ b/diff.h
@@ -103,6 +103,8 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
const char *base, struct diff_options *opt);
extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
const char *base, struct diff_options *opt);
+extern int diff_root_tree_sha1(const unsigned char *new, const char *base,
+ struct diff_options *opt);
struct combine_diff_path {
struct combine_diff_path *next;
diff --git a/environment.c b/environment.c
index 63b1d155b..84d870ca4 100644
--- a/environment.c
+++ b/environment.c
@@ -20,7 +20,6 @@ int warn_ambiguous_refs = 1;
int repository_format_version;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
int shared_repository = PERM_UMASK;
-int deny_non_fast_forwards = 0;
const char *apply_default_whitespace;
int zlib_compression_level = Z_DEFAULT_COMPRESSION;
int pager_in_use;
diff --git a/fetch-clone.c b/fetch-clone.c
deleted file mode 100644
index 76b99afcd..000000000
--- a/fetch-clone.c
+++ /dev/null
@@ -1,276 +0,0 @@
-#include "cache.h"
-#include "exec_cmd.h"
-#include "pkt-line.h"
-#include "sideband.h"
-#include <sys/wait.h>
-#include <sys/time.h>
-
-static int finish_pack(const char *pack_tmp_name, const char *me)
-{
- int pipe_fd[2];
- pid_t pid;
- char idx[PATH_MAX];
- char final[PATH_MAX];
- char hash[41];
- unsigned char sha1[20];
- char *cp;
- int err = 0;
-
- if (pipe(pipe_fd) < 0)
- die("%s: unable to set up pipe", me);
-
- strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
- cp = strrchr(idx, '/');
- memcpy(cp, "/pidx", 5);
-
- pid = fork();
- if (pid < 0)
- die("%s: unable to fork off git-index-pack", me);
- if (!pid) {
- close(0);
- dup2(pipe_fd[1], 1);
- close(pipe_fd[0]);
- close(pipe_fd[1]);
- execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL);
- error("cannot exec git-index-pack <%s> <%s>",
- idx, pack_tmp_name);
- exit(1);
- }
- close(pipe_fd[1]);
- if (read(pipe_fd[0], hash, 40) != 40) {
- error("%s: unable to read from git-index-pack", me);
- err = 1;
- }
- close(pipe_fd[0]);
-
- for (;;) {
- int status, code;
-
- if (waitpid(pid, &status, 0) < 0) {
- if (errno == EINTR)
- continue;
- error("waitpid failed (%s)", strerror(errno));
- goto error_die;
- }
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- error("git-index-pack died of signal %d", sig);
- goto error_die;
- }
- if (!WIFEXITED(status)) {
- error("git-index-pack died of unnatural causes %d",
- status);
- goto error_die;
- }
- code = WEXITSTATUS(status);
- if (code) {
- error("git-index-pack died with error code %d", code);
- goto error_die;
- }
- if (err)
- goto error_die;
- break;
- }
- hash[40] = 0;
- if (get_sha1_hex(hash, sha1)) {
- error("git-index-pack reported nonsense '%s'", hash);
- goto error_die;
- }
- /* Now we have pack in pack_tmp_name[], and
- * idx in idx[]; rename them to their final names.
- */
- snprintf(final, sizeof(final),
- "%s/pack/pack-%s.pack", get_object_directory(), hash);
- move_temp_to_file(pack_tmp_name, final);
- chmod(final, 0444);
- snprintf(final, sizeof(final),
- "%s/pack/pack-%s.idx", get_object_directory(), hash);
- move_temp_to_file(idx, final);
- chmod(final, 0444);
- return 0;
-
- error_die:
- unlink(idx);
- unlink(pack_tmp_name);
- exit(1);
-}
-
-static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2])
-{
- pid_t side_pid;
-
- if (!sideband) {
- fd[0] = xd[0];
- fd[1] = xd[1];
- return 0;
- }
- /* xd[] is talking with upload-pack; subprocess reads from
- * xd[0], spits out band#2 to stderr, and feeds us band#1
- * through our fd[0].
- */
- if (pipe(fd) < 0)
- die("%s: unable to set up pipe", me);
- side_pid = fork();
- if (side_pid < 0)
- die("%s: unable to fork off sideband demultiplexer", me);
- if (!side_pid) {
- /* subprocess */
- close(fd[0]);
- if (xd[0] != xd[1])
- close(xd[1]);
- if (recv_sideband(me, xd[0], fd[1], 2))
- exit(1);
- exit(0);
- }
- close(xd[0]);
- close(fd[1]);
- fd[1] = xd[1];
- return side_pid;
-}
-
-int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband)
-{
- int status;
- pid_t pid, side_pid;
- int fd[2];
-
- side_pid = setup_sideband(sideband, me, fd, xd);
- pid = fork();
- if (pid < 0)
- die("%s: unable to fork off git-unpack-objects", me);
- if (!pid) {
- dup2(fd[0], 0);
- close(fd[0]);
- close(fd[1]);
- execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL);
- die("git-unpack-objects exec failed");
- }
- close(fd[0]);
- close(fd[1]);
- while (waitpid(pid, &status, 0) < 0) {
- if (errno != EINTR)
- die("waiting for git-unpack-objects: %s",
- strerror(errno));
- }
- if (WIFEXITED(status)) {
- int code = WEXITSTATUS(status);
- if (code)
- die("git-unpack-objects died with error code %d",
- code);
- return 0;
- }
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- die("git-unpack-objects died of signal %d", sig);
- }
- die("git-unpack-objects died of unnatural causes %d", status);
-}
-
-/*
- * We average out the download speed over this many "events", where
- * an event is a minimum of about half a second. That way, we get
- * a reasonably stable number.
- */
-#define NR_AVERAGE (4)
-
-/*
- * A "binary msec" is a power-of-two-msec, aka 1/1024th of a second.
- * Keeping the time in that format means that "bytes / msecs" means
- * the same as kB/s (modulo rounding).
- *
- * 1000512 is a magic number (usecs in a second, rounded up by half
- * of 1024, to make "rounding" come out right ;)
- */
-#define usec_to_binarymsec(x) ((int)(x) / (1000512 >> 10))
-
-int receive_keep_pack(int xd[2], const char *me, int quiet, int sideband)
-{
- char tmpfile[PATH_MAX];
- int ofd, ifd, fd[2];
- unsigned long total;
- static struct timeval prev_tv;
- struct average {
- unsigned long bytes;
- unsigned long time;
- } download[NR_AVERAGE] = { {0, 0}, };
- unsigned long avg_bytes, avg_time;
- int idx = 0;
-
- setup_sideband(sideband, me, fd, xd);
-
- ifd = fd[0];
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/pack/tmp-XXXXXX", get_object_directory());
- ofd = mkstemp(tmpfile);
- if (ofd < 0)
- return error("unable to create temporary file %s", tmpfile);
-
- gettimeofday(&prev_tv, NULL);
- total = 0;
- avg_bytes = 0;
- avg_time = 0;
- while (1) {
- char buf[8192];
- ssize_t sz, wsz, pos;
- sz = read(ifd, buf, sizeof(buf));
- if (sz == 0)
- break;
- if (sz < 0) {
- if (errno != EINTR && errno != EAGAIN) {
- error("error reading pack (%s)", strerror(errno));
- close(ofd);
- unlink(tmpfile);
- return -1;
- }
- sz = 0;
- }
- pos = 0;
- while (pos < sz) {
- wsz = write(ofd, buf + pos, sz - pos);
- if (wsz < 0) {
- error("error writing pack (%s)",
- strerror(errno));
- close(ofd);
- unlink(tmpfile);
- return -1;
- }
- pos += wsz;
- }
- total += sz;
- if (!quiet) {
- static unsigned long last;
- struct timeval tv;
- unsigned long diff = total - last;
- /* not really "msecs", but a power-of-two millisec (1/1024th of a sec) */
- unsigned long msecs;
-
- gettimeofday(&tv, NULL);
- msecs = tv.tv_sec - prev_tv.tv_sec;
- msecs <<= 10;
- msecs += usec_to_binarymsec(tv.tv_usec - prev_tv.tv_usec);
-
- if (msecs > 500) {
- prev_tv = tv;
- last = total;
-
- /* Update averages ..*/
- avg_bytes += diff;
- avg_time += msecs;
- avg_bytes -= download[idx].bytes;
- avg_time -= download[idx].time;
- download[idx].bytes = diff;
- download[idx].time = msecs;
- idx++;
- if (idx >= NR_AVERAGE)
- idx = 0;
-
- fprintf(stderr, "%4lu.%03luMB (%lu kB/s) \r",
- total >> 20,
- 1000*((total >> 10) & 1023)>>10,
- avg_bytes / avg_time );
- }
- }
- }
- close(ofd);
- return finish_pack(tmpfile, me);
-}
diff --git a/fetch-pack.c b/fetch-pack.c
index e8708aa80..0a169dce8 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -3,6 +3,9 @@
#include "pkt-line.h"
#include "commit.h"
#include "tag.h"
+#include "exec_cmd.h"
+#include "sideband.h"
+#include <sys/wait.h>
static int keep_pack;
static int quiet;
@@ -42,7 +45,7 @@ static void rev_list_push(struct commit *commit, int mark)
}
}
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = deref_tag(parse_object(sha1), path, 0);
@@ -143,7 +146,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
unsigned in_vain = 0;
int got_continue = 0;
- for_each_ref(rev_list_insert_ref);
+ for_each_ref(rev_list_insert_ref, NULL);
fetching = 0;
for ( ; refs ; refs = refs->next) {
@@ -166,12 +169,13 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
if (!fetching)
- packet_write(fd[1], "want %s%s%s%s%s\n",
+ packet_write(fd[1], "want %s%s%s%s%s%s\n",
sha1_to_hex(remote),
(multi_ack ? " multi_ack" : ""),
(use_sideband == 2 ? " side-band-64k" : ""),
(use_sideband == 1 ? " side-band" : ""),
- (use_thin_pack ? " thin-pack" : ""));
+ (use_thin_pack ? " thin-pack" : ""),
+ " ofs-delta");
else
packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
fetching++;
@@ -253,7 +257,7 @@ done:
static struct commit_list *complete;
-static int mark_complete(const char *path, const unsigned char *sha1)
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = parse_object(sha1);
@@ -365,7 +369,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
}
}
- for_each_ref(mark_complete);
+ for_each_ref(mark_complete, NULL);
if (cutoff)
mark_recent_complete_commits(cutoff);
@@ -415,6 +419,103 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
return retval;
}
+static pid_t setup_sideband(int fd[2], int xd[2])
+{
+ pid_t side_pid;
+
+ if (!use_sideband) {
+ fd[0] = xd[0];
+ fd[1] = xd[1];
+ return 0;
+ }
+ /* xd[] is talking with upload-pack; subprocess reads from
+ * xd[0], spits out band#2 to stderr, and feeds us band#1
+ * through our fd[0].
+ */
+ if (pipe(fd) < 0)
+ die("fetch-pack: unable to set up pipe");
+ side_pid = fork();
+ if (side_pid < 0)
+ die("fetch-pack: unable to fork off sideband demultiplexer");
+ if (!side_pid) {
+ /* subprocess */
+ close(fd[0]);
+ if (xd[0] != xd[1])
+ close(xd[1]);
+ if (recv_sideband("fetch-pack", xd[0], fd[1], 2))
+ exit(1);
+ exit(0);
+ }
+ close(xd[0]);
+ close(fd[1]);
+ fd[1] = xd[1];
+ return side_pid;
+}
+
+static int get_pack(int xd[2], const char **argv)
+{
+ int status;
+ pid_t pid, side_pid;
+ int fd[2];
+
+ side_pid = setup_sideband(fd, xd);
+ pid = fork();
+ if (pid < 0)
+ die("fetch-pack: unable to fork off %s", argv[0]);
+ if (!pid) {
+ dup2(fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+ execv_git_cmd(argv);
+ die("%s exec failed", argv[0]);
+ }
+ close(fd[0]);
+ close(fd[1]);
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno != EINTR)
+ die("waiting for %s: %s", argv[0], strerror(errno));
+ }
+ if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
+ if (code)
+ die("%s died with error code %d", argv[0], code);
+ return 0;
+ }
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ die("%s died of signal %d", argv[0], sig);
+ }
+ die("%s died of unnatural causes %d", argv[0], status);
+}
+
+static int explode_rx_pack(int xd[2])
+{
+ const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL };
+ return get_pack(xd, argv);
+}
+
+static int keep_rx_pack(int xd[2])
+{
+ const char *argv[6];
+ char keep_arg[256];
+ int n = 0;
+
+ argv[n++] = "index-pack";
+ argv[n++] = "--stdin";
+ if (!quiet)
+ argv[n++] = "-v";
+ if (use_thin_pack)
+ argv[n++] = "--fix-thin";
+ if (keep_pack > 1) {
+ int s = sprintf(keep_arg, "--keep=fetch-pack %i on ", getpid());
+ if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+ strcpy(keep_arg + s, "localhost");
+ argv[n++] = keep_arg;
+ }
+ argv[n] = NULL;
+ return get_pack(xd, argv);
+}
+
static int fetch_pack(int fd[2], int nr_match, char **match)
{
struct ref *ref;
@@ -446,17 +547,13 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
goto all_done;
}
if (find_common(fd, sha1, ref) < 0)
- if (!keep_pack)
+ if (keep_pack != 1)
/* When cloning, it is not unusual to have
* no common commit.
*/
fprintf(stderr, "warning: no common commits\n");
- if (keep_pack)
- status = receive_keep_pack(fd, "git-fetch-pack", quiet, use_sideband);
- else
- status = receive_unpack_pack(fd, "git-fetch-pack", quiet, use_sideband);
-
+ status = (keep_pack) ? keep_rx_pack(fd) : explode_rx_pack(fd);
if (status)
die("git-fetch-pack: fetch failed.");
@@ -493,7 +590,7 @@ int main(int argc, char **argv)
continue;
}
if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
- keep_pack = 1;
+ keep_pack++;
continue;
}
if (!strcmp("--thin", arg)) {
@@ -517,8 +614,6 @@ int main(int argc, char **argv)
}
if (!dest)
usage(fetch_pack_usage);
- if (keep_pack)
- use_thin_pack = 0;
pid = git_connect(fd, dest, exec);
if (pid < 0)
return 1;
diff --git a/fetch.c b/fetch.c
index 34df8d37d..c426c0499 100644
--- a/fetch.c
+++ b/fetch.c
@@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1)
return -1;
}
-static int mark_complete(const char *path, const unsigned char *sha1)
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
if (commit) {
@@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref,
if (!write_ref || !write_ref[i])
continue;
- lock[i] = lock_ref_sha1(write_ref[i], NULL, 0);
+ lock[i] = lock_ref_sha1(write_ref[i], NULL);
if (!lock[i]) {
error("Can't lock ref %s", write_ref[i]);
goto unlock_and_fail;
@@ -274,7 +274,7 @@ int pull(int targets, char **target, const char **write_ref,
}
if (!get_recover)
- for_each_ref(mark_complete);
+ for_each_ref(mark_complete, NULL);
for (i = 0; i < targets; i++) {
if (interpret_target(target[i], &sha1[20 * i])) {
diff --git a/fsck-objects.c b/fsck-objects.c
index 4d994f3fc..46b628cb9 100644
--- a/fsck-objects.c
+++ b/fsck-objects.c
@@ -402,7 +402,7 @@ static void fsck_dir(int i, char *path)
static int default_refs;
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *obj;
@@ -424,7 +424,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
static void get_default_heads(void)
{
- for_each_ref(fsck_handle_ref);
+ for_each_ref(fsck_handle_ref, NULL);
/*
* Not having any default heads isn't really fatal, but
@@ -458,15 +458,14 @@ static void fsck_object_dir(const char *path)
static int fsck_head_link(void)
{
unsigned char sha1[20];
- const char *git_HEAD = xstrdup(git_path("HEAD"));
- const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
- int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+ int flag;
+ const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag);
- if (!git_refs_heads_master)
+ if (!head_points_at || !(flag & REF_ISSYMREF))
return error("HEAD is not a symbolic ref");
- if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+ if (strncmp(head_points_at, "refs/heads/", 11))
return error("HEAD points to something strange (%s)",
- git_refs_heads_master + pfxlen);
+ head_points_at);
if (is_null_sha1(sha1))
return error("HEAD: not a valid git pointer");
return 0;
diff --git a/git-annotate.perl b/git-annotate.perl
deleted file mode 100755
index 215ed26f3..000000000
--- a/git-annotate.perl
+++ /dev/null
@@ -1,708 +0,0 @@
-#!/usr/bin/perl
-# Copyright 2006, Ryan Anderson <ryan@michonline.com>
-#
-# GPL v2 (See COPYING)
-#
-# This file is licensed under the GPL v2, or a later version
-# at the discretion of Linus Torvalds.
-
-use warnings;
-use strict;
-use Getopt::Long;
-use POSIX qw(strftime gmtime);
-use File::Basename qw(basename dirname);
-
-sub usage() {
- print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
- -l, --long
- Show long rev (Defaults off)
- -t, --time
- Show raw timestamp (Defaults off)
- -r, --rename
- Follow renames (Defaults on).
- -S, --rev-file revs-file
- Use revs from revs-file instead of calling git-rev-list
- -h, --help
- This message.
-";
-
- exit(1);
-}
-
-our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
-
-my $rc = GetOptions( "long|l" => \$longrev,
- "time|t" => \$rawtime,
- "help|h" => \$help,
- "rename|r" => \$rename,
- "rev-file|S=s" => \$rev_file);
-if (!$rc or $help or !@ARGV) {
- usage();
-}
-
-my $filename = shift @ARGV;
-if (@ARGV) {
- $starting_rev = shift @ARGV;
-}
-
-my @stack = (
- {
- 'rev' => defined $starting_rev ? $starting_rev : "HEAD",
- 'filename' => $filename,
- },
-);
-
-our @filelines = ();
-
-if (defined $starting_rev) {
- @filelines = git_cat_file($starting_rev, $filename);
-} else {
- open(F,"<",$filename)
- or die "Failed to open filename: $!";
-
- while(<F>) {
- chomp;
- push @filelines, $_;
- }
- close(F);
-
-}
-
-our %revs;
-our @revqueue;
-our $head;
-
-my $revsprocessed = 0;
-while (my $bound = pop @stack) {
- my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});
- foreach my $revinst (@revisions) {
- my ($rev, @parents) = @$revinst;
- $head ||= $rev;
-
- if (!defined($rev)) {
- $rev = "";
- }
- $revs{$rev}{'filename'} = $bound->{'filename'};
- if (scalar @parents > 0) {
- $revs{$rev}{'parents'} = \@parents;
- next;
- }
-
- if (!$rename) {
- next;
- }
-
- my $newbound = find_parent_renames($rev, $bound->{'filename'});
- if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {
- push @stack, $newbound;
- $revs{$rev}{'parents'} = [$newbound->{'rev'}];
- }
- }
-}
-push @revqueue, $head;
-init_claim( defined $starting_rev ? $head : 'dirty');
-unless (defined $starting_rev) {
- my $diff = open_pipe("git","diff","HEAD", "--",$filename)
- or die "Failed to call git diff to check for dirty state: $!";
-
- _git_diff_parse($diff, [$head], "dirty", (
- 'author' => gitvar_name("GIT_AUTHOR_IDENT"),
- 'author_date' => sprintf("%s +0000",time()),
- )
- );
- close($diff);
-}
-handle_rev();
-
-
-my $i = 0;
-foreach my $l (@filelines) {
- my ($output, $rev, $committer, $date);
- if (ref $l eq 'ARRAY') {
- ($output, $rev, $committer, $date) = @$l;
- if (!$longrev && length($rev) > 8) {
- $rev = substr($rev,0,8);
- }
- } else {
- $output = $l;
- ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown');
- }
-
- printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
- format_date($date), ++$i, $output);
-}
-
-sub init_claim {
- my ($rev) = @_;
- for (my $i = 0; $i < @filelines; $i++) {
- $filelines[$i] = [ $filelines[$i], '', '', '', 1];
- # line,
- # rev,
- # author,
- # date,
- # 1 <-- belongs to the original file.
- }
- $revs{$rev}{'lines'} = \@filelines;
-}
-
-
-sub handle_rev {
- my $revseen = 0;
- my %seen;
- while (my $rev = shift @revqueue) {
- next if $seen{$rev}++;
-
- my %revinfo = git_commit_info($rev);
-
- if (exists $revs{$rev}{parents} &&
- scalar @{$revs{$rev}{parents}} != 0) {
-
- git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo);
- push @revqueue, @{$revs{$rev}{'parents'}};
-
- } else {
- # We must be at the initial rev here, so claim everything that is left.
- for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
- if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
- claim_line($i, $rev, $revs{$rev}{lines}, %revinfo);
- }
- }
- }
- }
-}
-
-
-sub git_rev_list {
- my ($rev, $file) = @_;
-
- my $revlist;
- if ($rev_file) {
- open($revlist, '<' . $rev_file)
- or die "Failed to open $rev_file : $!";
- } else {
- $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
- or die "Failed to exec git-rev-list: $!";
- }
-
- my @revs;
- while(my $line = <$revlist>) {
- chomp $line;
- my ($rev, @parents) = split /\s+/, $line;
- push @revs, [ $rev, @parents ];
- }
- close($revlist);
-
- printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0);
- return @revs;
-}
-
-sub find_parent_renames {
- my ($rev, $file) = @_;
-
- my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev")
- or die "Failed to exec git-diff: $!";
-
- local $/ = "\0";
- my %bound;
- my $junk = <$patch>;
- while (my $change = <$patch>) {
- chomp $change;
- my $filename = <$patch>;
- if (!defined $filename) {
- next;
- }
- chomp $filename;
-
- if ($change =~ m/^[AMD]$/ ) {
- next;
- } elsif ($change =~ m/^R/ ) {
- my $oldfilename = $filename;
- $filename = <$patch>;
- chomp $filename;
- if ( $file eq $filename ) {
- my $parent = git_find_parent($rev, $oldfilename);
- @bound{'rev','filename'} = ($parent, $oldfilename);
- last;
- }
- }
- }
- close($patch);
-
- return \%bound;
-}
-
-
-sub git_find_parent {
- my ($rev, $filename) = @_;
-
- my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename)
- or die "Failed to open git-rev-list to find a single parent: $!";
-
- my $parentline = <$revparent>;
- chomp $parentline;
- my ($revfound,$parent) = split m/\s+/, $parentline;
-
- close($revparent);
-
- return $parent;
-}
-
-sub git_find_all_parents {
- my ($rev) = @_;
-
- my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev")
- or die "Failed to open git-rev-list to find a single parent: $!";
-
- my $parentline = <$revparent>;
- chomp $parentline;
- my ($origrev, @parents) = split m/\s+/, $parentline;
-
- close($revparent);
-
- return @parents;
-}
-
-sub git_merge_base {
- my ($rev1, $rev2) = @_;
-
- my $mb = open_pipe("git-merge-base", $rev1, $rev2)
- or die "Failed to open git-merge-base: $!";
-
- my $base = <$mb>;
- chomp $base;
-
- close($mb);
-
- return $base;
-}
-
-# Construct a set of pseudo parents that are in the same order,
-# and the same quantity as the real parents,
-# but whose SHA1s are as similar to the logical parents
-# as possible.
-sub get_pseudo_parents {
- my ($all, $fake) = @_;
-
- my @all = @$all;
- my @fake = @$fake;
-
- my @pseudo;
-
- my %fake = map {$_ => 1} @fake;
- my %seenfake;
-
- my $fakeidx = 0;
- foreach my $p (@all) {
- if (exists $fake{$p}) {
- if ($fake[$fakeidx] ne $p) {
- die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n",
- $fake[$fakeidx], $p,
- join(", ", @all),
- join(", ", @fake),
- );
- }
-
- push @pseudo, $p;
- $fakeidx++;
- $seenfake{$p}++;
-
- } else {
- my $base = git_merge_base($fake[$fakeidx], $p);
- if ($base ne $fake[$fakeidx]) {
- die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n",
- $fake[$fakeidx], $p, $base);
- }
-
- # The details of how we parse the diffs
- # mean that we cannot have a duplicate
- # revision in the list, so if we've already
- # seen the revision we would normally add, just use
- # the actual revision.
- if ($seenfake{$base}) {
- push @pseudo, $p;
- } else {
- push @pseudo, $base;
- $seenfake{$base}++;
- }
- }
- }
-
- return @pseudo;
-}
-
-
-# Get a diff between the current revision and a parent.
-# Record the commit information that results.
-sub git_diff_parse {
- my ($parents, $rev, %revinfo) = @_;
-
- my @pseudo_parents;
- my @command = ("git-diff-tree");
- my $revision_spec;
-
- if (scalar @$parents == 1) {
-
- $revision_spec = join("..", $parents->[0], $rev);
- @pseudo_parents = @$parents;
- } else {
- my @all_parents = git_find_all_parents($rev);
-
- if (@all_parents != @$parents) {
- @pseudo_parents = get_pseudo_parents(\@all_parents, $parents);
- } else {
- @pseudo_parents = @$parents;
- }
-
- $revision_spec = $rev;
- push @command, "-c";
- }
-
- my @filenames = ( $revs{$rev}{'filename'} );
-
- foreach my $parent (@$parents) {
- push @filenames, $revs{$parent}{'filename'};
- }
-
- push @command, "-p", "-M", $revision_spec, "--", @filenames;
-
-
- my $diff = open_pipe( @command )
- or die "Failed to call git-diff for annotation: $!";
-
- _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo);
-
- close($diff);
-}
-
-sub _git_diff_parse {
- my ($diff, $parents, $rev, %revinfo) = @_;
-
- my $ri = 0;
-
- my $slines = $revs{$rev}{'lines'};
- my (%plines, %pi);
-
- my $gotheader = 0;
- my ($remstart);
- my $parent_count = @$parents;
-
- my $diff_header_regexp = "^@";
- $diff_header_regexp .= "@" x @$parents;
- $diff_header_regexp .= ' -\d+,\d+' x @$parents;
- $diff_header_regexp .= ' \+(\d+),\d+';
- $diff_header_regexp .= " " . ("@" x @$parents);
-
- my %claim_regexps;
- my $allparentplus = '^' . '\\+' x @$parents . '(.*)$';
-
- {
- my $i = 0;
- foreach my $parent (@$parents) {
-
- $pi{$parent} = 0;
- my $r = '^' . '.' x @$parents . '(.*)$';
- my $p = $r;
- substr($p,$i+1, 1) = '\\+';
-
- my $m = $r;
- substr($m,$i+1, 1) = '-';
-
- $claim_regexps{$parent}{plus} = $p;
- $claim_regexps{$parent}{minus} = $m;
-
- $plines{$parent} = [];
-
- $i++;
- }
- }
-
- DIFF:
- while(<$diff>) {
- chomp;
- #printf("%d:%s:\n", $gotheader, $_);
- if (m/$diff_header_regexp/) {
- $remstart = $1 - 1;
- # (0-based arrays)
-
- $gotheader = 1;
-
- foreach my $parent (@$parents) {
- for (my $i = $ri; $i < $remstart; $i++) {
- $plines{$parent}[$pi{$parent}++] = $slines->[$i];
- }
- }
- $ri = $remstart;
-
- next DIFF;
-
- } elsif (!$gotheader) {
- # Skip over the leadin.
- next DIFF;
- }
-
- if (m/^\\/) {
- ;
- # Skip \No newline at end of file.
- # But this can be internationalized, so only look
- # for an initial \
-
- } else {
- my %claims = ();
- my $negclaim = 0;
- my $allclaimed = 0;
- my $line;
-
- if (m/$allparentplus/) {
- claim_line($ri, $rev, $slines, %revinfo);
- $allclaimed = 1;
-
- }
-
- PARENT:
- foreach my $parent (keys %claim_regexps) {
- my $m = $claim_regexps{$parent}{minus};
- my $p = $claim_regexps{$parent}{plus};
-
- if (m/$m/) {
- $line = $1;
- $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ];
- $negclaim++;
-
- } elsif (m/$p/) {
- $line = $1;
- if (get_line($slines, $ri) eq $line) {
- # Found a match, claim
- $claims{$parent}++;
-
- } else {
- die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n",
- $ri, $line,
- get_line($slines, $ri),
- $rev, $parent);
- }
- }
- }
-
- if (%claims) {
- foreach my $parent (@$parents) {
- next if $claims{$parent} || $allclaimed;
- $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
- #[ $line, '', '', '', 0 ];
- }
- $ri++;
-
- } elsif ($negclaim) {
- next DIFF;
-
- } else {
- if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) {
- foreach my $parent (@$parents) {
- printf("parent %s is on line %d\n", $parent, $pi{$parent});
- }
-
- my @context;
- for (my $i = -2; $i < 2; $i++) {
- push @context, get_line($slines, $ri + $i);
- }
- my $context = join("\n", @context);
-
- my $justline = substr($_, scalar @$parents);
- die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n",
- $ri,
- $justline,
- $context);
- }
- foreach my $parent (@$parents) {
- $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
- }
- $ri++;
- }
- }
- }
-
- for (my $i = $ri; $i < @{$slines} ; $i++) {
- foreach my $parent (@$parents) {
- push @{$plines{$parent}}, $slines->[$ri];
- }
- $ri++;
- }
-
- foreach my $parent (@$parents) {
- $revs{$parent}{lines} = $plines{$parent};
- }
-
- return;
-}
-
-sub get_line {
- my ($lines, $index) = @_;
-
- return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index];
-}
-
-sub git_cat_file {
- my ($rev, $filename) = @_;
- return () unless defined $rev && defined $filename;
-
- my $blob = git_ls_tree($rev, $filename);
- die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob;
-
- my $catfile = open_pipe("git","cat-file", "blob", $blob)
- or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
-
- my @lines;
- while(<$catfile>) {
- chomp;
- push @lines, $_;
- }
- close($catfile);
-
- return @lines;
-}
-
-sub git_ls_tree {
- my ($rev, $filename) = @_;
-
- my $lstree = open_pipe("git","ls-tree",$rev,$filename)
- or die "Failed to call git ls-tree: $!";
-
- my ($mode, $type, $blob, $tfilename);
- while(<$lstree>) {
- chomp;
- ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
- last if ($tfilename eq $filename);
- }
- close($lstree);
-
- return $blob if ($tfilename eq $filename);
- die "git-ls-tree failed to find blob for $filename";
-
-}
-
-
-
-sub claim_line {
- my ($floffset, $rev, $lines, %revinfo) = @_;
- my $oline = get_line($lines, $floffset);
- @{$lines->[$floffset]} = ( $oline, $rev,
- $revinfo{'author'}, $revinfo{'author_date'} );
- #printf("Claiming line %d with rev %s: '%s'\n",
- # $floffset, $rev, $oline) if 1;
-}
-
-sub git_commit_info {
- my ($rev) = @_;
- my $commit = open_pipe("git-cat-file", "commit", $rev)
- or die "Failed to call git-cat-file: $!";
-
- my %info;
- while(<$commit>) {
- chomp;
- last if (length $_ == 0);
-
- if (m/^author (.*) <(.*)> (.*)$/) {
- $info{'author'} = $1;
- $info{'author_email'} = $2;
- $info{'author_date'} = $3;
- } elsif (m/^committer (.*) <(.*)> (.*)$/) {
- $info{'committer'} = $1;
- $info{'committer_email'} = $2;
- $info{'committer_date'} = $3;
- }
- }
- close($commit);
-
- return %info;
-}
-
-sub format_date {
- if ($rawtime) {
- return $_[0];
- }
- my ($timestamp, $timezone) = split(' ', $_[0]);
- my $minutes = abs($timezone);
- $minutes = int($minutes / 100) * 60 + ($minutes % 100);
- if ($timezone < 0) {
- $minutes = -$minutes;
- }
- my $t = $timestamp + $minutes * 60;
- return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t));
-}
-
-# Copied from git-send-email.perl - We need a Git.pm module..
-sub gitvar {
- my ($var) = @_;
- my $fh;
- my $pid = open($fh, '-|');
- die "$!" unless defined $pid;
- if (!$pid) {
- exec('git-var', $var) or die "$!";
- }
- my ($val) = <$fh>;
- close $fh or die "$!";
- chomp($val);
- return $val;
-}
-
-sub gitvar_name {
- my ($name) = @_;
- my $val = gitvar($name);
- my @field = split(/\s+/, $val);
- return join(' ', @field[0...(@field-4)]);
-}
-
-sub open_pipe {
- if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
- return open_pipe_activestate(@_);
- } else {
- return open_pipe_normal(@_);
- }
-}
-
-sub open_pipe_activestate {
- tie *fh, "Git::ActiveStatePipe", @_;
- return *fh;
-}
-
-sub open_pipe_normal {
- my (@execlist) = @_;
-
- my $pid = open my $kid, "-|";
- defined $pid or die "Cannot fork: $!";
-
- unless ($pid) {
- exec @execlist;
- die "Cannot exec @execlist: $!";
- }
-
- return $kid;
-}
-
-package Git::ActiveStatePipe;
-use strict;
-
-sub TIEHANDLE {
- my ($class, @params) = @_;
- my $cmdline = join " ", @params;
- my @data = qx{$cmdline};
- bless { i => 0, data => \@data }, $class;
-}
-
-sub READLINE {
- my $self = shift;
- if ($self->{i} >= scalar @{$self->{data}}) {
- return undef;
- }
- return $self->{'data'}->[ $self->{i}++ ];
-}
-
-sub CLOSE {
- my $self = shift;
- delete $self->{data};
- delete $self->{i};
-}
-
-sub EOF {
- my $self = shift;
- return ($self->{i} >= scalar @{$self->{data}});
-}
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755
index 4f31903d6..000000000
--- a/git-branch.sh
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/bin/sh
-
-USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
-If one argument, create a new branch <branchname> based off of current HEAD.
-If two arguments, create a new branch <branchname> based off of <start-point>.'
-
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-
-delete_branch () {
- option="$1"
- shift
- for branch_name
- do
- case ",$headref," in
- ",$branch_name,")
- die "Cannot delete the branch you are on." ;;
- ,,)
- die "What branch are you on anyway?" ;;
- esac
- branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
- branch=$(git-rev-parse --verify "$branch^0") ||
- die "Seriously, what branch are you talking about?"
- case "$option" in
- -D)
- ;;
- *)
- mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
- case " $mbs " in
- *' '$branch' '*)
- # the merge base of branch and HEAD contains branch --
- # which means that the HEAD contains everything in both.
- ;;
- *)
- echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
- exit 1
- ;;
- esac
- ;;
- esac
- rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
- rm -f "$GIT_DIR/refs/heads/$branch_name"
- echo "Deleted branch $branch_name."
- done
- exit 0
-}
-
-ls_remote_branches () {
- git-rev-parse --symbolic --all |
- sed -ne 's|^refs/\(remotes/\)|\1|p' |
- sort
-}
-
-force=
-create_log=
-while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
-do
- case "$1" in
- -d | -D)
- delete_branch "$@"
- exit
- ;;
- -r)
- ls_remote_branches
- exit
- ;;
- -f)
- force="$1"
- ;;
- -l)
- create_log="yes"
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- esac
- shift
-done
-
-case "$#" in
-0)
- git-rev-parse --symbolic --branches |
- sort |
- while read ref
- do
- if test "$headref" = "$ref"
- then
- pfx='*'
- else
- pfx=' '
- fi
- echo "$pfx $ref"
- done
- exit 0 ;;
-1)
- head=HEAD ;;
-2)
- head="$2^0" ;;
-esac
-branchname="$1"
-
-rev=$(git-rev-parse --verify "$head") || exit
-
-git-check-ref-format "heads/$branchname" ||
- die "we do not like '$branchname' as a branch name."
-
-if [ -d "$GIT_DIR/refs/heads/$branchname" ]
-then
- for refdir in `cd "$GIT_DIR" && \
- find "refs/heads/$branchname" -type d | sort -r`
- do
- rmdir "$GIT_DIR/$refdir" || \
- die "Could not delete '$refdir', there may still be a ref there."
- done
-fi
-
-if [ -e "$GIT_DIR/refs/heads/$branchname" ]
-then
- if test '' = "$force"
- then
- die "$branchname already exists."
- elif test "$branchname" = "$headref"
- then
- die "cannot force-update the current branch."
- fi
-fi
-if test "$create_log" = 'yes'
-then
- mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
- touch "$GIT_DIR/logs/refs/heads/$branchname"
-fi
-git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
diff --git a/git-checkout.sh b/git-checkout.sh
index dd477245f..119bca1ff 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do
shift
[ -z "$newbranch" ] &&
die "git checkout: -b needs a branch name"
- [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+ git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
die "git checkout: branch $newbranch already exists"
git-check-ref-format "heads/$newbranch" ||
die "git checkout: we do not like '$newbranch' as a branch name."
@@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do
fi
new="$rev"
new_name="$arg^0"
- if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+ if git-show-ref --verify --quiet -- "refs/heads/$arg"
+ then
branch="$arg"
fi
elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
diff --git a/git-cherry.sh b/git-cherry.sh
deleted file mode 100755
index 8832573fe..000000000
--- a/git-cherry.sh
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano.
-#
-
-USAGE='[-v] <upstream> [<head>] [<limit>]'
-LONG_USAGE=' __*__*__*__*__> <upstream>
- /
- fork-point
- \__+__+__+__+__+__+__+__> <head>
-
-Each commit between the fork-point (or <limit> if given) and <head> is
-examined, and compared against the change each commit between the
-fork-point and <upstream> introduces. If the change seems to be in
-the upstream, it is shown on the standard output with prefix "+".
-Otherwise it is shown with prefix "-".'
-. git-sh-setup
-
-case "$1" in -v) verbose=t; shift ;; esac
-
-case "$#,$1" in
-1,*..*)
- upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$')
- set x "$upstream" "$ours"
- shift ;;
-esac
-
-case "$#" in
-1) upstream=`git-rev-parse --verify "$1"` &&
- ours=`git-rev-parse --verify HEAD` || exit
- limit="$upstream"
- ;;
-2) upstream=`git-rev-parse --verify "$1"` &&
- ours=`git-rev-parse --verify "$2"` || exit
- limit="$upstream"
- ;;
-3) upstream=`git-rev-parse --verify "$1"` &&
- ours=`git-rev-parse --verify "$2"` &&
- limit=`git-rev-parse --verify "$3"` || exit
- ;;
-*) usage ;;
-esac
-
-# Note that these list commits in reverse order;
-# not that the order in inup matters...
-inup=`git-rev-list ^$ours $upstream` &&
-ours=`git-rev-list $ours ^$limit` || exit
-
-tmp=.cherry-tmp$$
-patch=$tmp-patch
-mkdir $patch
-trap "rm -rf $tmp-*" 0 1 2 3 15
-
-for c in $inup
-do
- git-diff-tree -p $c
-done | git-patch-id |
-while read id name
-do
- echo $name >>$patch/$id
-done
-
-LF='
-'
-
-O=
-for c in $ours
-do
- set x `git-diff-tree -p $c | git-patch-id`
- if test "$2" != ""
- then
- if test -f "$patch/$2"
- then
- sign=-
- else
- sign=+
- fi
- case "$verbose" in
- t)
- c=$(git-rev-list --pretty=oneline --max-count=1 $c)
- esac
- case "$O" in
- '') O="$sign $c" ;;
- *) O="$sign $c$LF$O" ;;
- esac
- fi
-done
-case "$O" in
-'') ;;
-*) echo "$O" ;;
-esac
diff --git a/git-clone.sh b/git-clone.sh
index bf54a1150..3f006d1a7 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -8,11 +8,15 @@
# See git-sh-setup why.
unset CDPATH
-usage() {
- echo >&2 "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+die() {
+ echo >&2 "$@"
exit 1
}
+usage() {
+ die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+}
+
get_repo_base() {
(cd "$1" && (cd .git ; pwd)) 2> /dev/null
}
@@ -35,11 +39,9 @@ clone_dumb_http () {
"`git-repo-config --bool http.noEPSV`" = true ]; then
curl_extra_args="${curl_extra_args} --disable-epsv"
fi
- http_fetch "$1/info/refs" "$clone_tmp/refs" || {
- echo >&2 "Cannot get remote repository information.
+ http_fetch "$1/info/refs" "$clone_tmp/refs" ||
+ die "Cannot get remote repository information.
Perhaps git-update-server-info needs to be run there?"
- exit 1;
- }
while read sha1 refname
do
name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
@@ -143,17 +145,12 @@ while
'')
usage ;;
*/*)
- echo >&2 "'$2' is not suitable for an origin name"
- exit 1
+ die "'$2' is not suitable for an origin name"
esac
- git-check-ref-format "heads/$2" || {
- echo >&2 "'$2' is not suitable for a branch name"
- exit 1
- }
- test -z "$origin_override" || {
- echo >&2 "Do not give more than one --origin options."
- exit 1
- }
+ git-check-ref-format "heads/$2" ||
+ die "'$2' is not suitable for a branch name"
+ test -z "$origin_override" ||
+ die "Do not give more than one --origin options."
origin_override=yes
origin="$2"; shift
;;
@@ -169,24 +166,19 @@ do
done
repo="$1"
-if test -z "$repo"
-then
- echo >&2 'you must specify a repository to clone.'
- exit 1
-fi
+test -n "$repo" ||
+ die 'you must specify a repository to clone.'
# --bare implies --no-checkout
if test yes = "$bare"
then
if test yes = "$origin_override"
then
- echo >&2 '--bare and --origin $origin options are incompatible.'
- exit 1
+ die '--bare and --origin $origin options are incompatible.'
fi
if test t = "$use_separate_remote"
then
- echo >&2 '--bare and --use-separate-remote options are incompatible.'
- exit 1
+ die '--bare and --use-separate-remote options are incompatible.'
fi
no_checkout=yes
fi
@@ -206,7 +198,7 @@ fi
dir="$2"
# Try using "humanish" part of source repo if user didn't specify one
[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
-[ -e "$dir" ] && echo "$dir already exists." && usage
+[ -e "$dir" ] && die "destination directory '$dir' already exists."
mkdir -p "$dir" &&
D=$(cd "$dir" && pwd) &&
trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0
@@ -233,7 +225,7 @@ then
cd reference-tmp &&
tar xf -)
else
- echo >&2 "$reference: not a local directory." && usage
+ die "reference repository '$reference' is not a local directory."
fi
fi
@@ -242,10 +234,8 @@ rm -f "$GIT_DIR/CLONE_HEAD"
# We do local magic only when the user tells us to.
case "$local,$use_local" in
yes,yes)
- ( cd "$repo/objects" ) || {
- echo >&2 "-l flag seen but $repo is not local."
- exit 1
- }
+ ( cd "$repo/objects" ) ||
+ die "-l flag seen but repository '$repo' is not local."
case "$local_shared" in
no)
@@ -307,18 +297,15 @@ yes,yes)
then
clone_dumb_http "$repo" "$D"
else
- echo >&2 "http transport not supported, rebuild Git with curl support"
- exit 1
+ die "http transport not supported, rebuild Git with curl support"
fi
;;
*)
case "$upload_pack" in
'') git-fetch-pack --all -k $quiet "$repo" ;;
*) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;;
- esac >"$GIT_DIR/CLONE_HEAD" || {
- echo >&2 "fetch-pack from '$repo' failed."
- exit 1
- }
+ esac >"$GIT_DIR/CLONE_HEAD" ||
+ die "fetch-pack from '$repo' failed."
;;
esac
;;
@@ -414,7 +401,8 @@ Pull: refs/heads/$head_points_at:$origin_track" &&
case "$no_checkout" in
'')
- git-read-tree -m -u -v HEAD HEAD
+ test "z$quiet" = z && v=-v || v=
+ git-read-tree -m -u $v HEAD HEAD
esac
fi
rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
diff --git a/git-commit.sh b/git-commit.sh
index 5b1cf8582..81c3a0cb6 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -441,7 +441,7 @@ then
elif test "$use_commit" != ""
then
git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
-elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
+elif test -f "$GIT_DIR/MERGE_MSG"
then
cat "$GIT_DIR/MERGE_MSG"
elif test -f "$GIT_DIR/SQUASH_MSG"
@@ -522,15 +522,15 @@ then
PARENTS=$(git-cat-file commit HEAD |
sed -n -e '/^$/q' -e 's/^parent /-p /p')
fi
- current=$(git-rev-parse --verify HEAD)
+ current="$(git-rev-parse --verify HEAD)"
else
if [ -z "$(git-ls-files)" ]; then
echo >&2 Nothing to commit
exit 1
fi
PARENTS=""
- current=
rloga='commit (initial)'
+ current=''
fi
if test -z "$no_edit"
@@ -606,8 +606,8 @@ then
fi &&
commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
- git-update-ref -m "$rloga: $rlogm" HEAD $commit $current &&
- rm -f -- "$GIT_DIR/MERGE_HEAD" &&
+ git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" &&
+ rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
if test -f "$NEXT_INDEX"
then
mv "$NEXT_INDEX" "$THIS_INDEX"
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index e5a00a128..14e2c6131 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -495,22 +495,17 @@ unless(-d $git_dir) {
$tip_at_start = `git-rev-parse --verify HEAD`;
# Get the last import timestamps
- opendir(D,"$git_dir/refs/heads");
- while(defined(my $head = readdir(D))) {
- next if $head =~ /^\./;
- open(F,"$git_dir/refs/heads/$head")
- or die "Bad head branch: $head: $!\n";
- chomp(my $ftag = <F>);
- close(F);
- open(F,"git-cat-file commit $ftag |");
- while(<F>) {
- next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
- $branch_date{$head} = $1;
- last;
- }
- close(F);
+ my $fmt = '($ref, $author) = (%(refname), %(author));';
+ open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
+ die "Cannot run git-for-each-ref: $!\n";
+ while(defined(my $entry = <H>)) {
+ my ($ref, $author);
+ eval($entry) || die "cannot eval refs list: $@";
+ my ($head) = ($ref =~ m|^refs/heads/(.*)|);
+ $author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+ $branch_date{$head} = $1;
}
- closedir(D);
+ close(H);
}
-d $git_dir
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 08ad831a3..8817f8bb4 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -2118,9 +2118,17 @@ sub new
mode TEXT NOT NULL
)
");
+ $self->{dbh}->do("
+ CREATE INDEX revision_ix1
+ ON revision (name,revision)
+ ");
+ $self->{dbh}->do("
+ CREATE INDEX revision_ix2
+ ON revision (name,commithash)
+ ");
}
- # Construct the revision table if required
+ # Construct the head table if required
unless ( $self->{tables}{head} )
{
$self->{dbh}->do("
@@ -2134,6 +2142,10 @@ sub new
mode TEXT NOT NULL
)
");
+ $self->{dbh}->do("
+ CREATE INDEX head_ix1
+ ON head (name)
+ ");
}
# Construct the properties table if required
diff --git a/git-fetch.sh b/git-fetch.sh
index b15fc2b38..7442dd2ca 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -20,7 +20,7 @@ verbose=
update_head_ok=
exec=
upload_pack=
-keep=--thin
+keep=
while case "$#" in 0) break ;; esac
do
case "$1" in
@@ -51,7 +51,7 @@ do
verbose=Yes
;;
-k|--k|--ke|--kee|--keep)
- keep=--keep
+ keep='-k -k'
;;
--reflog-action=*)
rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
@@ -147,15 +147,15 @@ update_local_ref () {
[ "$verbose" ] && echo >&2 " $label_: $newshort_"
return 0
fi
- oldshort_=$(git-rev-parse --short "$1" 2>/dev/null)
- mkdir -p "$(dirname "$GIT_DIR/$1")"
+ oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null)
+
case "$1" in
refs/tags/*)
# Tags need not be pointing at commits so there
# is no way to guarantee "fast-forward" anyway.
- if test -f "$GIT_DIR/$1"
+ if test -n "$oldshort_"
then
- if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+ if now_=$(git show-ref --hash "$1") && test "$now_" = "$2"
then
[ "$verbose" ] && echo >&2 "* $1: same as $3"
[ "$verbose" ] && echo >&2 " $label_: $newshort_" ||:
@@ -296,6 +296,7 @@ fetch_main () {
# There are transports that can fetch only one head at a time...
case "$remote" in
http://* | https://* | ftp://*)
+ proto=`expr "$remote" : '\([^:]*\):'`
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
@@ -319,7 +320,7 @@ fetch_main () {
done
expr "z$head" : "z$_x40\$" >/dev/null ||
die "Failed to fetch $remote_name from $remote"
- echo >&2 Fetching "$remote_name from $remote" using http
+ echo >&2 "Fetching $remote_name from $remote using $proto"
git-http-fetch -v -a "$head" "$remote/" || exit
;;
rsync://*)
@@ -367,9 +368,10 @@ fetch_main () {
;; # we are already done.
*)
( : subshell because we muck with IFS
+ pack_lockfile=
IFS=" $LF"
(
- git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
+ git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote"
) |
while read sha1 remote_name
do
@@ -377,6 +379,12 @@ fetch_main () {
failed)
echo >&2 "Fetch failure: $remote"
exit 1 ;;
+ # special line coming from index-pack with the pack name
+ pack)
+ continue ;;
+ keep)
+ pack_lockfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep"
+ continue ;;
esac
found=
single_force=
@@ -407,6 +415,7 @@ fetch_main () {
append_fetch_head "$sha1" "$remote" \
"$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
done
+ if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
) || exit ;;
esac
@@ -426,7 +435,7 @@ case "$no_tags$tags" in
sed -ne 's|^\([0-9a-f]*\)[ ]\(refs/tags/.*\)^{}$|\1 \2|p' |
while read sha1 name
do
- test -f "$GIT_DIR/$name" && continue
+ git-show-ref --verify --quiet -- $name && continue
git-check-ref-format "$name" || {
echo >&2 "warning: tag ${name} ignored"
continue
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index fba4b0cb5..c49e4c65a 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -23,6 +23,12 @@ case "${1:-.}${2:-.}${3:-.}" in
"$1.." | "$1.$1" | "$1$1.")
if [ "$2" ]; then
echo "Removing $4"
+ else
+ # read-tree checked that index matches HEAD already,
+ # so we know we do not have this path tracked.
+ # there may be an unrelated working tree file here,
+ # which we should just leave unmolested.
+ exit 0
fi
if test -f "$4"; then
rm -f -- "$4" &&
@@ -34,8 +40,16 @@ case "${1:-.}${2:-.}${3:-.}" in
#
# Added in one.
#
-".$2." | "..$3" )
+".$2.")
+ # the other side did not add and we added so there is nothing
+ # to be done.
+ ;;
+"..$3")
echo "Adding $4"
+ test -f "$4" || {
+ echo "ERROR: untracked $4 is overwritten by the merge."
+ exit 1
+ }
git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
exec git-checkout-index -u -f -- "$4"
;;
diff --git a/git-merge.sh b/git-merge.sh
index 49c46d55d..cb094388b 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -93,6 +93,8 @@ finish () {
esac
}
+case "$#" in 0) usage ;; esac
+
rloga=
while case "$#" in 0) break ;; esac
do
diff --git a/git-repack.sh b/git-repack.sh
index f2c9071d1..f150a558c 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Linus Torvalds
#
-USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]'
SUBDIRECTORY_OK='Yes'
. git-sh-setup
@@ -25,6 +25,15 @@ do
shift
done
+# Later we will default repack.UseDeltaBaseOffset to true
+default_dbo=false
+
+case "`git repo-config --bool repack.usedeltabaseoffset ||
+ echo $default_dbo`" in
+true)
+ extra="$extra --delta-base-offset" ;;
+esac
+
PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
PACKTMP="$GIT_DIR/.tmp-$$-pack"
rm -f "$PACKTMP"-*
@@ -36,11 +45,19 @@ case ",$all_into_one," in
args='--unpacked --incremental'
;;
,t,)
- args=
-
- # Redundancy check in all-into-one case is trivial.
- existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \
- find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
+ if [ -d "$PACKDIR" ]; then
+ for e in `cd "$PACKDIR" && find . -type f -name '*.pack' \
+ | sed -e 's/^\.\///' -e 's/\.pack$//'`
+ do
+ if [ -e "$PACKDIR/$e.keep" ]; then
+ : keep
+ else
+ args="$args --unpacked=$e.pack"
+ existing="$existing $e"
+ fi
+ done
+ fi
+ [ -z "$args" ] && args='--unpacked --incremental'
;;
esac
@@ -77,17 +94,16 @@ fi
if test "$remove_redundant" = t
then
- # We know $existing are all redundant only when
- # all-into-one is used.
- if test "$all_into_one" != '' && test "$existing" != ''
+ # We know $existing are all redundant.
+ if [ -n "$existing" ]
then
sync
( cd "$PACKDIR" &&
for e in $existing
do
case "$e" in
- ./pack-$name.pack | ./pack-$name.idx) ;;
- *) rm -f $e ;;
+ pack-$name) ;;
+ *) rm -f "$e.pack" "$e.idx" "$e.keep" ;;
esac
done
)
diff --git a/git-revert.sh b/git-revert.sh
index 4fd81b6ed..6eab3c72d 100755
--- a/git-revert.sh
+++ b/git-revert.sh
@@ -145,9 +145,18 @@ git-read-tree -m -u --aggressive $base $head $next &&
result=$(git-write-tree 2>/dev/null) || {
echo >&2 "Simple $me fails; trying Automatic $me."
git-merge-index -o git-merge-one-file -a || {
+ mv -f .msg "$GIT_DIR/MERGE_MSG"
+ {
+ echo '
+Conflicts:
+'
+ git ls-files --unmerged |
+ sed -e 's/^[^ ]* / /' |
+ uniq
+ } >>"$GIT_DIR/MERGE_MSG"
echo >&2 "Automatic $me failed. After resolving the conflicts,"
echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
- echo >&2 "and commit with 'git commit -F .msg'"
+ echo >&2 "and commit the result."
case "$me" in
cherry-pick)
echo >&2 "You may choose to use the following when making"
diff --git a/git-send-email.perl b/git-send-email.perl
index 1c6d2cc78..4c87c20c1 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -231,6 +231,9 @@ if (!defined $initial_reply_to && $prompting) {
}
if (!$smtp_server) {
+ $smtp_server = $repo->config('sendemail.smtpserver');
+}
+if (!$smtp_server) {
foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
if (-x $_) {
$smtp_server = $_;
@@ -514,7 +517,7 @@ foreach my $t (@files) {
$2, $_) unless $quiet;
push @cc, $2;
}
- elsif (/^[-A-Za-z]+:\s+\S/) {
+ elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
push @xh, $_;
}
diff --git a/git-svn.perl b/git-svn.perl
index 54d235693..4a56f1871 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -1501,10 +1501,13 @@ sub svn_checkout_tree {
apply_mod_line_blob($m);
svn_check_prop_executable($m);
} elsif ($m->{chg} eq 'T') {
- sys(qw(svn rm --force),$m->{file_b});
- apply_mod_line_blob($m);
- sys(qw(svn add), $m->{file_b});
svn_check_prop_executable($m);
+ apply_mod_line_blob($m);
+ if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+ sys(qw(svn propdel svn:special), $m->{file_b});
+ } else {
+ sys(qw(svn propset svn:special *),$m->{file_b});
+ }
} elsif ($m->{chg} eq 'A') {
svn_ensure_parent_path( $m->{file_b} );
apply_mod_line_blob($m);
@@ -2659,11 +2662,12 @@ sub libsvn_connect {
}
sub libsvn_get_file {
- my ($gui, $f, $rev) = @_;
+ my ($gui, $f, $rev, $chg) = @_;
my $p = $f;
if (length $SVN_PATH > 0) {
return unless ($p =~ s#^\Q$SVN_PATH\E/##);
}
+ print "\t$chg\t$f\n" unless $_q;
my ($hash, $pid, $in, $out);
my $pool = SVN::Pool->new;
@@ -2766,8 +2770,7 @@ sub libsvn_fetch {
$pool->clear;
}
foreach (@amr) {
- print "\t$_->[0]\t$_->[1]\n" unless $_q;
- libsvn_get_file($gui, $_->[1], $rev)
+ libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
}
close $gui or croak $?;
return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
@@ -2845,8 +2848,7 @@ sub libsvn_traverse {
if (defined $files) {
push @$files, $file;
} else {
- print "\tA\t$file\n" unless $_q;
- libsvn_get_file($gui, $file, $rev);
+ libsvn_get_file($gui, $file, $rev, 'A');
}
}
}
@@ -3137,7 +3139,7 @@ sub copy_remote_ref {
my $ref = "refs/remotes/$GIT_SVN";
if (safe_qx('git-ls-remote', $origin, $ref)) {
sys(qw/git fetch/, $origin, "$ref:$ref");
- } else {
+ } elsif ($_cp_remote && !$_upgrade) {
die "Unable to find remote reference: ",
"refs/remotes/$GIT_SVN on $origin\n";
}
diff --git a/git-svnimport.perl b/git-svnimport.perl
index f6eff8e32..cbaa8ab37 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE";
$ENV{'TZ'}="UTC";
our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
- $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F);
+ $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,$opt_P);
sub usage() {
print STDERR <<END;
@@ -39,17 +39,19 @@ Usage: ${\basename $0} # fetch/update GIT from SVN
[-o branch-for-HEAD] [-h] [-v] [-l max_rev]
[-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
[-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
- [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL]
+ [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
END
exit(1);
}
-getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage();
+getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:uv") or usage();
usage if $opt_h;
my $tag_name = $opt_t || "tags";
my $trunk_name = $opt_T || "trunk";
my $branch_name = $opt_b || "branches";
+my $project_name = $opt_P || "";
+$project_name = "/" . $project_name if ($project_name);
@ARGV == 1 or @ARGV == 2 or usage();
@@ -427,6 +429,20 @@ sub get_ignore($$$$$) {
}
}
+sub project_path($$)
+{
+ my ($path, $project) = @_;
+
+ $path = "/".$path unless ($path =~ m#^\/#) ;
+ return $1 if ($path =~ m#^$project\/(.*)$#);
+
+ $path =~ s#\.#\\\.#g;
+ $path =~ s#\+#\\\+#g;
+ return "/" if ($project =~ m#^$path.*$#);
+
+ return undef;
+}
+
sub split_path($$) {
my($rev,$path) = @_;
my $branch;
@@ -446,7 +462,11 @@ sub split_path($$) {
print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
return ()
}
- $path = "/" if $path eq "";
+ if ($path eq "") {
+ $path = "/";
+ } elsif ($project_name) {
+ $path = project_path($path, $project_name);
+ }
return ($branch,$path);
}
@@ -898,6 +918,7 @@ sub commit_all {
while(my($path,$action) = each %$changed_paths) {
($branch,$path) = split_path($revision,$path);
next if not defined $branch;
+ next if not defined $path;
$done{$branch}{$path} = $action;
}
while(($branch,$changed_paths) = each %done) {
diff --git a/git-tag.sh b/git-tag.sh
index a0afa2582..ac269e327 100755
--- a/git-tag.sh
+++ b/git-tag.sh
@@ -47,8 +47,10 @@ do
-d)
shift
tag_name="$1"
- rm "$GIT_DIR/refs/tags/$tag_name" && \
- echo "Deleted tag $tag_name."
+ tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") ||
+ die "Seriously, what tag are you talking about?"
+ git-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" &&
+ echo "Deleted tag $tag_name."
exit $?
;;
-*)
@@ -63,8 +65,11 @@ done
name="$1"
[ "$name" ] || usage
-if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
- die "tag '$name' already exists"
+prev=0000000000000000000000000000000000000000
+if git-show-ref --verify --quiet -- "refs/tags/$name"
+then
+ test -n "$force" || die "tag '$name' already exists"
+ prev=`git rev-parse "refs/tags/$name"`
fi
shift
git-check-ref-format "tags/$name" ||
@@ -107,6 +112,5 @@ if [ "$annotate" ]; then
object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
fi
-leading=`expr "refs/tags/$name" : '\(.*\)/'` &&
-mkdir -p "$GIT_DIR/$leading" &&
-echo $object > "$GIT_DIR/refs/tags/$name"
+git update-ref "refs/tags/$name" "$object" "$prev"
+
diff --git a/git.c b/git.c
index b944c3740..9a9addf71 100644
--- a/git.c
+++ b/git.c
@@ -219,11 +219,14 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
int option;
} commands[] = {
{ "add", cmd_add, RUN_SETUP },
+ { "annotate", cmd_annotate, },
{ "apply", cmd_apply },
{ "archive", cmd_archive },
+ { "branch", cmd_branch, RUN_SETUP },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
+ { "cherry", cmd_cherry, RUN_SETUP },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "count-objects", cmd_count_objects, RUN_SETUP },
{ "diff", cmd_diff, RUN_SETUP | USE_PAGER },
@@ -232,6 +235,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "diff-stages", cmd_diff_stages, RUN_SETUP },
{ "diff-tree", cmd_diff_tree, RUN_SETUP },
{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
+ { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
{ "format-patch", cmd_format_patch, RUN_SETUP },
{ "get-tar-commit-id", cmd_get_tar_commit_id },
{ "grep", cmd_grep, RUN_SETUP },
@@ -268,6 +272,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
{ "write-tree", cmd_write_tree, RUN_SETUP },
{ "verify-pack", cmd_verify_pack },
+ { "show-ref", cmd_show_ref, RUN_SETUP },
+ { "pack-refs", cmd_pack_refs, RUN_SETUP },
};
int i;
diff --git a/git.spec.in b/git.spec.in
index 9b1217ac3..83268fc9d 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -96,10 +96,10 @@ find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
%if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
%else
rm -rf $RPM_BUILD_ROOT%{_mandir}
%endif
@@ -126,10 +126,10 @@ rm -rf $RPM_BUILD_ROOT
%files arch
%defattr(-,root,root)
-%doc Documentation/*arch*.txt
-%{_bindir}/*arch*
-%{!?_without_docs: %{_mandir}/man1/*arch*.1*}
-%{!?_without_docs: %doc Documentation/*arch*.html }
+%doc Documentation/git-archimport.txt
+%{_bindir}/git-archimport
+%{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
+%{!?_without_docs: %doc Documentation/git-archimport.html }
%files email
%defattr(-,root,root)
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index 3f62b6d75..0eda98237 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -333,6 +333,8 @@ div.index_include {
}
div.search {
+ font-size: 12px;
+ font-weight: normal;
margin: 4px 8px;
position: absolute;
top: 56px;
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 035e85e6e..3c6fd7ca4 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -39,13 +39,20 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
# name of your site or organization to appear in page titles
# replace this with something more descriptive for clearer bookmarks
-our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
+our $site_name = "++GITWEB_SITENAME++"
+ || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
+# filename of html text to include at top of each page
+our $site_header = "++GITWEB_SITE_HEADER++";
# html text to include at home page
our $home_text = "++GITWEB_HOMETEXT++";
+# filename of html text to include at bottom of each page
+our $site_footer = "++GITWEB_SITE_FOOTER++";
-# URI of default stylesheet
-our $stylesheet = "++GITWEB_CSS++";
+# URI of stylesheets
+our @stylesheets = ("++GITWEB_CSS++");
+# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
+our $stylesheet = undef;
# URI of GIT logo (72x27 size)
our $logo = "++GITWEB_LOGO++";
# URI of GIT favicon, assumed to be image/png type
@@ -69,7 +76,7 @@ our $strict_export = "++GITWEB_STRICT_EXPORT++";
# list of git base URLs used for URL to where fetch project from,
# i.e. full URL is "$git_base_url/$project"
-our @git_base_url_list = ("++GITWEB_BASE_URL++");
+our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
# default blob_plain mimetype and default charset for text/plain blob
our $default_blob_plain_mimetype = 'text/plain';
@@ -153,6 +160,21 @@ our %feature = (
'pathinfo' => {
'override' => 0,
'default' => [0]},
+
+ # Make gitweb consider projects in project root subdirectories
+ # to be forks of existing projects. Given project $projname.git,
+ # projects matching $projname/*.git will not be shown in the main
+ # projects list, instead a '+' mark will be added to $projname
+ # there and a 'forks' view will be enabled for the project, listing
+ # all the forks. This feature is supported only if project list
+ # is taken from a directory, not file.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'forks'}{'default'} = [1];
+ # Project specific override is not supported.
+ 'forks' => {
+ 'override' => 0,
+ 'default' => [0]},
);
sub gitweb_check_feature {
@@ -217,6 +239,22 @@ sub feature_pickaxe {
return ($_[0]);
}
+# checking HEAD file with -e is fragile if the repository was
+# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
+# and then pruned.
+sub check_head_link {
+ my ($dir) = @_;
+ my $headfile = "$dir/HEAD";
+ return ((-e $headfile) ||
+ (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
+}
+
+sub check_export_ok {
+ my ($dir) = @_;
+ return (check_head_link($dir) &&
+ (!$export_ok || -e "$dir/$export_ok"));
+}
+
# rename detection options for git-diff and git-diff-tree
# - default is '-M', with the cost proportional to
# (number of removed files) * (number of new files).
@@ -249,7 +287,7 @@ our $project = $cgi->param('p');
if (defined $project) {
if (!validate_pathname($project) ||
!(-d "$projectroot/$project") ||
- !(-e "$projectroot/$project/HEAD") ||
+ !check_head_link("$projectroot/$project") ||
($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
($strict_export && !project_in_list($project))) {
undef $project;
@@ -316,6 +354,13 @@ if (defined $searchtext) {
$searchtext = quotemeta $searchtext;
}
+our $searchtype = $cgi->param('st');
+if (defined $searchtype) {
+ if ($searchtype =~ m/[^a-z]/) {
+ die_error(undef, "Invalid searchtype parameter");
+ }
+}
+
# now read PATH_INFO and use it as alternative to parameters
sub evaluate_path_info {
return if defined $project;
@@ -326,7 +371,7 @@ sub evaluate_path_info {
# find which part of PATH_INFO is project
$project = $path_info;
$project =~ s,/+$,,;
- while ($project && !-e "$projectroot/$project/HEAD") {
+ while ($project && !check_head_link("$projectroot/$project")) {
$project =~ s,/*[^/]*$,,;
}
# validate project
@@ -375,11 +420,13 @@ my %actions = (
"commitdiff" => \&git_commitdiff,
"commitdiff_plain" => \&git_commitdiff_plain,
"commit" => \&git_commit,
+ "forks" => \&git_forks,
"heads" => \&git_heads,
"history" => \&git_history,
"log" => \&git_log,
"rss" => \&git_rss,
"search" => \&git_search,
+ "search_help" => \&git_search_help,
"shortlog" => \&git_shortlog,
"summary" => \&git_summary,
"tag" => \&git_tag,
@@ -429,6 +476,7 @@ sub href(%) {
page => "pg",
order => "o",
searchtext => "s",
+ searchtype => "st",
);
my %mapping = @mapping;
@@ -522,12 +570,17 @@ sub esc_url {
}
# replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html {
+sub esc_html ($;%) {
my $str = shift;
+ my %opts = @_;
+
$str = to_utf8($str);
$str = escapeHTML($str);
$str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
$str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1)
+ if ($opts{'-nbsp'}) {
+ $str =~ s/ /&nbsp;/g;
+ }
return $str;
}
@@ -752,7 +805,7 @@ sub format_diff_line {
$diff_class = " incomplete";
}
$line = untabify($line);
- return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
+ return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
}
## ----------------------------------------------------------------------
@@ -828,7 +881,7 @@ sub git_get_hash_by_path {
close $fd or return undef;
#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
- $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+ $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
if (defined $type && $type ne $2) {
# type doesn't match
return undef;
@@ -860,13 +913,21 @@ sub git_get_project_url_list {
}
sub git_get_projects_list {
+ my ($filter) = @_;
my @list;
+ $filter ||= '';
+ $filter =~ s/\.git$//;
+
if (-d $projects_list) {
# search in directory
- my $dir = $projects_list;
+ my $dir = $projects_list . ($filter ? "/$filter" : '');
+ # remove the trailing "/"
+ $dir =~ s!/+$!!;
my $pfxlen = length("$dir");
+ my $check_forks = gitweb_check_feature('forks');
+
File::Find::find({
follow_fast => 1, # follow symbolic links
dangling_symlinks => 0, # ignore dangling symlinks, silently
@@ -878,9 +939,10 @@ sub git_get_projects_list {
my $subdir = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- if (-e "$projectroot/$subdir/HEAD" && (!$export_ok ||
- -e "$projectroot/$subdir/$export_ok")) {
- push @list, { path => $subdir };
+ if ($check_forks and $subdir =~ m#/.#) {
+ $File::Find::prune = 1;
+ } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
+ push @list, { path => ($filter ? "$filter/" : '') . $subdir };
$File::Find::prune = 1;
}
},
@@ -900,8 +962,7 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if (-e "$projectroot/$path/HEAD" && (!$export_ok ||
- -e "$projectroot/$path/$export_ok")) {
+ if (check_export_ok("$projectroot/$path")) {
my $pr = {
path => $path,
owner => to_utf8($owner),
@@ -946,6 +1007,24 @@ sub git_get_project_owner {
return $owner;
}
+sub git_get_last_activity {
+ my ($path) = @_;
+ my $fd;
+
+ $git_dir = "$projectroot/$path";
+ open($fd, "-|", git_cmd(), 'for-each-ref',
+ '--format=%(refname) %(committer)',
+ '--sort=-committerdate',
+ 'refs/heads') or return;
+ my $most_recent = <$fd>;
+ close $fd or return;
+ if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+ my $timestamp = $1;
+ my $age = time - $timestamp;
+ return ($age, age_string($age));
+ }
+}
+
sub git_get_references {
my $type = shift || "";
my %refs;
@@ -1011,6 +1090,9 @@ sub parse_date {
$date{'hour_local'} = $hour;
$date{'minute_local'} = $min;
$date{'tz_local'} = $tz;
+ $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
+ 1900+$year, $mon+1, $mday,
+ $hour, $min, $sec, $tz);
return %date;
}
@@ -1059,12 +1141,13 @@ sub parse_commit {
if (defined $commit_text) {
@commit_lines = @$commit_text;
} else {
- $/ = "\0";
- open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id
+ local $/ = "\0";
+ open my $fd, "-|", git_cmd(), "rev-list",
+ "--header", "--parents", "--max-count=1",
+ $commit_id, "--"
or return;
@commit_lines = split '\n', <$fd>;
close $fd or return;
- $/ = "\n";
pop @commit_lines;
}
my $header = shift @commit_lines;
@@ -1225,7 +1308,7 @@ sub parse_ls_tree_line ($;%) {
my %res;
#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
- $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+ $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
$res{'mode'} = $1;
$res{'type'} = $2;
@@ -1242,47 +1325,88 @@ sub parse_ls_tree_line ($;%) {
## ......................................................................
## parse to array of hashes functions
-sub git_get_refs_list {
- my $type = shift || "";
- my %refs;
- my @reflist;
+sub git_get_heads_list {
+ my $limit = shift;
+ my @headslist;
- my @refs;
- open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+ open my $fd, '-|', git_cmd(), 'for-each-ref',
+ ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+ '--format=%(objectname) %(refname) %(subject)%00%(committer)',
+ 'refs/heads'
or return;
while (my $line = <$fd>) {
- chomp $line;
- if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) {
- if (defined $refs{$1}) {
- push @{$refs{$1}}, $2;
- } else {
- $refs{$1} = [ $2 ];
- }
+ my %ref_item;
- if (! $4) { # unpeeled, direct reference
- push @refs, { hash => $1, name => $3 }; # without type
- } elsif ($3 eq $refs[-1]{'name'}) {
- # most likely a tag is followed by its peeled
- # (deref) one, and when that happens we know the
- # previous one was of type 'tag'.
- $refs[-1]{'type'} = "tag";
- }
+ chomp $line;
+ my ($refinfo, $committerinfo) = split(/\0/, $line);
+ my ($hash, $name, $title) = split(' ', $refinfo, 3);
+ my ($committer, $epoch, $tz) =
+ ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+ $name =~ s!^refs/heads/!!;
+
+ $ref_item{'name'} = $name;
+ $ref_item{'id'} = $hash;
+ $ref_item{'title'} = $title || '(no commit message)';
+ $ref_item{'epoch'} = $epoch;
+ if ($epoch) {
+ $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+ } else {
+ $ref_item{'age'} = "unknown";
}
+
+ push @headslist, \%ref_item;
}
close $fd;
- foreach my $ref (@refs) {
- my $ref_file = $ref->{'name'};
- my $ref_id = $ref->{'hash'};
+ return wantarray ? @headslist : \@headslist;
+}
- my $type = $ref->{'type'} || git_get_type($ref_id) || next;
- my %ref_item = parse_ref($ref_file, $ref_id, $type);
+sub git_get_tags_list {
+ my $limit = shift;
+ my @tagslist;
- push @reflist, \%ref_item;
+ open my $fd, '-|', git_cmd(), 'for-each-ref',
+ ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
+ '--format=%(objectname) %(objecttype) %(refname) '.
+ '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
+ 'refs/tags'
+ or return;
+ while (my $line = <$fd>) {
+ my %ref_item;
+
+ chomp $line;
+ my ($refinfo, $creatorinfo) = split(/\0/, $line);
+ my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
+ my ($creator, $epoch, $tz) =
+ ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+ $name =~ s!^refs/tags/!!;
+
+ $ref_item{'type'} = $type;
+ $ref_item{'id'} = $id;
+ $ref_item{'name'} = $name;
+ if ($type eq "tag") {
+ $ref_item{'subject'} = $title;
+ $ref_item{'reftype'} = $reftype;
+ $ref_item{'refid'} = $refid;
+ } else {
+ $ref_item{'reftype'} = $type;
+ $ref_item{'refid'} = $id;
+ }
+
+ if ($type eq "tag" || $type eq "commit") {
+ $ref_item{'epoch'} = $epoch;
+ if ($epoch) {
+ $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+ } else {
+ $ref_item{'age'} = "unknown";
+ }
+ }
+
+ push @tagslist, \%ref_item;
}
- # sort refs by age
- @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
- return (\@reflist, \%refs);
+ close $fd;
+
+ return wantarray ? @tagslist : \@tagslist;
}
## ----------------------------------------------------------------------
@@ -1379,7 +1503,7 @@ sub git_header_html {
my $status = shift || "200 OK";
my $expires = shift;
- my $title = "$site_name git";
+ my $title = "$site_name";
if (defined $project) {
$title .= " - $project";
if (defined $action) {
@@ -1417,8 +1541,17 @@ sub git_header_html {
<meta name="generator" content="gitweb/$version git/$git_version"/>
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
EOF
+# print out each stylesheet that exist
+ if (defined $stylesheet) {
+#provides backwards capability for those people who define style sheet in a config file
+ print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ } else {
+ foreach my $stylesheet (@stylesheets) {
+ next unless $stylesheet;
+ print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ }
+ }
if (defined $project) {
printf('<link rel="alternate" title="%s log" '.
'href="%s" type="application/rss+xml"/>'."\n",
@@ -1436,8 +1569,15 @@ EOF
}
print "</head>\n" .
- "<body>\n" .
- "<div class=\"page_header\">\n" .
+ "<body>\n";
+
+ if (-f $site_header) {
+ open (my $fd, $site_header);
+ print <$fd>;
+ close $fd;
+ }
+
+ print "<div class=\"page_header\">\n" .
$cgi->a({-href => esc_url($logo_url),
-title => $logo_label},
qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
@@ -1467,6 +1607,10 @@ EOF
$cgi->hidden(-name => "p") . "\n" .
$cgi->hidden(-name => "a") . "\n" .
$cgi->hidden(-name => "h") . "\n" .
+ $cgi->popup_menu(-name => 'st', -default => 'commit',
+ -values => ['commit', 'author', 'committer', 'pickaxe']) .
+ $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+ " search:\n",
$cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
"</div>" .
$cgi->end_form() . "\n";
@@ -1489,8 +1633,15 @@ sub git_footer_html {
print $cgi->a({-href => href(project=>undef, action=>"project_index"),
-class => "rss_logo"}, "TXT") . "\n";
}
- print "</div>\n" .
- "</body>\n" .
+ print "</div>\n" ;
+
+ if (-f $site_footer) {
+ open (my $fd, $site_footer);
+ print <$fd>;
+ close $fd;
+ }
+
+ print "</body>\n" .
"</html>";
}
@@ -1615,17 +1766,16 @@ sub git_print_page_path {
my $type = shift;
my $hb = shift;
- if (!defined $name) {
- print "<div class=\"page_path\">/</div>\n";
- } else {
+
+ print "<div class=\"page_path\">";
+ print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+ -title => 'tree root'}, "[$project]");
+ print " / ";
+ if (defined $name) {
my @dirname = split '/', $name;
my $basename = pop @dirname;
my $fullname = '';
- print "<div class=\"page_path\">";
- print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
- -title => 'tree root'}, "[$project]");
- print " / ";
foreach my $dir (@dirname) {
$fullname .= ($fullname ? '/' : '') . $dir;
print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
@@ -1641,11 +1791,12 @@ sub git_print_page_path {
print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
hash_base=>$hb),
-title => $name}, esc_html($basename));
+ print " / ";
} else {
print esc_html($basename);
}
- print "<br/></div>\n";
}
+ print "<br/></div>\n";
}
# sub git_print_log (\@;%) {
@@ -1698,15 +1849,6 @@ sub git_print_log ($;%) {
}
}
-sub git_print_simplified_log {
- my $log = shift;
- my $remove_title = shift;
-
- git_print_log($log,
- -final_empty_line=> 1,
- -remove_title => $remove_title);
-}
-
# print tree entry (row of git_tree), but without encompassing <tr> element
sub git_print_tree_entry {
my ($t, $basedir, $hash_base, $have_blame) = @_;
@@ -1722,26 +1864,28 @@ sub git_print_tree_entry {
if ($t->{'type'} eq "blob") {
print "<td class=\"list\">" .
$cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
- file_name=>"$basedir$t->{'name'}", %base_key),
- -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
+ file_name=>"$basedir$t->{'name'}", %base_key),
+ -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
print "<td class=\"link\">";
+ print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "blob");
if ($have_blame) {
- print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
- file_name=>"$basedir$t->{'name'}", %base_key)},
- "blame");
+ print " | " .
+ $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "blame");
}
if (defined $hash_base) {
- if ($have_blame) {
- print " | ";
- }
- print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ print " | " .
+ $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
"history");
}
print " | " .
$cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
- file_name=>"$basedir$t->{'name'}")},
- "raw");
+ file_name=>"$basedir$t->{'name'}")},
+ "raw");
print "</td>\n";
} elsif ($t->{'type'} eq "tree") {
@@ -1751,8 +1895,12 @@ sub git_print_tree_entry {
esc_html($t->{'name'}));
print "</td>\n";
print "<td class=\"link\">";
+ print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "tree");
if (defined $hash_base) {
- print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ print " | " .
+ $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
file_name=>"$basedir$t->{'name'}")},
"history");
}
@@ -1809,7 +1957,7 @@ sub git_difftree_body {
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
+ -class => "list"}, esc_html($diff{'file'}));
print "</td>\n";
print "<td>$mode_chng</td>\n";
print "<td class=\"link\">";
@@ -1835,12 +1983,15 @@ sub git_difftree_body {
print $cgi->a({-href => "#patch$patchno"}, "patch");
print " | ";
}
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'file'})},
+ "blob") . " | ";
print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
- file_name=>$diff{'file'})},
- "blame") . " | ";
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
- file_name=>$diff{'file'})},
- "history");
+ file_name=>$diff{'file'})},
+ "history");
print "</td>\n";
} elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
@@ -1861,31 +2012,34 @@ sub git_difftree_body {
}
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
- hash_base=>$hash, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_html($diff{'file'}));
print "</td>\n";
print "<td>$mode_chnge</td>\n";
print "<td class=\"link\">";
- if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
- if ($action eq 'commitdiff') {
- # link to patch
- $patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch");
- } else {
- print $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'file'})},
- "diff");
- }
- print " | ";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ " | ";
+ } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+ # "commit" view and modified file (not onlu mode changed)
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'file'})},
+ "diff") .
+ " | ";
}
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob") . " | ";
print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
- file_name=>$diff{'file'})},
- "blame") . " | ";
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
- file_name=>$diff{'file'})},
- "history");
+ file_name=>$diff{'file'})},
+ "history");
print "</td>\n";
} elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
@@ -1906,26 +2060,29 @@ sub git_difftree_body {
-class => "list"}, esc_html($diff{'from_file'})) .
" with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
"<td class=\"link\">";
- if ($diff{'to_id'} ne $diff{'from_id'}) {
- if ($action eq 'commitdiff') {
- # link to patch
- $patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch");
- } else {
- print $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
- "diff");
- }
- print " | ";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ " | ";
+ } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+ # "commit" view and modified file (not only pure rename or copy)
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+ "diff") .
+ " | ";
}
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'from_file'})},
+ "blob") . " | ";
print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
- file_name=>$diff{'from_file'})},
- "blame") . " | ";
+ file_name=>$diff{'from_file'})},
+ "blame") . " | ";
print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
- file_name=>$diff{'from_file'})},
- "history");
+ file_name=>$diff{'from_file'})},
+ "history");
print "</td>\n";
} # we should not encounter Unmerged (U) or Unknown (X) status
@@ -1966,13 +2123,6 @@ sub git_patchset_body {
}
$patch_idx++;
- # for now, no extended header, hence we skip empty patches
- # companion to next LINE if $in_header;
- if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change
- $in_header = 1;
- next LINE;
- }
-
if ($diffinfo->{'status'} eq "A") { # added
print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
@@ -2059,6 +2209,124 @@ sub git_patchset_body {
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+sub git_project_list_body {
+ my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+
+ my $check_forks = gitweb_check_feature('forks');
+
+ my @projects;
+ foreach my $pr (@$projlist) {
+ my (@aa) = git_get_last_activity($pr->{'path'});
+ unless (@aa) {
+ next;
+ }
+ ($pr->{'age'}, $pr->{'age_string'}) = @aa;
+ if (!defined $pr->{'descr'}) {
+ my $descr = git_get_project_description($pr->{'path'}) || "";
+ $pr->{'descr'} = chop_str($descr, 25, 5);
+ }
+ if (!defined $pr->{'owner'}) {
+ $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+ }
+ if ($check_forks) {
+ my $pname = $pr->{'path'};
+ $pname =~ s/\.git$//;
+ $pr->{'forks'} = -d "$projectroot/$pname";
+ }
+ push @projects, $pr;
+ }
+
+ $order ||= "project";
+ $from = 0 unless defined $from;
+ $to = $#projects if (!defined $to || $#projects < $to);
+
+ print "<table class=\"project_list\">\n";
+ unless ($no_header) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<th></th>\n";
+ }
+ if ($order eq "project") {
+ @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+ print "<th>Project</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'project'),
+ -class => "header"}, "Project") .
+ "</th>\n";
+ }
+ if ($order eq "descr") {
+ @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+ print "<th>Description</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'descr'),
+ -class => "header"}, "Description") .
+ "</th>\n";
+ }
+ if ($order eq "owner") {
+ @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+ print "<th>Owner</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'owner'),
+ -class => "header"}, "Owner") .
+ "</th>\n";
+ }
+ if ($order eq "age") {
+ @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
+ print "<th>Last Change</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'age'),
+ -class => "header"}, "Last Change") .
+ "</th>\n";
+ }
+ print "<th></th>\n" .
+ "</tr>\n";
+ }
+ my $alternate = 1;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $pr = $projects[$i];
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ if ($check_forks) {
+ print "<td>";
+ if ($pr->{'forks'}) {
+ print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+ }
+ print "</td>\n";
+ }
+ print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+ -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+ "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+ "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+ print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+ $pr->{'age_string'} . "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
+ $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+ ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+ "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<td></td>\n";
+ }
+ print "<td colspan=\"5\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
sub git_shortlog_body {
# uses global variable $project
my ($revlist, $from, $to, $refs, $extra) = @_;
@@ -2087,6 +2355,7 @@ sub git_shortlog_body {
href(action=>"commit", hash=>$commit), $ref);
print "</td>\n" .
"<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
$cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
$cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
if (gitweb_have_snapshot()) {
@@ -2178,8 +2447,7 @@ sub git_tags_body {
for (my $i = $from; $i <= $to; $i++) {
my $entry = $taglist->[$i];
my %tag = %$entry;
- my $comment_lines = $tag{'comment'};
- my $comment = shift @$comment_lines;
+ my $comment = $tag{'subject'};
my $comment_short;
if (defined $comment) {
$comment_short = chop_str($comment, 30, 5);
@@ -2212,7 +2480,7 @@ sub git_tags_body {
$cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
if ($tag{'reftype'} eq "commit") {
print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") .
- " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log");
+ " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log");
} elsif ($tag{'reftype'} eq "blob") {
print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
}
@@ -2237,23 +2505,23 @@ sub git_heads_body {
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $entry = $headlist->[$i];
- my %tag = %$entry;
- my $curr = $tag{'id'} eq $head;
+ my %ref = %$entry;
+ my $curr = $ref{'id'} eq $head;
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
- print "<td><i>$tag{'age'}</i></td>\n" .
- ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
- $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}),
- -class => "list name"},esc_html($tag{'name'})) .
+ print "<td><i>$ref{'age'}</i></td>\n" .
+ ($curr ? "<td class=\"current_head\">" : "<td>") .
+ $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}),
+ -class => "list name"},esc_html($ref{'name'})) .
"</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " .
- $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " .
- $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") .
+ $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " .
+ $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") .
"</td>\n" .
"</tr>";
}
@@ -2276,30 +2544,9 @@ sub git_project_list {
}
my @list = git_get_projects_list();
- my @projects;
if (!@list) {
die_error(undef, "No projects found");
}
- foreach my $pr (@list) {
- my $head = git_get_head_hash($pr->{'path'});
- if (!defined $head) {
- next;
- }
- $git_dir = "$projectroot/$pr->{'path'}";
- my %co = parse_commit($head);
- if (!%co) {
- next;
- }
- $pr->{'commit'} = \%co;
- if (!defined $pr->{'descr'}) {
- my $descr = git_get_project_description($pr->{'path'}) || "";
- $pr->{'descr'} = chop_str($descr, 25, 5);
- }
- if (!defined $pr->{'owner'}) {
- $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
- }
- push @projects, $pr;
- }
git_header_html();
if (-f $home_text) {
@@ -2309,75 +2556,30 @@ sub git_project_list {
close $fd;
print "</div>\n";
}
- print "<table class=\"project_list\">\n" .
- "<tr>\n";
- $order ||= "project";
- if ($order eq "project") {
- @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
- print "<th>Project</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'project'),
- -class => "header"}, "Project") .
- "</th>\n";
- }
- if ($order eq "descr") {
- @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
- print "<th>Description</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'descr'),
- -class => "header"}, "Description") .
- "</th>\n";
- }
- if ($order eq "owner") {
- @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
- print "<th>Owner</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'owner'),
- -class => "header"}, "Owner") .
- "</th>\n";
- }
- if ($order eq "age") {
- @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
- print "<th>Last Change</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'age'),
- -class => "header"}, "Last Change") .
- "</th>\n";
+ git_project_list_body(\@list, $order);
+ git_footer_html();
+}
+
+sub git_forks {
+ my $order = $cgi->param('o');
+ if (defined $order && $order !~ m/project|descr|owner|age/) {
+ die_error(undef, "Unknown order parameter");
}
- print "<th></th>\n" .
- "</tr>\n";
- my $alternate = 1;
- foreach my $pr (@projects) {
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
- print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
- -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
- "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
- "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
- print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
- $pr->{'commit'}{'age_string'} . "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
- "</td>\n" .
- "</tr>\n";
+
+ my @list = git_get_projects_list($project);
+ if (!@list) {
+ die_error(undef, "No forks found");
}
- print "</table>\n";
+
+ git_header_html();
+ git_print_page_nav('','');
+ git_print_header_div('summary', "$project forks");
+ git_project_list_body(\@list, $order);
git_footer_html();
}
sub git_project_index {
- my @projects = git_get_projects_list();
+ my @projects = git_get_projects_list($project);
print $cgi->header(
-type => 'text/plain',
@@ -2408,17 +2610,12 @@ sub git_summary {
my $owner = git_get_project_owner($project);
- my ($reflist, $refs) = git_get_refs_list();
-
- my @taglist;
- my @headlist;
- foreach my $ref (@$reflist) {
- if ($ref->{'name'} =~ s!^heads/!!) {
- push @headlist, $ref;
- } else {
- $ref->{'name'} =~ s!^tags/!!;
- push @taglist, $ref;
- }
+ my $refs = git_get_references();
+ my @taglist = git_get_tags_list(15);
+ my @headlist = git_get_heads_list(15);
+ my @forklist;
+ if (gitweb_check_feature('forks')) {
+ @forklist = git_get_projects_list($project);
}
git_header_html();
@@ -2441,8 +2638,16 @@ sub git_summary {
}
print "</table>\n";
+ if (-s "$projectroot/$project/README.html") {
+ if (open my $fd, "$projectroot/$project/README.html") {
+ print "<div class=\"title\">readme</div>\n";
+ print $_ while (<$fd>);
+ close $fd;
+ }
+ }
+
open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
- git_get_head_hash($project)
+ git_get_head_hash($project), "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd;
@@ -2462,6 +2667,13 @@ sub git_summary {
$cgi->a({-href => href(action=>"heads")}, "..."));
}
+ if (@forklist) {
+ git_print_header_div('forks');
+ git_project_list_body(\@forklist, undef, 0, 15,
+ $cgi->a({-href => href(action=>"forks")}, "..."),
+ 'noheader');
+ }
+
git_footer_html();
}
@@ -2519,7 +2731,8 @@ sub git_blame2 {
if ($ftype !~ "blob") {
die_error("400 Bad Request", "Object is not a blob");
}
- open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base)
+ open ($fd, "-|", git_cmd(), "blame", '-p', '--',
+ $file_name, $hash_base)
or die_error(undef, "Open git-blame failed");
git_header_html();
my $formats_nav =
@@ -2543,25 +2756,52 @@ sub git_blame2 {
<table class="blame">
<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
HTML
- while (<$fd>) {
- /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
- my $full_rev = $1;
+ my %metainfo = ();
+ while (1) {
+ $_ = <$fd>;
+ last unless defined $_;
+ my ($full_rev, $orig_lineno, $lineno, $group_size) =
+ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+ if (!exists $metainfo{$full_rev}) {
+ $metainfo{$full_rev} = {};
+ }
+ my $meta = $metainfo{$full_rev};
+ while (<$fd>) {
+ last if (s/^\t//);
+ if (/^(\S+) (.*)$/) {
+ $meta->{$1} = $2;
+ }
+ }
+ my $data = $_;
my $rev = substr($full_rev, 0, 8);
- my $lineno = $2;
- my $data = $3;
-
- if (!defined $last_rev) {
- $last_rev = $full_rev;
- } elsif ($last_rev ne $full_rev) {
- $last_rev = $full_rev;
+ my $author = $meta->{'author'};
+ my %date = parse_date($meta->{'author-time'},
+ $meta->{'author-tz'});
+ my $date = $date{'iso-tz'};
+ if ($group_size) {
$current_color = ++$current_color % $num_colors;
}
print "<tr class=\"$rev_color[$current_color]\">\n";
- print "<td class=\"sha1\">" .
- $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)},
- esc_html($rev)) . "</td>\n";
- print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" .
- esc_html($lineno) . "</a></td>\n";
+ if ($group_size) {
+ print "<td class=\"sha1\"";
+ print " title=\"". esc_html($author) . ", $date\"";
+ print " rowspan=\"$group_size\"" if ($group_size > 1);
+ print ">";
+ print $cgi->a({-href => href(action=>"commit",
+ hash=>$full_rev,
+ file_name=>$file_name)},
+ esc_html($rev));
+ print "</td>\n";
+ }
+ my $blamed = href(action => 'blame',
+ file_name => $meta->{'filename'},
+ hash_base => $full_rev);
+ print "<td class=\"linenr\">";
+ print $cgi->a({ -href => "$blamed#l$orig_lineno",
+ -id => "l$lineno",
+ -class => "linenr" },
+ esc_html($lineno));
+ print "</td>";
print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
print "</tr>\n";
}
@@ -2675,9 +2915,9 @@ sub git_tags {
git_print_page_nav('','', $head,undef,$head);
git_print_header_div('summary', $project);
- my ($taglist) = git_get_refs_list("tags");
- if (@$taglist) {
- git_tags_body($taglist);
+ my @tagslist = git_get_tags_list();
+ if (@tagslist) {
+ git_tags_body(\@tagslist);
}
git_footer_html();
}
@@ -2688,9 +2928,9 @@ sub git_heads {
git_print_page_nav('','', $head,undef,$head);
git_print_header_div('summary', $project);
- my ($headlist) = git_get_refs_list("heads");
- if (@$headlist) {
- git_heads_body($headlist, $head);
+ my @headslist = git_get_heads_list();
+ if (@headslist) {
+ git_heads_body(\@headslist, $head);
}
git_footer_html();
}
@@ -2803,7 +3043,7 @@ sub git_blob {
$nr++;
$line = untabify($line);
printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
- $nr, $nr, $nr, esc_html($line);
+ $nr, $nr, $nr, esc_html($line, -nbsp=>1);
}
close $fd
or print "Reading blob failed.\n";
@@ -2834,7 +3074,7 @@ sub git_tree {
my $refs = git_get_references();
my $ref = format_ref_marker($refs, $hash_base);
git_header_html();
- my $base = "";
+ my $basedir = '';
my ($have_blame) = gitweb_check_feature('blame');
if (defined $hash_base && (my %co = parse_commit($hash_base))) {
my @views_nav = ();
@@ -2851,7 +3091,7 @@ sub git_tree {
# FIXME: Should be available when we have no hash base as well.
push @views_nav,
$cgi->a({-href => href(action=>"snapshot", hash=>$hash)},
- "snapshot");
+ "snapshot");
}
git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
@@ -2862,12 +3102,39 @@ sub git_tree {
print "<div class=\"title\">$hash</div>\n";
}
if (defined $file_name) {
- $base = esc_html("$file_name/");
+ $basedir = $file_name;
+ if ($basedir ne '' && substr($basedir, -1) ne '/') {
+ $basedir .= '/';
+ }
}
git_print_page_path($file_name, 'tree', $hash_base);
print "<div class=\"page_body\">\n";
print "<table cellspacing=\"0\">\n";
my $alternate = 1;
+ # '..' (top directory) link if possible
+ if (defined $hash_base &&
+ defined $file_name && $file_name =~ m![^/]+$!) {
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+
+ my $up = $file_name;
+ $up =~ s!/?[^/]+$!!;
+ undef $up unless $up;
+ # based on git_print_tree_entry
+ print '<td class="mode">' . mode_str('040000') . "</td>\n";
+ print '<td class="list">';
+ print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
+ file_name=>$up)},
+ "..");
+ print "</td>\n";
+ print "<td class=\"link\"></td>\n";
+
+ print "</tr>\n";
+ }
foreach my $line (@entries) {
my %t = parse_ls_tree_line($line, -z => 1);
@@ -2878,7 +3145,7 @@ sub git_tree {
}
$alternate ^= 1;
- git_print_tree_entry(\%t, $base, $hash_base, $have_blame);
+ git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
print "</tr>\n";
}
@@ -2930,7 +3197,7 @@ sub git_log {
my $refs = git_get_references();
my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
- open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash
+ open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd;
@@ -2969,7 +3236,7 @@ sub git_log {
"</div>\n";
print "<div class=\"log_body\">\n";
- git_print_simplified_log($co{'comment'});
+ git_print_log($co{'comment'}, -final_empty_line=> 1);
print "</div>\n";
}
git_footer_html();
@@ -2987,7 +3254,8 @@ sub git_commit {
if (!defined $parent) {
$parent = "--root";
}
- open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $parent, $hash
+ open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
+ @diff_opts, $parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
my @difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-diff-tree failed");
@@ -3092,7 +3360,8 @@ sub git_blobdiff {
if (defined $hash_base && defined $hash_parent_base) {
if (defined $file_name) {
# read raw output
- open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base,
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ $hash_parent_base, $hash_base,
"--", $file_name
or die_error(undef, "Open git-diff-tree failed");
@difftree = map { chomp; $_ } <$fd>;
@@ -3106,7 +3375,8 @@ sub git_blobdiff {
# try to find filename from $hash
# read filtered raw output
- open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ $hash_parent_base, $hash_base, "--"
or die_error(undef, "Open git-diff-tree failed");
@difftree =
# ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
@@ -3176,7 +3446,8 @@ sub git_blobdiff {
}
# open patch output
- open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, $hash_parent, $hash
+ open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts,
+ $hash_parent, $hash, "--"
or die_error(undef, "Open git-diff failed");
} else {
die_error('404 Not Found', "Missing one of the blob diff parameters")
@@ -3253,6 +3524,51 @@ sub git_commitdiff {
if (!%co) {
die_error(undef, "Unknown commit object");
}
+
+ # we need to prepare $formats_nav before any parameter munging
+ my $formats_nav;
+ if ($format eq 'html') {
+ $formats_nav =
+ $cgi->a({-href => href(action=>"commitdiff_plain",
+ hash=>$hash, hash_parent=>$hash_parent)},
+ "raw");
+
+ if (defined $hash_parent) {
+ # commitdiff with two commits given
+ my $hash_parent_short = $hash_parent;
+ if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
+ $hash_parent_short = substr($hash_parent, 0, 7);
+ }
+ $formats_nav .=
+ ' (from: ' .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$hash_parent)},
+ esc_html($hash_parent_short)) .
+ ')';
+ } elsif (!$co{'parent'}) {
+ # --root commitdiff
+ $formats_nav .= ' (initial)';
+ } elsif (scalar @{$co{'parents'}} == 1) {
+ # single parent commit
+ $formats_nav .=
+ ' (parent: ' .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$co{'parent'})},
+ esc_html(substr($co{'parent'}, 0, 7))) .
+ ')';
+ } else {
+ # merge commit
+ $formats_nav .=
+ ' (merge: ' .
+ join(' ', map {
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$_)},
+ esc_html(substr($_, 0, 7)));
+ } @{$co{'parents'}} ) .
+ ')';
+ }
+ }
+
if (!defined $hash_parent) {
$hash_parent = $co{'parent'} || '--root';
}
@@ -3262,7 +3578,8 @@ sub git_commitdiff {
my @difftree;
if ($format eq 'html') {
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
- "--patch-with-raw", "--full-index", $hash_parent, $hash
+ "--no-commit-id", "--patch-with-raw", "--full-index",
+ $hash_parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
while (chomp(my $line = <$fd>)) {
@@ -3273,7 +3590,7 @@ sub git_commitdiff {
} elsif ($format eq 'plain') {
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
- '-p', $hash_parent, $hash
+ '-p', $hash_parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
} else {
@@ -3290,19 +3607,17 @@ sub git_commitdiff {
if ($format eq 'html') {
my $refs = git_get_references();
my $ref = format_ref_marker($refs, $co{'id'});
- my $formats_nav =
- $cgi->a({-href => href(action=>"commitdiff_plain",
- hash=>$hash, hash_parent=>$hash_parent)},
- "raw");
git_header_html(undef, $expires);
git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
git_print_authorship(\%co);
print "<div class=\"page_body\">\n";
- print "<div class=\"log\">\n";
- git_print_simplified_log($co{'comment'}, 1); # skip title
- print "</div>\n"; # class="log"
+ if (@{$co{'comment'}} > 1) {
+ print "<div class=\"log\">\n";
+ git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
+ print "</div>\n"; # class="log"
+ }
} elsif ($format eq 'plain') {
my $refs = git_get_references("tags");
@@ -3434,18 +3749,8 @@ sub git_search {
die_error(undef, "Unknown commit object");
}
- my $commit_search = 1;
- my $author_search = 0;
- my $committer_search = 0;
- my $pickaxe_search = 0;
- if ($searchtext =~ s/^author\\://i) {
- $author_search = 1;
- } elsif ($searchtext =~ s/^committer\\://i) {
- $committer_search = 1;
- } elsif ($searchtext =~ s/^pickaxe\\://i) {
- $commit_search = 0;
- $pickaxe_search = 1;
-
+ $searchtype ||= 'commit';
+ if ($searchtype eq 'pickaxe') {
# pickaxe may take all resources of your box and run for several minutes
# with every query - so decide by yourself how public you make this feature
my ($have_pickaxe) = gitweb_check_feature('pickaxe');
@@ -3453,23 +3758,26 @@ sub git_search {
die_error('403 Permission denied', "Permission denied");
}
}
+
git_header_html();
git_print_page_nav('','', $hash,$co{'tree'},$hash);
git_print_header_div('commit', esc_html($co{'title'}), $hash);
print "<table cellspacing=\"0\">\n";
my $alternate = 1;
- if ($commit_search) {
+ if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
$/ = "\0";
- open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next;
+ open my $fd, "-|", git_cmd(), "rev-list",
+ "--header", "--parents", $hash, "--"
+ or next;
while (my $commit_text = <$fd>) {
if (!grep m/$searchtext/i, $commit_text) {
next;
}
- if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+ if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
next;
}
- if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+ if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
next;
}
my @commit_lines = split "\n", $commit_text;
@@ -3511,7 +3819,7 @@ sub git_search {
close $fd;
}
- if ($pickaxe_search) {
+ if ($searchtype eq 'pickaxe') {
$/ = "\n";
my $git_command = git_cmd_str();
open my $fd, "-|", "$git_command rev-list $hash | " .
@@ -3571,6 +3879,31 @@ sub git_search {
git_footer_html();
}
+sub git_search_help {
+ git_header_html();
+ git_print_page_nav('','', $hash,$hash,$hash);
+ print <<EOT;
+<dl>
+<dt><b>commit</b></dt>
+<dd>The commit messages and authorship information will be scanned for the given string.</dd>
+<dt><b>author</b></dt>
+<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
+<dt><b>committer</b></dt>
+<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
+EOT
+ my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+ if ($have_pickaxe) {
+ print <<EOT;
+<dt><b>pickaxe</b></dt>
+<dd>All commits that caused the string to appear or disappear from any file (changes that
+added, removed or "modified" the string) will be listed. This search can take a while and
+takes a lot of strain on the server, so please use it wisely.</dd>
+EOT
+ }
+ print "</dl>\n";
+ git_footer_html();
+}
+
sub git_shortlog {
my $head = git_get_head_hash($project);
if (!defined $hash) {
@@ -3582,7 +3915,7 @@ sub git_shortlog {
my $refs = git_get_references();
my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
- open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash
+ open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd;
@@ -3610,7 +3943,8 @@ sub git_shortlog {
sub git_rss {
# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
- open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project)
+ open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
+ git_get_head_hash($project), "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-rev-list failed");
@@ -3634,7 +3968,7 @@ XML
}
my %cd = parse_date($co{'committer_epoch'});
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
- $co{'parent'}, $co{'id'}
+ $co{'parent'}, $co{'id'}, "--"
or next;
my @difftree = map { chomp; $_ } <$fd>;
close $fd
@@ -3679,7 +4013,7 @@ sub git_opml {
<?xml version="1.0" encoding="utf-8"?>
<opml version="1.0">
<head>
- <title>$site_name Git OPML Export</title>
+ <title>$site_name OPML Export</title>
</head>
<body>
<outline text="git RSS feeds">
diff --git a/http-push.c b/http-push.c
index 670ff007b..ecefdfd4f 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1864,7 +1864,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
static struct ref *local_refs, **local_tail;
static struct ref *remote_refs, **remote_tail;
-static int one_local_ref(const char *refname, const unsigned char *sha1)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct ref *ref;
int len = strlen(refname) + 1;
@@ -1913,7 +1913,7 @@ static void one_remote_ref(char *refname)
static void get_local_heads(void)
{
local_tail = &local_refs;
- for_each_ref(one_local_ref);
+ for_each_ref(one_local_ref, NULL);
}
static void get_dav_remote_heads(void)
diff --git a/imap-send.c b/imap-send.c
index 16804ab28..a6a65680e 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -272,7 +272,7 @@ buffer_gets( buffer_t * b, char **s )
n = b->bytes - start;
if (n)
- memcpy( b->buf, b->buf + start, n );
+ memmove(b->buf, b->buf + start, n);
b->offset -= start;
b->bytes = n;
start = 0;
diff --git a/index-pack.c b/index-pack.c
index 80bc6cb45..042aea884 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -6,84 +6,173 @@
#include "commit.h"
#include "tag.h"
#include "tree.h"
+#include <sys/time.h>
+#include <signal.h>
static const char index_pack_usage[] =
-"git-index-pack [-o index-file] pack-file";
+"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
struct object_entry
{
unsigned long offset;
+ unsigned long size;
+ unsigned int hdr_size;
enum object_type type;
enum object_type real_type;
unsigned char sha1[20];
};
+union delta_base {
+ unsigned char sha1[20];
+ unsigned long offset;
+};
+
+/*
+ * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
+ * to memcmp() only the first 20 bytes.
+ */
+#define UNION_BASE_SZ 20
+
struct delta_entry
{
- struct object_entry *obj;
- unsigned char base_sha1[20];
+ union delta_base base;
+ int obj_no;
};
-static const char *pack_name;
-static unsigned char *pack_base;
-static unsigned long pack_size;
static struct object_entry *objects;
static struct delta_entry *deltas;
static int nr_objects;
static int nr_deltas;
+static int nr_resolved_deltas;
+
+static int from_stdin;
+static int verbose;
+
+static volatile sig_atomic_t progress_update;
-static void open_pack_file(void)
+static void progress_interval(int signum)
{
- int fd;
- struct stat st;
+ progress_update = 1;
+}
- fd = open(pack_name, O_RDONLY);
- if (fd < 0)
- die("cannot open packfile '%s': %s", pack_name,
- strerror(errno));
- if (fstat(fd, &st)) {
- int err = errno;
- close(fd);
- die("cannot fstat packfile '%s': %s", pack_name,
- strerror(err));
+static void setup_progress_signal(void)
+{
+ struct sigaction sa;
+ struct itimerval v;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = progress_interval;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGALRM, &sa, NULL);
+
+ v.it_interval.tv_sec = 1;
+ v.it_interval.tv_usec = 0;
+ v.it_value = v.it_interval;
+ setitimer(ITIMER_REAL, &v, NULL);
+
+}
+
+static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
+{
+ unsigned percent = n * 100 / total;
+ if (percent != last_pc || progress_update) {
+ fprintf(stderr, "%4u%% (%u/%u) done\r", percent, n, total);
+ progress_update = 0;
}
- pack_size = st.st_size;
- pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (pack_base == MAP_FAILED) {
- int err = errno;
- close(fd);
- die("cannot mmap packfile '%s': %s", pack_name,
- strerror(err));
+ return percent;
+}
+
+/* We always read in 4kB chunks. */
+static unsigned char input_buffer[4096];
+static unsigned long input_offset, input_len, consumed_bytes;
+static SHA_CTX input_ctx;
+static int input_fd, output_fd, mmap_fd;
+
+/* Discard current buffer used content. */
+static void flush()
+{
+ if (input_offset) {
+ if (output_fd >= 0)
+ write_or_die(output_fd, input_buffer, input_offset);
+ SHA1_Update(&input_ctx, input_buffer, input_offset);
+ memcpy(input_buffer, input_buffer + input_offset, input_len);
+ input_offset = 0;
}
- close(fd);
}
-static void parse_pack_header(void)
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
{
- const struct pack_header *hdr;
- unsigned char sha1[20];
- SHA_CTX ctx;
+ if (min <= input_len)
+ return input_buffer + input_offset;
+ if (min > sizeof(input_buffer))
+ die("cannot fill %d bytes", min);
+ flush();
+ do {
+ int ret = xread(input_fd, input_buffer + input_len,
+ sizeof(input_buffer) - input_len);
+ if (ret <= 0) {
+ if (!ret)
+ die("early EOF");
+ die("read error on input: %s", strerror(errno));
+ }
+ input_len += ret;
+ } while (input_len < min);
+ return input_buffer;
+}
+
+static void use(int bytes)
+{
+ if (bytes > input_len)
+ die("used more bytes than were available");
+ input_len -= bytes;
+ input_offset += bytes;
+ consumed_bytes += bytes;
+}
+
+static const char *open_pack_file(const char *pack_name)
+{
+ if (from_stdin) {
+ input_fd = 0;
+ if (!pack_name) {
+ static char tmpfile[PATH_MAX];
+ snprintf(tmpfile, sizeof(tmpfile),
+ "%s/pack_XXXXXX", get_object_directory());
+ output_fd = mkstemp(tmpfile);
+ pack_name = xstrdup(tmpfile);
+ } else
+ output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (output_fd < 0)
+ die("unable to create %s: %s\n", pack_name, strerror(errno));
+ mmap_fd = output_fd;
+ } else {
+ input_fd = open(pack_name, O_RDONLY);
+ if (input_fd < 0)
+ die("cannot open packfile '%s': %s",
+ pack_name, strerror(errno));
+ output_fd = -1;
+ mmap_fd = input_fd;
+ }
+ SHA1_Init(&input_ctx);
+ return pack_name;
+}
- /* Ensure there are enough bytes for the header and final SHA1 */
- if (pack_size < sizeof(struct pack_header) + 20)
- die("packfile '%s' is too small", pack_name);
+static void parse_pack_header(void)
+{
+ struct pack_header *hdr = fill(sizeof(struct pack_header));
/* Header consistency check */
- hdr = (void *)pack_base;
if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
- die("packfile '%s' signature mismatch", pack_name);
+ die("pack signature mismatch");
if (!pack_version_ok(hdr->hdr_version))
- die("packfile '%s' version %d unsupported",
- pack_name, ntohl(hdr->hdr_version));
+ die("pack version %d unsupported", ntohl(hdr->hdr_version));
nr_objects = ntohl(hdr->hdr_entries);
-
- /* Check packfile integrity */
- SHA1_Init(&ctx);
- SHA1_Update(&ctx, pack_base, pack_size - 20);
- SHA1_Final(sha1, &ctx);
- if (hashcmp(sha1, pack_base + pack_size - 20))
- die("packfile '%s' SHA1 mismatch", pack_name);
+ use(sizeof(struct pack_header));
}
static void bad_object(unsigned long offset, const char *format,
@@ -97,90 +186,124 @@ static void bad_object(unsigned long offset, const char *format, ...)
va_start(params, format);
vsnprintf(buf, sizeof(buf), format, params);
va_end(params);
- die("packfile '%s': bad object at offset %lu: %s",
- pack_name, offset, buf);
+ die("pack has bad object at offset %lu: %s", offset, buf);
}
-static void *unpack_entry_data(unsigned long offset,
- unsigned long *current_pos, unsigned long size)
+static void *unpack_entry_data(unsigned long offset, unsigned long size)
{
- unsigned long pack_limit = pack_size - 20;
- unsigned long pos = *current_pos;
z_stream stream;
void *buf = xmalloc(size);
memset(&stream, 0, sizeof(stream));
stream.next_out = buf;
stream.avail_out = size;
- stream.next_in = pack_base + pos;
- stream.avail_in = pack_limit - pos;
+ stream.next_in = fill(1);
+ stream.avail_in = input_len;
inflateInit(&stream);
for (;;) {
int ret = inflate(&stream, 0);
- if (ret == Z_STREAM_END)
+ use(input_len - stream.avail_in);
+ if (stream.total_out == size && ret == Z_STREAM_END)
break;
if (ret != Z_OK)
bad_object(offset, "inflate returned %d", ret);
+ stream.next_in = fill(1);
+ stream.avail_in = input_len;
}
inflateEnd(&stream);
- if (stream.total_out != size)
- bad_object(offset, "size mismatch (expected %lu, got %lu)",
- size, stream.total_out);
- *current_pos = pack_limit - stream.avail_in;
return buf;
}
-static void *unpack_raw_entry(unsigned long offset,
- enum object_type *obj_type,
- unsigned long *obj_size,
- unsigned char *delta_base,
- unsigned long *next_obj_offset)
+static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
{
- unsigned long pack_limit = pack_size - 20;
- unsigned long pos = offset;
- unsigned char c;
- unsigned long size;
+ unsigned char *p, c;
+ unsigned long size, base_offset;
unsigned shift;
- enum object_type type;
- void *data;
- c = pack_base[pos++];
- type = (c >> 4) & 7;
+ obj->offset = consumed_bytes;
+
+ p = fill(1);
+ c = *p;
+ use(1);
+ obj->type = (c >> 4) & 7;
size = (c & 15);
shift = 4;
while (c & 0x80) {
- if (pos >= pack_limit)
- bad_object(offset, "object extends past end of pack");
- c = pack_base[pos++];
+ p = fill(1);
+ c = *p;
+ use(1);
size += (c & 0x7fUL) << shift;
shift += 7;
}
+ obj->size = size;
- switch (type) {
- case OBJ_DELTA:
- if (pos + 20 >= pack_limit)
- bad_object(offset, "object extends past end of pack");
- hashcpy(delta_base, pack_base + pos);
- pos += 20;
- /* fallthru */
+ switch (obj->type) {
+ case OBJ_REF_DELTA:
+ hashcpy(delta_base->sha1, fill(20));
+ use(20);
+ break;
+ case OBJ_OFS_DELTA:
+ memset(delta_base, 0, sizeof(*delta_base));
+ p = fill(1);
+ c = *p;
+ use(1);
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || base_offset & ~(~0UL >> 7))
+ bad_object(obj->offset, "offset value overflow for delta base object");
+ p = fill(1);
+ c = *p;
+ use(1);
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ delta_base->offset = obj->offset - base_offset;
+ if (delta_base->offset >= obj->offset)
+ bad_object(obj->offset, "delta base offset is out of bound");
+ break;
case OBJ_COMMIT:
case OBJ_TREE:
case OBJ_BLOB:
case OBJ_TAG:
- data = unpack_entry_data(offset, &pos, size);
break;
default:
- bad_object(offset, "bad object type %d", type);
+ bad_object(obj->offset, "bad object type %d", obj->type);
}
+ obj->hdr_size = consumed_bytes - obj->offset;
- *obj_type = type;
- *obj_size = size;
- *next_obj_offset = pos;
+ return unpack_entry_data(obj->offset, obj->size);
+}
+
+static void *get_data_from_pack(struct object_entry *obj)
+{
+ unsigned long from = obj[0].offset + obj[0].hdr_size;
+ unsigned long len = obj[1].offset - from;
+ unsigned pg_offset = from % getpagesize();
+ unsigned char *map, *data;
+ z_stream stream;
+ int st;
+
+ map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
+ mmap_fd, from - pg_offset);
+ if (map == MAP_FAILED)
+ die("cannot mmap pack file: %s", strerror(errno));
+ data = xmalloc(obj->size);
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = data;
+ stream.avail_out = obj->size;
+ stream.next_in = map + pg_offset;
+ stream.avail_in = len;
+ inflateInit(&stream);
+ while ((st = inflate(&stream, Z_FINISH)) == Z_OK);
+ inflateEnd(&stream);
+ if (st != Z_STREAM_END || stream.total_out != obj->size)
+ die("serious inflate inconsistency");
+ munmap(map, len + pg_offset);
return data;
}
-static int find_delta(const unsigned char *base_sha1)
+static int find_delta(const union delta_base *base)
{
int first = 0, last = nr_deltas;
@@ -189,7 +312,7 @@ static int find_delta(const unsigned char *base_sha1)
struct delta_entry *delta = &deltas[next];
int cmp;
- cmp = hashcmp(base_sha1, delta->base_sha1);
+ cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
if (!cmp)
return next;
if (cmp < 0) {
@@ -201,18 +324,18 @@ static int find_delta(const unsigned char *base_sha1)
return -first-1;
}
-static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
- int *first_index, int *last_index)
+static int find_delta_children(const union delta_base *base,
+ int *first_index, int *last_index)
{
- int first = find_delta(base_sha1);
+ int first = find_delta(base);
int last = first;
int end = nr_deltas - 1;
if (first < 0)
return -1;
- while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1))
+ while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
--first;
- while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1))
+ while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
++last;
*first_index = first;
*last_index = last;
@@ -244,33 +367,46 @@ static void sha1_object(const void *data, unsigned long size,
SHA1_Final(sha1, &ctx);
}
-static void resolve_delta(struct delta_entry *delta, void *base_data,
+static void resolve_delta(struct object_entry *delta_obj, void *base_data,
unsigned long base_size, enum object_type type)
{
- struct object_entry *obj = delta->obj;
void *delta_data;
unsigned long delta_size;
void *result;
unsigned long result_size;
- enum object_type delta_type;
- unsigned char base_sha1[20];
- unsigned long next_obj_offset;
+ union delta_base delta_base;
int j, first, last;
- obj->real_type = type;
- delta_data = unpack_raw_entry(obj->offset, &delta_type,
- &delta_size, base_sha1,
- &next_obj_offset);
+ delta_obj->real_type = type;
+ delta_data = get_data_from_pack(delta_obj);
+ delta_size = delta_obj->size;
result = patch_delta(base_data, base_size, delta_data, delta_size,
&result_size);
free(delta_data);
if (!result)
- bad_object(obj->offset, "failed to apply delta");
- sha1_object(result, result_size, type, obj->sha1);
- if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
- for (j = first; j <= last; j++)
- resolve_delta(&deltas[j], result, result_size, type);
+ bad_object(delta_obj->offset, "failed to apply delta");
+ sha1_object(result, result_size, type, delta_obj->sha1);
+ nr_resolved_deltas++;
+
+ hashcpy(delta_base.sha1, delta_obj->sha1);
+ if (!find_delta_children(&delta_base, &first, &last)) {
+ for (j = first; j <= last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_REF_DELTA)
+ resolve_delta(child, result, result_size, type);
+ }
+ }
+
+ memset(&delta_base, 0, sizeof(delta_base));
+ delta_base.offset = delta_obj->offset;
+ if (!find_delta_children(&delta_base, &first, &last)) {
+ for (j = first; j <= last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_OFS_DELTA)
+ resolve_delta(child, result, result_size, type);
+ }
}
+
free(result);
}
@@ -278,41 +414,60 @@ static int compare_delta_entry(const void *a, const void *b)
{
const struct delta_entry *delta_a = a;
const struct delta_entry *delta_b = b;
- return hashcmp(delta_a->base_sha1, delta_b->base_sha1);
+ return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
}
-static void parse_pack_objects(void)
+/* Parse all objects and return the pack content SHA1 hash */
+static void parse_pack_objects(unsigned char *sha1)
{
- int i;
- unsigned long offset = sizeof(struct pack_header);
- unsigned char base_sha1[20];
+ int i, percent = -1;
+ struct delta_entry *delta = deltas;
void *data;
- unsigned long data_size;
+ struct stat st;
/*
* First pass:
* - find locations of all objects;
* - calculate SHA1 of all non-delta objects;
- * - remember base SHA1 for all deltas.
+ * - remember base (SHA1 or offset) for all deltas.
*/
+ if (verbose)
+ fprintf(stderr, "Indexing %d objects.\n", nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- obj->offset = offset;
- data = unpack_raw_entry(offset, &obj->type, &data_size,
- base_sha1, &offset);
+ data = unpack_raw_entry(obj, &delta->base);
obj->real_type = obj->type;
- if (obj->type == OBJ_DELTA) {
- struct delta_entry *delta = &deltas[nr_deltas++];
- delta->obj = obj;
- hashcpy(delta->base_sha1, base_sha1);
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ nr_deltas++;
+ delta->obj_no = i;
+ delta++;
} else
- sha1_object(data, data_size, obj->type, obj->sha1);
+ sha1_object(data, obj->size, obj->type, obj->sha1);
free(data);
+ if (verbose)
+ percent = display_progress(i+1, nr_objects, percent);
}
- if (offset != pack_size - 20)
- die("packfile '%s' has junk at the end", pack_name);
-
- /* Sort deltas by base SHA1 for fast searching */
+ objects[i].offset = consumed_bytes;
+ if (verbose)
+ fputc('\n', stderr);
+
+ /* Check pack integrity */
+ flush();
+ SHA1_Final(sha1, &input_ctx);
+ if (hashcmp(fill(20), sha1))
+ die("pack is corrupted (SHA1 mismatch)");
+ use(20);
+
+ /* If input_fd is a file, we should have reached its end now. */
+ if (fstat(input_fd, &st))
+ die("cannot fstat packfile: %s", strerror(errno));
+ if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
+ die("pack has junk at the end");
+
+ if (!nr_deltas)
+ return;
+
+ /* Sort deltas by base SHA1/offset for fast searching */
qsort(deltas, nr_deltas, sizeof(struct delta_entry),
compare_delta_entry);
@@ -324,26 +479,189 @@ static void parse_pack_objects(void)
* recursively checking if the resulting object is used as a base
* for some more deltas.
*/
+ if (verbose)
+ fprintf(stderr, "Resolving %d deltas.\n", nr_deltas);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- int j, first, last;
+ union delta_base base;
+ int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
- if (obj->type == OBJ_DELTA)
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
continue;
- if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+ hashcpy(base.sha1, obj->sha1);
+ ref = !find_delta_children(&base, &ref_first, &ref_last);
+ memset(&base, 0, sizeof(base));
+ base.offset = obj->offset;
+ ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
+ if (!ref && !ofs)
continue;
- data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
- base_sha1, &offset);
- for (j = first; j <= last; j++)
- resolve_delta(&deltas[j], data, data_size, obj->type);
+ data = get_data_from_pack(obj);
+ if (ref)
+ for (j = ref_first; j <= ref_last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_REF_DELTA)
+ resolve_delta(child, data,
+ obj->size, obj->type);
+ }
+ if (ofs)
+ for (j = ofs_first; j <= ofs_last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_OFS_DELTA)
+ resolve_delta(child, data,
+ obj->size, obj->type);
+ }
free(data);
+ if (verbose)
+ percent = display_progress(nr_resolved_deltas,
+ nr_deltas, percent);
}
+ if (verbose && nr_resolved_deltas == nr_deltas)
+ fputc('\n', stderr);
+}
+
+static int write_compressed(int fd, void *in, unsigned int size)
+{
+ z_stream stream;
+ unsigned long maxsize;
+ void *out;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, zlib_compression_level);
+ maxsize = deflateBound(&stream, size);
+ out = xmalloc(maxsize);
+
+ /* Compress it */
+ stream.next_in = in;
+ stream.avail_in = size;
+ stream.next_out = out;
+ stream.avail_out = maxsize;
+ while (deflate(&stream, Z_FINISH) == Z_OK);
+ deflateEnd(&stream);
+
+ size = stream.total_out;
+ write_or_die(fd, out, size);
+ free(out);
+ return size;
+}
+
+static void append_obj_to_pack(void *buf,
+ unsigned long size, enum object_type type)
+{
+ struct object_entry *obj = &objects[nr_objects++];
+ unsigned char header[10];
+ unsigned long s = size;
+ int n = 0;
+ unsigned char c = (type << 4) | (s & 15);
+ s >>= 4;
+ while (s) {
+ header[n++] = c | 0x80;
+ c = s & 0x7f;
+ s >>= 7;
+ }
+ header[n++] = c;
+ write_or_die(output_fd, header, n);
+ obj[1].offset = obj[0].offset + n;
+ obj[1].offset += write_compressed(output_fd, buf, size);
+ sha1_object(buf, size, type, obj->sha1);
+}
+
+static int delta_pos_compare(const void *_a, const void *_b)
+{
+ struct delta_entry *a = *(struct delta_entry **)_a;
+ struct delta_entry *b = *(struct delta_entry **)_b;
+ return a->obj_no - b->obj_no;
+}
+
+static void fix_unresolved_deltas(int nr_unresolved)
+{
+ struct delta_entry **sorted_by_pos;
+ int i, n = 0, percent = -1;
- /* Check for unresolved deltas */
+ /*
+ * Since many unresolved deltas may well be themselves base objects
+ * for more unresolved deltas, we really want to include the
+ * smallest number of base objects that would cover as much delta
+ * as possible by picking the
+ * trunc deltas first, allowing for other deltas to resolve without
+ * additional base objects. Since most base objects are to be found
+ * before deltas depending on them, a good heuristic is to start
+ * resolving deltas in the same order as their position in the pack.
+ */
+ sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
for (i = 0; i < nr_deltas; i++) {
- if (deltas[i].obj->real_type == OBJ_DELTA)
- die("packfile '%s' has unresolved deltas", pack_name);
+ if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
+ continue;
+ sorted_by_pos[n++] = &deltas[i];
+ }
+ qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+
+ for (i = 0; i < n; i++) {
+ struct delta_entry *d = sorted_by_pos[i];
+ void *data;
+ unsigned long size;
+ char type[10];
+ enum object_type obj_type;
+ int j, first, last;
+
+ if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
+ continue;
+ data = read_sha1_file(d->base.sha1, type, &size);
+ if (!data)
+ continue;
+ if (!strcmp(type, blob_type)) obj_type = OBJ_BLOB;
+ else if (!strcmp(type, tree_type)) obj_type = OBJ_TREE;
+ else if (!strcmp(type, commit_type)) obj_type = OBJ_COMMIT;
+ else if (!strcmp(type, tag_type)) obj_type = OBJ_TAG;
+ else die("base object %s is of type '%s'",
+ sha1_to_hex(d->base.sha1), type);
+
+ find_delta_children(&d->base, &first, &last);
+ for (j = first; j <= last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_REF_DELTA)
+ resolve_delta(child, data, size, obj_type);
+ }
+
+ append_obj_to_pack(data, size, obj_type);
+ free(data);
+ if (verbose)
+ percent = display_progress(nr_resolved_deltas,
+ nr_deltas, percent);
}
+ free(sorted_by_pos);
+ if (verbose)
+ fputc('\n', stderr);
+}
+
+static void readjust_pack_header_and_sha1(unsigned char *sha1)
+{
+ struct pack_header hdr;
+ SHA_CTX ctx;
+ int size;
+
+ /* Rewrite pack header with updated object number */
+ if (lseek(output_fd, 0, SEEK_SET) != 0)
+ die("cannot seek back: %s", strerror(errno));
+ if (xread(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+ die("cannot read pack header back: %s", strerror(errno));
+ hdr.hdr_entries = htonl(nr_objects);
+ if (lseek(output_fd, 0, SEEK_SET) != 0)
+ die("cannot seek back: %s", strerror(errno));
+ write_or_die(output_fd, &hdr, sizeof(hdr));
+ if (lseek(output_fd, 0, SEEK_SET) != 0)
+ die("cannot seek back: %s", strerror(errno));
+
+ /* Recompute and store the new pack's SHA1 */
+ SHA1_Init(&ctx);
+ do {
+ unsigned char *buf[4096];
+ size = xread(output_fd, buf, sizeof(buf));
+ if (size < 0)
+ die("cannot read pack data back: %s", strerror(errno));
+ SHA1_Update(&ctx, buf, size);
+ } while (size > 0);
+ SHA1_Final(sha1, &ctx);
+ write_or_die(output_fd, sha1, 20);
}
static int sha1_compare(const void *_a, const void *_b)
@@ -353,12 +671,16 @@ static int sha1_compare(const void *_a, const void *_b)
return hashcmp(a->sha1, b->sha1);
}
-static void write_index_file(const char *index_name, unsigned char *sha1)
+/*
+ * On entry *sha1 contains the pack content SHA1 hash, on exit it is
+ * the SHA1 hash of sorted object names.
+ */
+static const char *write_index_file(const char *index_name, unsigned char *sha1)
{
struct sha1file *f;
struct object_entry **sorted_by_sha, **list, **last;
unsigned int array[256];
- int i;
+ int i, fd;
SHA_CTX ctx;
if (nr_objects) {
@@ -375,8 +697,19 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
else
sorted_by_sha = list = last = NULL;
- unlink(index_name);
- f = sha1create("%s", index_name);
+ if (!index_name) {
+ static char tmpfile[PATH_MAX];
+ snprintf(tmpfile, sizeof(tmpfile),
+ "%s/index_XXXXXX", get_object_directory());
+ fd = mkstemp(tmpfile);
+ index_name = xstrdup(tmpfile);
+ } else {
+ unlink(index_name);
+ fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ }
+ if (fd < 0)
+ die("unable to create %s: %s", index_name, strerror(errno));
+ f = sha1fd(fd, index_name);
/*
* Write the first-level table (the list is sorted,
@@ -412,24 +745,132 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
sha1write(f, obj->sha1, 20);
SHA1_Update(&ctx, obj->sha1, 20);
}
- sha1write(f, pack_base + pack_size - 20, 20);
+ sha1write(f, sha1, 20);
sha1close(f, NULL, 1);
free(sorted_by_sha);
SHA1_Final(sha1, &ctx);
+ return index_name;
+}
+
+static void final(const char *final_pack_name, const char *curr_pack_name,
+ const char *final_index_name, const char *curr_index_name,
+ const char *keep_name, const char *keep_msg,
+ unsigned char *sha1)
+{
+ char *report = "pack";
+ char name[PATH_MAX];
+ int err;
+
+ if (!from_stdin) {
+ close(input_fd);
+ } else {
+ err = close(output_fd);
+ if (err)
+ die("error while closing pack file: %s", strerror(errno));
+ chmod(curr_pack_name, 0444);
+ }
+
+ if (keep_msg) {
+ int keep_fd, keep_msg_len = strlen(keep_msg);
+ if (!keep_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
+ get_object_directory(), sha1_to_hex(sha1));
+ keep_name = name;
+ }
+ keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ if (keep_fd < 0) {
+ if (errno != EEXIST)
+ die("cannot write keep file");
+ } else {
+ if (keep_msg_len > 0) {
+ write_or_die(keep_fd, keep_msg, keep_msg_len);
+ write_or_die(keep_fd, "\n", 1);
+ }
+ close(keep_fd);
+ report = "keep";
+ }
+ }
+
+ if (final_pack_name != curr_pack_name) {
+ if (!final_pack_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+ get_object_directory(), sha1_to_hex(sha1));
+ final_pack_name = name;
+ }
+ if (move_temp_to_file(curr_pack_name, final_pack_name))
+ die("cannot store pack file");
+ }
+
+ chmod(curr_index_name, 0444);
+ if (final_index_name != curr_index_name) {
+ if (!final_index_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+ get_object_directory(), sha1_to_hex(sha1));
+ final_index_name = name;
+ }
+ if (move_temp_to_file(curr_index_name, final_index_name))
+ die("cannot store index file");
+ }
+
+ if (!from_stdin) {
+ printf("%s\n", sha1_to_hex(sha1));
+ } else {
+ char buf[48];
+ int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+ report, sha1_to_hex(sha1));
+ xwrite(1, buf, len);
+
+ /*
+ * Let's just mimic git-unpack-objects here and write
+ * the last part of the input buffer to stdout.
+ */
+ while (input_len) {
+ err = xwrite(1, input_buffer + input_offset, input_len);
+ if (err <= 0)
+ break;
+ input_len -= err;
+ input_offset += err;
+ }
+ }
}
int main(int argc, char **argv)
{
- int i;
- char *index_name = NULL;
- char *index_name_buf = NULL;
+ int i, fix_thin_pack = 0;
+ const char *curr_pack, *pack_name = NULL;
+ const char *curr_index, *index_name = NULL;
+ const char *keep_name = NULL, *keep_msg = NULL;
+ char *index_name_buf = NULL, *keep_name_buf = NULL;
unsigned char sha1[20];
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg == '-') {
- if (!strcmp(arg, "-o")) {
+ if (!strcmp(arg, "--stdin")) {
+ from_stdin = 1;
+ } else if (!strcmp(arg, "--fix-thin")) {
+ fix_thin_pack = 1;
+ } else if (!strcmp(arg, "--keep")) {
+ keep_msg = "";
+ } else if (!strncmp(arg, "--keep=", 7)) {
+ keep_msg = arg + 7;
+ } else if (!strncmp(arg, "--pack_header=", 14)) {
+ struct pack_header *hdr;
+ char *c;
+
+ hdr = (struct pack_header *)input_buffer;
+ hdr->hdr_signature = htonl(PACK_SIGNATURE);
+ hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+ if (*c != ',')
+ die("bad %s", arg);
+ hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+ if (*c)
+ die("bad %s", arg);
+ input_len = sizeof(*hdr);
+ } else if (!strcmp(arg, "-v")) {
+ verbose = 1;
+ } else if (!strcmp(arg, "-o")) {
if (index_name || (i+1) >= argc)
usage(index_pack_usage);
index_name = argv[++i];
@@ -443,9 +884,11 @@ int main(int argc, char **argv)
pack_name = arg;
}
- if (!pack_name)
+ if (!pack_name && !from_stdin)
usage(index_pack_usage);
- if (!index_name) {
+ if (fix_thin_pack && !from_stdin)
+ die("--fix-thin cannot be used without --stdin");
+ if (!index_name && pack_name) {
int len = strlen(pack_name);
if (!has_extension(pack_name, ".pack"))
die("packfile name '%s' does not end with '.pack'",
@@ -455,18 +898,55 @@ int main(int argc, char **argv)
strcpy(index_name_buf + len - 5, ".idx");
index_name = index_name_buf;
}
+ if (keep_msg && !keep_name && pack_name) {
+ int len = strlen(pack_name);
+ if (!has_extension(pack_name, ".pack"))
+ die("packfile name '%s' does not end with '.pack'",
+ pack_name);
+ keep_name_buf = xmalloc(len);
+ memcpy(keep_name_buf, pack_name, len - 5);
+ strcpy(keep_name_buf + len - 5, ".keep");
+ keep_name = keep_name_buf;
+ }
- open_pack_file();
+ curr_pack = open_pack_file(pack_name);
parse_pack_header();
- objects = xcalloc(nr_objects, sizeof(struct object_entry));
- deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
- parse_pack_objects();
+ objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
+ deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+ if (verbose)
+ setup_progress_signal();
+ parse_pack_objects(sha1);
+ if (nr_deltas != nr_resolved_deltas) {
+ if (fix_thin_pack) {
+ int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_objects_initial = nr_objects;
+ if (nr_unresolved <= 0)
+ die("confusion beyond insanity");
+ objects = xrealloc(objects,
+ (nr_objects + nr_unresolved + 1)
+ * sizeof(*objects));
+ fix_unresolved_deltas(nr_unresolved);
+ if (verbose)
+ fprintf(stderr, "%d objects were added to complete this thin pack.\n",
+ nr_objects - nr_objects_initial);
+ readjust_pack_header_and_sha1(sha1);
+ }
+ if (nr_deltas != nr_resolved_deltas)
+ die("pack has %d unresolved deltas",
+ nr_deltas - nr_resolved_deltas);
+ } else {
+ /* Flush remaining pack final 20-byte SHA1. */
+ flush();
+ }
free(deltas);
- write_index_file(index_name, sha1);
+ curr_index = write_index_file(index_name, sha1);
+ final(pack_name, curr_pack,
+ index_name, curr_index,
+ keep_name, keep_msg,
+ sha1);
free(objects);
free(index_name_buf);
-
- printf("%s\n", sha1_to_hex(sha1));
+ free(keep_name_buf);
return 0;
}
diff --git a/log-tree.c b/log-tree.c
index fbe139920..8787df5cc 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -252,26 +252,6 @@ int log_tree_diff_flush(struct rev_info *opt)
return 1;
}
-static int diff_root_tree(struct rev_info *opt,
- const unsigned char *new, const char *base)
-{
- int retval;
- void *tree;
- struct tree_desc empty, real;
-
- tree = read_object_with_reference(new, tree_type, &real.size, NULL);
- if (!tree)
- die("unable to read root tree (%s)", sha1_to_hex(new));
- real.buf = tree;
-
- empty.buf = "";
- empty.size = 0;
- retval = diff_tree(&empty, &real, base, &opt->diffopt);
- free(tree);
- log_tree_diff_flush(opt);
- return retval;
-}
-
static int do_diff_combined(struct rev_info *opt, struct commit *commit)
{
unsigned const char *sha1 = commit->object.sha1;
@@ -297,8 +277,10 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
/* Root commit? */
parents = commit->parents;
if (!parents) {
- if (opt->show_root_diff)
- diff_root_tree(opt, sha1, "");
+ if (opt->show_root_diff) {
+ diff_root_tree_sha1(sha1, "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+ }
return !opt->loginfo;
}
diff --git a/merge-recursive.c b/merge-recursive.c
index 2ba43ae84..cd2cc77bf 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -98,7 +98,7 @@ static void output_commit_title(struct commit *commit)
if (commit->util)
printf("virtual %s\n", (char *)commit->util);
else {
- printf("%s ", sha1_to_hex(commit->object.sha1));
+ printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
if (parse_commit(commit) != 0)
printf("(bad commit)\n");
else {
@@ -427,8 +427,9 @@ static struct path_list *get_renames(struct tree *tree,
return renames;
}
-int update_stages(const char *path, struct diff_filespec *o,
- struct diff_filespec *a, struct diff_filespec *b, int clear)
+static int update_stages(const char *path, struct diff_filespec *o,
+ struct diff_filespec *a, struct diff_filespec *b,
+ int clear)
{
int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
if (clear)
@@ -468,10 +469,10 @@ static int remove_path(const char *name)
return ret;
}
-int remove_file(int clean, const char *path)
+static int remove_file(int clean, const char *path, int no_wd)
{
int update_cache = index_only || clean;
- int update_working_directory = !index_only;
+ int update_working_directory = !index_only && !no_wd;
if (update_cache) {
if (!cache_dirty)
@@ -480,8 +481,7 @@ int remove_file(int clean, const char *path)
if (remove_file_from_cache(path))
return -1;
}
- if (update_working_directory)
- {
+ if (update_working_directory) {
unlink(path);
if (errno != ENOENT || errno != EISDIR)
return -1;
@@ -537,11 +537,11 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
}
}
-void update_file_flags(const unsigned char *sha,
- unsigned mode,
- const char *path,
- int update_cache,
- int update_wd)
+static void update_file_flags(const unsigned char *sha,
+ unsigned mode,
+ const char *path,
+ int update_cache,
+ int update_wd)
{
if (index_only)
update_wd = 0;
@@ -586,10 +586,10 @@ void update_file_flags(const unsigned char *sha,
add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
}
-void update_file(int clean,
- const unsigned char *sha,
- unsigned mode,
- const char *path)
+static void update_file(int clean,
+ const unsigned char *sha,
+ unsigned mode,
+ const char *path)
{
update_file_flags(sha, mode, path, index_only || clean, !index_only);
}
@@ -724,13 +724,13 @@ static void conflict_rename_rename(struct rename *ren1,
dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
output("%s is a directory in %s adding as %s instead",
ren1_dst, branch2, dst_name1);
- remove_file(0, ren1_dst);
+ remove_file(0, ren1_dst, 0);
}
if (path_list_has_path(&current_directory_set, ren2_dst)) {
dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
output("%s is a directory in %s adding as %s instead",
ren2_dst, branch1, dst_name2);
- remove_file(0, ren2_dst);
+ remove_file(0, ren2_dst, 0);
}
update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
@@ -743,7 +743,7 @@ static void conflict_rename_dir(struct rename *ren1,
{
char *new_path = unique_path(ren1->pair->two->path, branch1);
output("Renaming %s to %s instead", ren1->pair->one->path, new_path);
- remove_file(0, ren1->pair->two->path);
+ remove_file(0, ren1->pair->two->path, 0);
update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
free(new_path);
}
@@ -758,7 +758,7 @@ static void conflict_rename_rename_2(struct rename *ren1,
output("Renaming %s to %s and %s to %s instead",
ren1->pair->one->path, new_path1,
ren2->pair->one->path, new_path2);
- remove_file(0, ren1->pair->two->path);
+ remove_file(0, ren1->pair->two->path, 0);
update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
free(new_path2);
@@ -856,7 +856,7 @@ static int process_renames(struct path_list *a_renames,
conflict_rename_rename(ren1, branch1, ren2, branch2);
} else {
struct merge_file_info mfi;
- remove_file(1, ren1_src);
+ remove_file(1, ren1_src, 1);
mfi = merge_file(ren1->pair->one,
ren1->pair->two,
ren2->pair->two,
@@ -889,7 +889,7 @@ static int process_renames(struct path_list *a_renames,
struct diff_filespec src_other, dst_other;
int try_merge, stage = a_renames == renames1 ? 3: 2;
- remove_file(1, ren1_src);
+ remove_file(1, ren1_src, 1);
hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
src_other.mode = ren1->src_entry->stages[stage].mode;
@@ -1007,7 +1007,8 @@ static int process_entry(const char *path, struct stage_data *entry,
* unchanged in the other */
if (a_sha)
output("Removing %s", path);
- remove_file(1, path);
+ /* do not touch working file if it did not exist */
+ remove_file(1, path, !a_sha);
} else {
/* Deleted in one and changed in the other */
clean_merge = 0;
@@ -1054,7 +1055,7 @@ static int process_entry(const char *path, struct stage_data *entry,
output("CONFLICT (%s): There is a directory with name %s in %s. "
"Adding %s as %s",
conf, path, other_branch, path, new_path);
- remove_file(0, path);
+ remove_file(0, path, 0);
update_file(0, sha, mode, new_path);
} else {
output("Adding %s", path);
@@ -1082,7 +1083,7 @@ static int process_entry(const char *path, struct stage_data *entry,
output("CONFLICT (add/add): File %s added non-identically "
"in both branches. Adding as %s and %s instead.",
path, new_path1, new_path2);
- remove_file(0, path);
+ remove_file(0, path, 0);
update_file(0, a_sha, a_mode, new_path1);
update_file(0, b_sha, b_mode, new_path2);
}
@@ -1204,14 +1205,13 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)
* Merge the commits h1 and h2, return the resulting virtual
* commit object and a flag indicating the cleaness of the merge.
*/
-static
-int merge(struct commit *h1,
- struct commit *h2,
- const char *branch1,
- const char *branch2,
- int call_depth /* =0 */,
- struct commit *ancestor /* =None */,
- struct commit **result)
+static int merge(struct commit *h1,
+ struct commit *h2,
+ const char *branch1,
+ const char *branch2,
+ int call_depth /* =0 */,
+ struct commit *ancestor /* =None */,
+ struct commit **result)
{
struct commit_list *ca = NULL, *iter;
struct commit *merged_common_ancestors;
@@ -1308,6 +1308,7 @@ int main(int argc, char *argv[])
const char *branch1, *branch2;
struct commit *result, *h1, *h2;
+ git_config(git_default_config); /* core.filemode */
original_index_file = getenv("GIT_INDEX_FILE");
if (!original_index_file)
diff --git a/object.c b/object.c
index 92813001e..de244e206 100644
--- a/object.c
+++ b/object.c
@@ -138,42 +138,56 @@ struct object *lookup_unknown_object(const unsigned char *sha1)
return obj;
}
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p)
+{
+ struct object *obj;
+ int eaten = 0;
+
+ if (!strcmp(type, blob_type)) {
+ struct blob *blob = lookup_blob(sha1);
+ parse_blob_buffer(blob, buffer, size);
+ obj = &blob->object;
+ } else if (!strcmp(type, tree_type)) {
+ struct tree *tree = lookup_tree(sha1);
+ obj = &tree->object;
+ if (!tree->object.parsed) {
+ parse_tree_buffer(tree, buffer, size);
+ eaten = 1;
+ }
+ } else if (!strcmp(type, commit_type)) {
+ struct commit *commit = lookup_commit(sha1);
+ parse_commit_buffer(commit, buffer, size);
+ if (!commit->buffer) {
+ commit->buffer = buffer;
+ eaten = 1;
+ }
+ obj = &commit->object;
+ } else if (!strcmp(type, tag_type)) {
+ struct tag *tag = lookup_tag(sha1);
+ parse_tag_buffer(tag, buffer, size);
+ obj = &tag->object;
+ } else {
+ obj = NULL;
+ }
+ *eaten_p = eaten;
+ return obj;
+}
+
struct object *parse_object(const unsigned char *sha1)
{
unsigned long size;
char type[20];
+ int eaten;
void *buffer = read_sha1_file(sha1, type, &size);
+
if (buffer) {
struct object *obj;
if (check_sha1_signature(sha1, buffer, size, type) < 0)
printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
- if (!strcmp(type, blob_type)) {
- struct blob *blob = lookup_blob(sha1);
- parse_blob_buffer(blob, buffer, size);
- obj = &blob->object;
- } else if (!strcmp(type, tree_type)) {
- struct tree *tree = lookup_tree(sha1);
- obj = &tree->object;
- if (!tree->object.parsed) {
- parse_tree_buffer(tree, buffer, size);
- buffer = NULL;
- }
- } else if (!strcmp(type, commit_type)) {
- struct commit *commit = lookup_commit(sha1);
- parse_commit_buffer(commit, buffer, size);
- if (!commit->buffer) {
- commit->buffer = buffer;
- buffer = NULL;
- }
- obj = &commit->object;
- } else if (!strcmp(type, tag_type)) {
- struct tag *tag = lookup_tag(sha1);
- parse_tag_buffer(tag, buffer, size);
- obj = &tag->object;
- } else {
- obj = NULL;
- }
- free(buffer);
+
+ obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
+ if (!eaten)
+ free(buffer);
return obj;
}
return NULL;
diff --git a/object.h b/object.h
index 3d4ff4611..caee733cd 100644
--- a/object.h
+++ b/object.h
@@ -59,6 +59,12 @@ void created_object(const unsigned char *sha1, struct object *obj);
/** Returns the object, having parsed it to find out what it is. **/
struct object *parse_object(const unsigned char *sha1);
+/* Given the result of read_sha1_file(), returns the object after
+ * parsing it. eaten_p indicates if the object has a borrowed copy
+ * of buffer and the caller should not free() it.
+ */
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p);
+
/** Returns the object, with potentially excess memory allocated. **/
struct object *lookup_unknown_object(const unsigned char *sha1);
diff --git a/pack.h b/pack.h
index eb07b033a..4814800f2 100644
--- a/pack.h
+++ b/pack.h
@@ -16,7 +16,4 @@ struct pack_header {
};
extern int verify_pack(struct packed_git *, int);
-extern int check_reuse_pack_delta(struct packed_git *, unsigned long,
- unsigned char *, unsigned long *,
- enum object_type *);
#endif
diff --git a/pager.c b/pager.c
index dcb398da8..4587fbbdb 100644
--- a/pager.c
+++ b/pager.c
@@ -50,7 +50,7 @@ void setup_pager(void)
close(fd[0]);
close(fd[1]);
- setenv("LESS", "-RS", 0);
+ setenv("LESS", "FRSX", 0);
run_pager(pager);
die("unable to execute pager '%s'", pager);
exit(255);
diff --git a/path.c b/path.c
index bb89fb02d..d2c076d7c 100644
--- a/path.c
+++ b/path.c
@@ -279,7 +279,7 @@ int adjust_shared_perm(const char *path)
: 0));
if (S_ISDIR(mode))
mode |= S_ISGID;
- if (chmod(path, mode) < 0)
+ if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
return -2;
return 0;
}
diff --git a/quote.c b/quote.c
index e3a4d4aef..a418a0f80 100644
--- a/quote.c
+++ b/quote.c
@@ -209,7 +209,7 @@ static int quote_c_style_counted(const char *name, int namelen,
if (!ch)
break;
if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
- (ch == 0177)) {
+ (ch >= 0177)) {
needquote = 1;
switch (ch) {
case '\a': EMITQ(); ch = 'a'; break;
@@ -349,3 +349,41 @@ void write_name_quoted(const char *prefix, int prefix_len,
else
goto no_quote;
}
+
+/* quoting as a string literal for other languages */
+
+void perl_quote_print(FILE *stream, const char *src)
+{
+ const char sq = '\'';
+ const char bq = '\\';
+ char c;
+
+ fputc(sq, stream);
+ while ((c = *src++)) {
+ if (c == sq || c == bq)
+ fputc(bq, stream);
+ fputc(c, stream);
+ }
+ fputc(sq, stream);
+}
+
+void python_quote_print(FILE *stream, const char *src)
+{
+ const char sq = '\'';
+ const char bq = '\\';
+ const char nl = '\n';
+ char c;
+
+ fputc(sq, stream);
+ while ((c = *src++)) {
+ if (c == nl) {
+ fputc(bq, stream);
+ fputc('n', stream);
+ continue;
+ }
+ if (c == sq || c == bq)
+ fputc(bq, stream);
+ fputc(c, stream);
+ }
+ fputc(sq, stream);
+}
diff --git a/quote.h b/quote.h
index 1a29e791d..b55e69975 100644
--- a/quote.h
+++ b/quote.h
@@ -52,4 +52,8 @@ extern char *unquote_c_style(const char *quoted, const char **endp);
extern void write_name_quoted(const char *prefix, int prefix_len,
const char *name, int quote, FILE *out);
+/* quoting as a string literal for other languages */
+extern void perl_quote_print(FILE *stream, const char *src);
+extern void python_quote_print(FILE *stream, const char *src);
+
#endif
diff --git a/receive-pack.c b/receive-pack.c
index ea2dbd4e3..d56898c9b 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -1,20 +1,42 @@
#include "cache.h"
+#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
#include "run-command.h"
+#include "exec_cmd.h"
#include "commit.h"
#include "object.h"
+#include <sys/wait.h>
static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
-static const char *unpacker[] = { "unpack-objects", NULL };
-
+static int deny_non_fast_forwards = 0;
+static int unpack_limit = 5000;
static int report_status;
static char capabilities[] = "report-status";
static int capabilities_sent;
-static int show_ref(const char *path, const unsigned char *sha1)
+static int receive_pack_config(const char *var, const char *value)
+{
+ git_default_config(var, value);
+
+ if (strcmp(var, "receive.denynonfastforwards") == 0)
+ {
+ deny_non_fast_forwards = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.unpacklimit") == 0)
+ {
+ unpack_limit = git_config_int(var, value);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
if (capabilities_sent)
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
@@ -27,9 +49,9 @@ static int show_ref(const char *path, const unsigned char *sha1)
static void write_head_info(void)
{
- for_each_ref(show_ref);
+ for_each_ref(show_ref, NULL);
if (!capabilities_sent)
- show_ref("capabilities^{}", null_sha1);
+ show_ref("capabilities^{}", null_sha1, 0, NULL);
}
@@ -43,34 +65,6 @@ struct command {
static struct command *commands;
-static int is_all_zeroes(const char *hex)
-{
- int i;
- for (i = 0; i < 40; i++)
- if (*hex++ != '0')
- return 0;
- return 1;
-}
-
-static int verify_old_ref(const char *name, char *hex_contents)
-{
- int fd, ret;
- char buffer[60];
-
- if (is_all_zeroes(hex_contents))
- return 0;
- fd = open(name, O_RDONLY);
- if (fd < 0)
- return -1;
- ret = read(fd, buffer, 40);
- close(fd);
- if (ret != 40)
- return -1;
- if (memcmp(buffer, hex_contents, 40))
- return -1;
- return 0;
-}
-
static char update_hook[] = "hooks/update";
static int run_update_hook(const char *refname,
@@ -107,8 +101,8 @@ static int update(struct command *cmd)
const char *name = cmd->ref_name;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
- char new_hex[60], *old_hex, *lock_name;
- int newfd, namelen, written;
+ char new_hex[41], old_hex[41];
+ struct ref_lock *lock;
cmd->error_string = NULL;
if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) {
@@ -117,13 +111,8 @@ static int update(struct command *cmd)
name);
}
- namelen = strlen(name);
- lock_name = xmalloc(namelen + 10);
- memcpy(lock_name, name, namelen);
- memcpy(lock_name + namelen, ".lock", 6);
-
strcpy(new_hex, sha1_to_hex(new_sha1));
- old_hex = sha1_to_hex(old_sha1);
+ strcpy(old_hex, sha1_to_hex(old_sha1));
if (!has_sha1_file(new_sha1)) {
cmd->error_string = "bad pack";
return error("unpack should have generated %s, "
@@ -144,47 +133,20 @@ static int update(struct command *cmd)
return error("denying non-fast forward;"
" you should pull first");
}
- safe_create_leading_directories(lock_name);
-
- newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
- if (newfd < 0) {
- cmd->error_string = "can't lock";
- return error("unable to create %s (%s)",
- lock_name, strerror(errno));
- }
-
- /* Write the ref with an ending '\n' */
- new_hex[40] = '\n';
- new_hex[41] = 0;
- written = write(newfd, new_hex, 41);
- /* Remove the '\n' again */
- new_hex[40] = 0;
-
- close(newfd);
- if (written != 41) {
- unlink(lock_name);
- cmd->error_string = "can't write";
- return error("unable to write %s", lock_name);
- }
- if (verify_old_ref(name, old_hex) < 0) {
- unlink(lock_name);
- cmd->error_string = "raced";
- return error("%s changed during push", name);
- }
if (run_update_hook(name, old_hex, new_hex)) {
- unlink(lock_name);
cmd->error_string = "hook declined";
return error("hook declined to update %s", name);
}
- else if (rename(lock_name, name) < 0) {
- unlink(lock_name);
- cmd->error_string = "can't rename";
- return error("unable to replace %s", name);
- }
- else {
- fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
- return 0;
+
+ lock = lock_any_ref_for_update(name, old_sha1);
+ if (!lock) {
+ cmd->error_string = "failed to lock";
+ return error("failed to lock %s", name);
}
+ write_ref_sha1(lock, new_sha1, "push");
+
+ fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+ return 0;
}
static char update_post_hook[] = "hooks/post-update";
@@ -273,29 +235,127 @@ static void read_head_info(void)
}
}
-static const char *unpack(int *error_code)
+static const char *parse_pack_header(struct pack_header *hdr)
{
- int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+ char *c = (char*)hdr;
+ ssize_t remaining = sizeof(struct pack_header);
+ do {
+ ssize_t r = xread(0, c, remaining);
+ if (r <= 0)
+ return "eof before pack header was fully read";
+ remaining -= r;
+ c += r;
+ } while (remaining > 0);
+ if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+ return "protocol error (pack signature mismatch detected)";
+ if (!pack_version_ok(hdr->hdr_version))
+ return "protocol error (pack version unsupported)";
+ return NULL;
+}
- *error_code = 0;
- switch (code) {
- case 0:
- return NULL;
- case -ERR_RUN_COMMAND_FORK:
- return "unpack fork failed";
- case -ERR_RUN_COMMAND_EXEC:
- return "unpack execute failed";
- case -ERR_RUN_COMMAND_WAITPID:
- return "waitpid failed";
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- return "waitpid is confused";
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- return "unpacker died of signal";
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- return "unpacker died strangely";
- default:
- *error_code = -code;
- return "unpacker exited with error code";
+static const char *pack_lockfile;
+
+static const char *unpack(void)
+{
+ struct pack_header hdr;
+ const char *hdr_err;
+ char hdr_arg[38];
+
+ hdr_err = parse_pack_header(&hdr);
+ if (hdr_err)
+ return hdr_err;
+ snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u",
+ ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+
+ if (ntohl(hdr.hdr_entries) < unpack_limit) {
+ int code;
+ const char *unpacker[3];
+ unpacker[0] = "unpack-objects";
+ unpacker[1] = hdr_arg;
+ unpacker[2] = NULL;
+ code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+ switch (code) {
+ case 0:
+ return NULL;
+ case -ERR_RUN_COMMAND_FORK:
+ return "unpack fork failed";
+ case -ERR_RUN_COMMAND_EXEC:
+ return "unpack execute failed";
+ case -ERR_RUN_COMMAND_WAITPID:
+ return "waitpid failed";
+ case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+ return "waitpid is confused";
+ case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+ return "unpacker died of signal";
+ case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+ return "unpacker died strangely";
+ default:
+ return "unpacker exited with error code";
+ }
+ } else {
+ const char *keeper[6];
+ int fd[2], s, len, status;
+ pid_t pid;
+ char keep_arg[256];
+ char packname[46];
+
+ s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
+ if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+ strcpy(keep_arg + s, "localhost");
+
+ keeper[0] = "index-pack";
+ keeper[1] = "--stdin";
+ keeper[2] = "--fix-thin";
+ keeper[3] = hdr_arg;
+ keeper[4] = keep_arg;
+ keeper[5] = NULL;
+
+ if (pipe(fd) < 0)
+ return "index-pack pipe failed";
+ pid = fork();
+ if (pid < 0)
+ return "index-pack fork failed";
+ if (!pid) {
+ dup2(fd[1], 1);
+ close(fd[1]);
+ close(fd[0]);
+ execv_git_cmd(keeper);
+ die("execv of index-pack failed");
+ }
+ close(fd[1]);
+
+ /*
+ * The first thing we expects from index-pack's output
+ * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
+ * %40s is the newly created pack SHA1 name. In the "keep"
+ * case, we need it to remove the corresponding .keep file
+ * later on. If we don't get that then tough luck with it.
+ */
+ for (len = 0;
+ len < 46 && (s = xread(fd[0], packname+len, 46-len)) > 0;
+ len += s);
+ close(fd[0]);
+ if (len == 46 && packname[45] == '\n' &&
+ memcmp(packname, "keep\t", 5) == 0) {
+ char path[PATH_MAX];
+ packname[45] = 0;
+ snprintf(path, sizeof(path), "%s/pack/pack-%s.keep",
+ get_object_directory(), packname + 5);
+ pack_lockfile = xstrdup(path);
+ }
+
+ /* Then wrap our index-pack process. */
+ while (waitpid(pid, &status, 0) < 0)
+ if (errno != EINTR)
+ return "waitpid failed";
+ if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
+ if (code)
+ return "index-pack exited with error code";
+ reprepare_packed_git();
+ return NULL;
+ }
+ return "index-pack abnormal exit";
}
}
@@ -335,9 +395,12 @@ int main(int argc, char **argv)
if (!dir)
usage(receive_pack_usage);
- if(!enter_repo(dir, 0))
+ if (!enter_repo(dir, 0))
die("'%s': unable to chdir or not a git archive", dir);
+ setup_ident();
+ git_config(receive_pack_config);
+
write_head_info();
/* EOF */
@@ -345,10 +408,11 @@ int main(int argc, char **argv)
read_head_info();
if (commands) {
- int code;
- const char *unpack_status = unpack(&code);
+ const char *unpack_status = unpack();
if (!unpack_status)
execute_commands();
+ if (pack_lockfile)
+ unlink(pack_lockfile);
if (report_status)
report(unpack_status);
}
diff --git a/refs.c b/refs.c
index 98327d798..f003a0b10 100644
--- a/refs.c
+++ b/refs.c
@@ -3,15 +3,193 @@
#include <errno.h>
+struct ref_list {
+ struct ref_list *next;
+ unsigned char flag; /* ISSYMREF? ISPACKED? */
+ unsigned char sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+ /*
+ * 42: the answer to everything.
+ *
+ * In this case, it happens to be the answer to
+ * 40 (length of sha1 hex representation)
+ * +1 (space in between hex and name)
+ * +1 (newline at the end of the line)
+ */
+ int len = strlen(line) - 42;
+
+ if (len <= 0)
+ return NULL;
+ if (get_sha1_hex(line, sha1) < 0)
+ return NULL;
+ if (!isspace(line[40]))
+ return NULL;
+ line += 41;
+ if (isspace(*line))
+ return NULL;
+ if (line[len] != '\n')
+ return NULL;
+ line[len] = 0;
+ return line;
+}
+
+static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
+ int flag, struct ref_list *list)
+{
+ int len;
+ struct ref_list **p = &list, *entry;
+
+ /* Find the place to insert the ref into.. */
+ while ((entry = *p) != NULL) {
+ int cmp = strcmp(entry->name, name);
+ if (cmp > 0)
+ break;
+
+ /* Same as existing entry? */
+ if (!cmp)
+ return list;
+ p = &entry->next;
+ }
+
+ /* Allocate it and add it in.. */
+ len = strlen(name) + 1;
+ entry = xmalloc(sizeof(struct ref_list) + len);
+ hashcpy(entry->sha1, sha1);
+ memcpy(entry->name, name, len);
+ entry->flag = flag;
+ entry->next = *p;
+ *p = entry;
+ return list;
+}
+
+/*
+ * Future: need to be in "struct repository"
+ * when doing a full libification.
+ */
+struct cached_refs {
+ char did_loose;
+ char did_packed;
+ struct ref_list *loose;
+ struct ref_list *packed;
+} cached_refs;
+
+static void free_ref_list(struct ref_list *list)
+{
+ struct ref_list *next;
+ for ( ; list; list = next) {
+ next = list->next;
+ free(list);
+ }
+}
+
+static void invalidate_cached_refs(void)
+{
+ struct cached_refs *ca = &cached_refs;
+
+ if (ca->did_loose && ca->loose)
+ free_ref_list(ca->loose);
+ if (ca->did_packed && ca->packed)
+ free_ref_list(ca->packed);
+ ca->loose = ca->packed = NULL;
+ ca->did_loose = ca->did_packed = 0;
+}
+
+static struct ref_list *get_packed_refs(void)
+{
+ if (!cached_refs.did_packed) {
+ struct ref_list *refs = NULL;
+ FILE *f = fopen(git_path("packed-refs"), "r");
+ if (f) {
+ struct ref_list *list = NULL;
+ char refline[PATH_MAX];
+ while (fgets(refline, sizeof(refline), f)) {
+ unsigned char sha1[20];
+ const char *name = parse_ref_line(refline, sha1);
+ if (!name)
+ continue;
+ list = add_ref(name, sha1, REF_ISPACKED, list);
+ }
+ fclose(f);
+ refs = list;
+ }
+ cached_refs.packed = refs;
+ cached_refs.did_packed = 1;
+ }
+ return cached_refs.packed;
+}
+
+static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+{
+ DIR *dir = opendir(git_path("%s", base));
+
+ if (dir) {
+ struct dirent *de;
+ int baselen = strlen(base);
+ char *ref = xmalloc(baselen + 257);
+
+ memcpy(ref, base, baselen);
+ if (baselen && base[baselen-1] != '/')
+ ref[baselen++] = '/';
+
+ while ((de = readdir(dir)) != NULL) {
+ unsigned char sha1[20];
+ struct stat st;
+ int flag;
+ int namelen;
+
+ if (de->d_name[0] == '.')
+ continue;
+ namelen = strlen(de->d_name);
+ if (namelen > 255)
+ continue;
+ if (has_extension(de->d_name, ".lock"))
+ continue;
+ memcpy(ref + baselen, de->d_name, namelen+1);
+ if (stat(git_path("%s", ref), &st) < 0)
+ continue;
+ if (S_ISDIR(st.st_mode)) {
+ list = get_ref_dir(ref, list);
+ continue;
+ }
+ if (!resolve_ref(ref, sha1, 1, &flag)) {
+ error("%s points nowhere!", ref);
+ continue;
+ }
+ list = add_ref(ref, sha1, flag, list);
+ }
+ free(ref);
+ closedir(dir);
+ }
+ return list;
+}
+
+static struct ref_list *get_loose_refs(void)
+{
+ if (!cached_refs.did_loose) {
+ cached_refs.loose = get_ref_dir("refs", NULL);
+ cached_refs.did_loose = 1;
+ }
+ return cached_refs.loose;
+}
+
/* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5
-const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
{
int depth = MAXDEPTH, len;
char buffer[256];
+ static char ref_buffer[256];
+
+ if (flag)
+ *flag = 0;
for (;;) {
+ const char *path = git_path("%s", ref);
struct stat st;
char *buf;
int fd;
@@ -27,17 +205,31 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
* reading.
*/
if (lstat(path, &st) < 0) {
+ struct ref_list *list = get_packed_refs();
+ while (list) {
+ if (!strcmp(ref, list->name)) {
+ hashcpy(sha1, list->sha1);
+ if (flag)
+ *flag |= REF_ISPACKED;
+ return ref;
+ }
+ list = list->next;
+ }
if (reading || errno != ENOENT)
return NULL;
hashclr(sha1);
- return path;
+ return ref;
}
/* Follow "normalized" - ie "refs/.." symlinks by hand */
if (S_ISLNK(st.st_mode)) {
len = readlink(path, buffer, sizeof(buffer)-1);
if (len >= 5 && !memcmp("refs/", buffer, 5)) {
- path = git_path("%.*s", len, buffer);
+ buffer[len] = 0;
+ strcpy(ref_buffer, buffer);
+ ref = ref_buffer;
+ if (flag)
+ *flag |= REF_ISSYMREF;
continue;
}
}
@@ -68,19 +260,24 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
while (len && isspace(*buf))
buf++, len--;
while (len && isspace(buf[len-1]))
- buf[--len] = 0;
- path = git_path("%.*s", len, buf);
+ len--;
+ buf[len] = 0;
+ memcpy(ref_buffer, buf, len + 1);
+ ref = ref_buffer;
+ if (flag)
+ *flag |= REF_ISSYMREF;
}
if (len < 40 || get_sha1_hex(buffer, sha1))
return NULL;
- return path;
+ return ref;
}
-int create_symref(const char *git_HEAD, const char *refs_heads_master)
+int create_symref(const char *ref_target, const char *refs_heads_master)
{
const char *lockpath;
char ref[1000];
int fd, len, written;
+ const char *git_HEAD = git_path("%s", ref_target);
#ifndef NO_SYMLINK_HEAD
if (prefer_symlink_refs) {
@@ -118,104 +315,101 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
return 0;
}
-int read_ref(const char *filename, unsigned char *sha1)
+int read_ref(const char *ref, unsigned char *sha1)
{
- if (resolve_ref(filename, sha1, 1))
+ if (resolve_ref(ref, sha1, 1, NULL))
return 0;
return -1;
}
-static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
+static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
+ void *cb_data)
{
- int retval = 0;
- DIR *dir = opendir(git_path("%s", base));
-
- if (dir) {
- struct dirent *de;
- int baselen = strlen(base);
- char *path = xmalloc(baselen + 257);
-
- if (!strncmp(base, "./", 2)) {
- base += 2;
- baselen -= 2;
+ int retval;
+ struct ref_list *packed = get_packed_refs();
+ struct ref_list *loose = get_loose_refs();
+
+ while (packed && loose) {
+ struct ref_list *entry;
+ int cmp = strcmp(packed->name, loose->name);
+ if (!cmp) {
+ packed = packed->next;
+ continue;
}
- memcpy(path, base, baselen);
- if (baselen && base[baselen-1] != '/')
- path[baselen++] = '/';
-
- while ((de = readdir(dir)) != NULL) {
- unsigned char sha1[20];
- struct stat st;
- int namelen;
+ if (cmp > 0) {
+ entry = loose;
+ loose = loose->next;
+ } else {
+ entry = packed;
+ packed = packed->next;
+ }
+ if (strncmp(base, entry->name, trim))
+ continue;
+ if (is_null_sha1(entry->sha1))
+ continue;
+ if (!has_sha1_file(entry->sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ continue;
+ }
+ retval = fn(entry->name + trim, entry->sha1,
+ entry->flag, cb_data);
+ if (retval)
+ return retval;
+ }
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if (namelen > 255)
- continue;
- if (has_extension(de->d_name, ".lock"))
- continue;
- memcpy(path + baselen, de->d_name, namelen+1);
- if (stat(git_path("%s", path), &st) < 0)
- continue;
- if (S_ISDIR(st.st_mode)) {
- retval = do_for_each_ref(path, fn, trim);
- if (retval)
- break;
- continue;
- }
- if (read_ref(git_path("%s", path), sha1) < 0) {
- error("%s points nowhere!", path);
- continue;
- }
- if (!has_sha1_file(sha1)) {
- error("%s does not point to a valid "
- "commit object!", path);
- continue;
- }
- retval = fn(path + trim, sha1);
+ packed = packed ? packed : loose;
+ while (packed) {
+ if (!strncmp(base, packed->name, trim)) {
+ retval = fn(packed->name + trim, packed->sha1,
+ packed->flag, cb_data);
if (retval)
- break;
+ return retval;
}
- free(path);
- closedir(dir);
+ packed = packed->next;
}
- return retval;
+ return 0;
}
-int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int head_ref(each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
- if (!read_ref(git_path("HEAD"), sha1))
- return fn("HEAD", sha1);
+ int flag;
+
+ if (resolve_ref("HEAD", sha1, 1, &flag))
+ return fn("HEAD", sha1, flag, cb_data);
return 0;
}
-int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs", fn, 0);
+ return do_for_each_ref("refs/", fn, 0, cb_data);
}
-int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_tag_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/tags", fn, 10);
+ return do_for_each_ref("refs/tags/", fn, 10, cb_data);
}
-int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/heads", fn, 11);
+ return do_for_each_ref("refs/heads/", fn, 11, cb_data);
}
-int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/remotes", fn, 13);
+ return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
}
+/* NEEDSWORK: This is only used by ssh-upload and it should go; the
+ * caller should do resolve_ref or read_ref like everybody else. Or
+ * maybe everybody else should use get_ref_sha1() instead of doing
+ * read_ref().
+ */
int get_ref_sha1(const char *ref, unsigned char *sha1)
{
if (check_ref_format(ref))
return -1;
- return read_ref(git_path("refs/%s", ref), sha1);
+ return read_ref(mkpath("refs/%s", ref), sha1);
}
/*
@@ -273,22 +467,13 @@ int check_ref_format(const char *ref)
static struct ref_lock *verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
- char buf[40];
- int nr, fd = open(lock->ref_file, O_RDONLY);
- if (fd < 0 && (mustexist || errno != ENOENT)) {
- error("Can't verify ref %s", lock->ref_file);
- unlock_ref(lock);
- return NULL;
- }
- nr = read(fd, buf, 40);
- close(fd);
- if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
- error("Can't verify ref %s", lock->ref_file);
+ if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+ error("Can't verify ref %s", lock->ref_name);
unlock_ref(lock);
return NULL;
}
if (hashcmp(lock->old_sha1, old_sha1)) {
- error("Ref %s is at %s but expected %s", lock->ref_file,
+ error("Ref %s is at %s but expected %s", lock->ref_name,
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
unlock_ref(lock);
return NULL;
@@ -296,54 +481,223 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,
return lock;
}
-static struct ref_lock *lock_ref_sha1_basic(const char *path,
- int plen,
- const unsigned char *old_sha1, int mustexist)
+static int remove_empty_dir_recursive(char *path, int len)
+{
+ DIR *dir = opendir(path);
+ struct dirent *e;
+ int ret = 0;
+
+ if (!dir)
+ return -1;
+ if (path[len-1] != '/')
+ path[len++] = '/';
+ while ((e = readdir(dir)) != NULL) {
+ struct stat st;
+ int namlen;
+ if ((e->d_name[0] == '.') &&
+ ((e->d_name[1] == 0) ||
+ ((e->d_name[1] == '.') && e->d_name[2] == 0)))
+ continue; /* "." and ".." */
+
+ namlen = strlen(e->d_name);
+ if ((len + namlen < PATH_MAX) &&
+ strcpy(path + len, e->d_name) &&
+ !lstat(path, &st) &&
+ S_ISDIR(st.st_mode) &&
+ !remove_empty_dir_recursive(path, len + namlen))
+ continue; /* happy */
+
+ /* path too long, stat fails, or non-directory still exists */
+ ret = -1;
+ break;
+ }
+ closedir(dir);
+ if (!ret) {
+ path[len] = 0;
+ ret = rmdir(path);
+ }
+ return ret;
+}
+
+static int remove_empty_directories(char *file)
{
- const char *orig_path = path;
+ /* we want to create a file but there is a directory there;
+ * if that is an empty directory (or a directory that contains
+ * only empty directories), remove them.
+ */
+ char path[PATH_MAX];
+ int len = strlen(file);
+
+ if (len >= PATH_MAX) /* path too long ;-) */
+ return -1;
+ strcpy(path, file);
+ return remove_empty_dir_recursive(path, len);
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+{
+ char *ref_file;
+ const char *orig_ref = ref;
struct ref_lock *lock;
struct stat st;
+ int last_errno = 0;
+ int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
- plen = strlen(path) - plen;
- path = resolve_ref(path, lock->old_sha1, mustexist);
- if (!path) {
- int last_errno = errno;
+ ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+ if (!ref && errno == EISDIR) {
+ /* we are trying to lock foo but we used to
+ * have foo/bar which now does not exist;
+ * it is normal for the empty directory 'foo'
+ * to remain.
+ */
+ ref_file = git_path("%s", orig_ref);
+ if (remove_empty_directories(ref_file)) {
+ last_errno = errno;
+ error("there are still refs under '%s'", orig_ref);
+ goto error_return;
+ }
+ ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+ }
+ if (!ref) {
+ last_errno = errno;
error("unable to resolve reference %s: %s",
- orig_path, strerror(errno));
- unlock_ref(lock);
- errno = last_errno;
- return NULL;
+ orig_ref, strerror(errno));
+ goto error_return;
+ }
+ if (is_null_sha1(lock->old_sha1)) {
+ /* The ref did not exist and we are creating it.
+ * Make sure there is no existing ref that is packed
+ * whose name begins with our refname, nor a ref whose
+ * name is a proper prefix of our refname.
+ */
+ int namlen = strlen(ref); /* e.g. 'foo/bar' */
+ struct ref_list *list = get_packed_refs();
+ while (list) {
+ /* list->name could be 'foo' or 'foo/bar/baz' */
+ int len = strlen(list->name);
+ int cmplen = (namlen < len) ? namlen : len;
+ const char *lead = (namlen < len) ? list->name : ref;
+
+ if (!strncmp(ref, list->name, cmplen) &&
+ lead[cmplen] == '/') {
+ error("'%s' exists; cannot create '%s'",
+ list->name, ref);
+ goto error_return;
+ }
+ list = list->next;
+ }
}
+
lock->lk = xcalloc(1, sizeof(struct lock_file));
- lock->ref_file = xstrdup(path);
- lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen));
- lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+ lock->ref_name = xstrdup(ref);
+ lock->log_file = xstrdup(git_path("logs/%s", ref));
+ ref_file = git_path("%s", ref);
+ lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
- if (safe_create_leading_directories(lock->ref_file))
- die("unable to create directory for %s", lock->ref_file);
- lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1);
+ if (safe_create_leading_directories(ref_file)) {
+ last_errno = errno;
+ error("unable to create directory for %s", ref_file);
+ goto error_return;
+ }
+ lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1);
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+
+ error_return:
+ unlock_ref(lock);
+ errno = last_errno;
+ return NULL;
}
-struct ref_lock *lock_ref_sha1(const char *ref,
- const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
{
+ char refpath[PATH_MAX];
if (check_ref_format(ref))
return NULL;
- return lock_ref_sha1_basic(git_path("refs/%s", ref),
- 5 + strlen(ref), old_sha1, mustexist);
+ strcpy(refpath, mkpath("refs/%s", ref));
+ return lock_ref_sha1_basic(refpath, old_sha1, NULL);
}
-struct ref_lock *lock_any_ref_for_update(const char *ref,
- const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+{
+ return lock_ref_sha1_basic(ref, old_sha1, NULL);
+}
+
+static struct lock_file packlock;
+
+static int repack_without_ref(const char *refname)
{
- return lock_ref_sha1_basic(git_path("%s", ref),
- strlen(ref), old_sha1, mustexist);
+ struct ref_list *list, *packed_ref_list;
+ int fd;
+ int found = 0;
+
+ packed_ref_list = get_packed_refs();
+ for (list = packed_ref_list; list; list = list->next) {
+ if (!strcmp(refname, list->name)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ return 0;
+ memset(&packlock, 0, sizeof(packlock));
+ fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+ if (fd < 0)
+ return error("cannot delete '%s' from packed refs", refname);
+
+ for (list = packed_ref_list; list; list = list->next) {
+ char line[PATH_MAX + 100];
+ int len;
+
+ if (!strcmp(refname, list->name))
+ continue;
+ len = snprintf(line, sizeof(line), "%s %s\n",
+ sha1_to_hex(list->sha1), list->name);
+ /* this should not happen but just being defensive */
+ if (len > sizeof(line))
+ die("too long a refname '%s'", list->name);
+ write_or_die(fd, line, len);
+ }
+ return commit_lock_file(&packlock);
+}
+
+int delete_ref(const char *refname, unsigned char *sha1)
+{
+ struct ref_lock *lock;
+ int err, i, ret = 0, flag = 0;
+
+ lock = lock_ref_sha1_basic(refname, sha1, &flag);
+ if (!lock)
+ return 1;
+ if (!(flag & REF_ISPACKED)) {
+ /* loose */
+ i = strlen(lock->lk->filename) - 5; /* .lock */
+ lock->lk->filename[i] = 0;
+ err = unlink(lock->lk->filename);
+ if (err) {
+ ret = 1;
+ error("unlink(%s) failed: %s",
+ lock->lk->filename, strerror(errno));
+ }
+ lock->lk->filename[i] = '.';
+ }
+ /* removing the loose one could have resurrected an earlier
+ * packed one. Also, if it was not loose we need to repack
+ * without it.
+ */
+ ret |= repack_without_ref(refname);
+
+ err = unlink(lock->log_file);
+ if (err && errno != ENOENT)
+ fprintf(stderr, "warning: unlink(%s) failed: %s",
+ lock->log_file, strerror(errno));
+ invalidate_cached_refs();
+ unlock_ref(lock);
+ return ret;
}
void unlock_ref(struct ref_lock *lock)
@@ -354,7 +708,7 @@ void unlock_ref(struct ref_lock *lock)
if (lock->lk)
rollback_lock_file(lock->lk);
}
- free(lock->ref_file);
+ free(lock->ref_name);
free(lock->log_file);
free(lock);
}
@@ -367,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock,
char *logrec;
const char *committer;
- if (log_all_ref_updates) {
+ if (log_all_ref_updates &&
+ !strncmp(lock->ref_name, "refs/heads/", 11)) {
if (safe_create_leading_directories(lock->log_file) < 0)
return error("unable to create directory for %s",
lock->log_file);
@@ -376,10 +731,20 @@ static int log_ref_write(struct ref_lock *lock,
logfd = open(lock->log_file, oflags, 0666);
if (logfd < 0) {
- if (!log_all_ref_updates && errno == ENOENT)
+ if (!(oflags & O_CREAT) && errno == ENOENT)
return 0;
- return error("Unable to append to %s: %s",
- lock->log_file, strerror(errno));
+
+ if ((oflags & O_CREAT) && errno == EISDIR) {
+ if (remove_empty_directories(lock->log_file)) {
+ return error("There are still logs under '%s'",
+ lock->log_file);
+ }
+ logfd = open(lock->log_file, oflags, 0666);
+ }
+
+ if (logfd < 0)
+ return error("Unable to append to %s: %s",
+ lock->log_file, strerror(errno));
}
committer = git_committer_info(1);
@@ -426,12 +791,13 @@ int write_ref_sha1(struct ref_lock *lock,
unlock_ref(lock);
return -1;
}
+ invalidate_cached_refs();
if (log_ref_write(lock, sha1, logmsg) < 0) {
unlock_ref(lock);
return -1;
}
if (commit_lock_file(lock->lk)) {
- error("Couldn't set %s", lock->ref_file);
+ error("Couldn't set %s", lock->ref_name);
unlock_ref(lock);
return -1;
}
@@ -440,7 +806,7 @@ int write_ref_sha1(struct ref_lock *lock,
return 0;
}
-int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1)
{
const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
char *tz_c;
@@ -473,7 +839,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
if (!lastgt)
die("Log %s is corrupt.", logfile);
date = strtoul(lastgt + 1, &tz_c, 10);
- if (date <= at_time) {
+ if (date <= at_time || cnt == 0) {
if (lastrec) {
if (get_sha1_hex(lastrec, logged_sha1))
die("Log %s is corrupt.", logfile);
@@ -504,6 +870,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
return 0;
}
lastrec = rec;
+ if (cnt > 0)
+ cnt--;
}
rec = logdata;
diff --git a/refs.h b/refs.h
index 553155c04..a57d43726 100644
--- a/refs.h
+++ b/refs.h
@@ -2,7 +2,7 @@
#define REFS_H
struct ref_lock {
- char *ref_file;
+ char *ref_name;
char *log_file;
struct lock_file *lk;
unsigned char old_sha1[20];
@@ -14,20 +14,23 @@ struct ref_lock {
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
*/
-extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1));
+#define REF_ISSYMREF 01
+#define REF_ISPACKED 02
+typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
+extern int head_ref(each_ref_fn, void *);
+extern int for_each_ref(each_ref_fn, void *);
+extern int for_each_tag_ref(each_ref_fn, void *);
+extern int for_each_branch_ref(each_ref_fn, void *);
+extern int for_each_remote_ref(each_ref_fn, void *);
/** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *sha1);
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
-extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
/** Locks any ref (for 'HEAD' type refs). */
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1);
/** Release any lock taken but not written. **/
extern void unlock_ref(struct ref_lock *lock);
@@ -36,7 +39,7 @@ extern void unlock_ref(struct ref_lock *lock);
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
+extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1);
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);
diff --git a/revision.c b/revision.c
index b021d3354..993bb668a 100644
--- a/revision.c
+++ b/revision.c
@@ -418,9 +418,6 @@ static void limit_list(struct rev_info *revs)
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
- if (revs->unpacked &&
- has_sha1_pack(obj->sha1, revs->ignore_packed))
- obj->flags |= UNINTERESTING;
add_parents_to_list(revs, commit, &list);
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
@@ -468,7 +465,7 @@ static void limit_list(struct rev_info *revs)
static int all_flags;
static struct rev_info *all_revs;
-static int handle_one_ref(const char *path, const unsigned char *sha1)
+static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *object = get_reference(all_revs, path, sha1, all_flags);
add_pending_object(all_revs, object, "");
@@ -479,7 +476,7 @@ static void handle_all(struct rev_info *revs, unsigned flags)
{
all_revs = revs;
all_flags = flags;
- for_each_ref(handle_one_ref);
+ for_each_ref(handle_one_ref, NULL);
}
static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
@@ -1022,7 +1019,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
add_pending_object(revs, object, def);
}
- if (revs->topo_order || revs->unpacked)
+ if (revs->topo_order)
revs->limited = 1;
if (revs->prune_data) {
@@ -1156,17 +1153,18 @@ struct commit *get_revision(struct rev_info *revs)
* that we'd otherwise have done in limit_list().
*/
if (!revs->limited) {
- if ((revs->unpacked &&
- has_sha1_pack(commit->object.sha1,
- revs->ignore_packed)) ||
- (revs->max_age != -1 &&
- (commit->date < revs->max_age)))
+ if (revs->max_age != -1 &&
+ (commit->date < revs->max_age))
continue;
add_parents_to_list(revs, commit, &revs->commits);
}
if (commit->object.flags & SHOWN)
continue;
+ if (revs->unpacked && has_sha1_pack(commit->object.sha1,
+ revs->ignore_packed))
+ continue;
+
/* We want to show boundary commits only when their
* children are shown. When path-limiter is in effect,
* rewrite_parents() drops some commits from getting shown,
diff --git a/send-pack.c b/send-pack.c
index 5bb123a37..447666665 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -29,6 +29,7 @@ static void exec_pack_objects(void)
{
static const char *args[] = {
"pack-objects",
+ "--all-progress",
"--stdout",
NULL
};
@@ -215,7 +216,7 @@ static int ref_newer(const unsigned char *new_sha1,
static struct ref *local_refs, **local_tail;
static struct ref *remote_refs, **remote_tail;
-static int one_local_ref(const char *refname, const unsigned char *sha1)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct ref *ref;
int len = strlen(refname) + 1;
@@ -230,7 +231,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1)
static void get_local_heads(void)
{
local_tail = &local_refs;
- for_each_ref(one_local_ref);
+ for_each_ref(one_local_ref, NULL);
}
static int receive_status(int in)
diff --git a/server-info.c b/server-info.c
index 2fb8f5710..6cd38be32 100644
--- a/server-info.c
+++ b/server-info.c
@@ -7,7 +7,7 @@
/* refs */
static FILE *info_ref_fp;
-static int add_info_ref(const char *path, const unsigned char *sha1)
+static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = parse_object(sha1);
@@ -34,7 +34,7 @@ static int update_info_refs(int force)
info_ref_fp = fopen(path1, "w");
if (!info_ref_fp)
return error("unable to update %s", path0);
- for_each_ref(add_info_ref);
+ for_each_ref(add_info_ref, NULL);
fclose(info_ref_fp);
rename(path1, path0);
free(path0);
diff --git a/setup.c b/setup.c
index 9a46a58a4..2afdba414 100644
--- a/setup.c
+++ b/setup.c
@@ -244,8 +244,6 @@ int check_repository_format_version(const char *var, const char *value)
repository_format_version = git_config_int(var, value);
else if (strcmp(var, "core.sharedrepository") == 0)
shared_repository = git_config_perm(var, value);
- else if (strcmp(var, "receive.denynonfastforwards") == 0)
- deny_non_fast_forwards = git_config_bool(var, value);
return 0;
}
diff --git a/sha1_file.c b/sha1_file.c
index 47e2a29ab..6ea59b558 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -663,7 +663,7 @@ void prepare_packed_git(void)
prepare_packed_git_run_once = 1;
}
-static void reprepare_packed_git(void)
+void reprepare_packed_git(void)
{
prepare_packed_git_run_once = 0;
prepare_packed_git();
@@ -877,26 +877,61 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l
return unpack_sha1_rest(&stream, hdr, *size);
}
+static unsigned long get_delta_base(struct packed_git *p,
+ unsigned long offset,
+ enum object_type kind,
+ unsigned long delta_obj_offset,
+ unsigned long *base_obj_offset)
+{
+ unsigned char *base_info = (unsigned char *) p->pack_base + offset;
+ unsigned long base_offset;
+
+ /* there must be at least 20 bytes left regardless of delta type */
+ if (p->pack_size <= offset + 20)
+ die("truncated pack file");
+
+ if (kind == OBJ_OFS_DELTA) {
+ unsigned used = 0;
+ unsigned char c = base_info[used++];
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || base_offset & ~(~0UL >> 7))
+ die("offset value overflow for delta base object");
+ c = base_info[used++];
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ base_offset = delta_obj_offset - base_offset;
+ if (base_offset >= delta_obj_offset)
+ die("delta base offset out of bound");
+ offset += used;
+ } else if (kind == OBJ_REF_DELTA) {
+ /* The base entry _must_ be in the same pack */
+ base_offset = find_pack_entry_one(base_info, p);
+ if (!base_offset)
+ die("failed to find delta-pack base object %s",
+ sha1_to_hex(base_info));
+ offset += 20;
+ } else
+ die("I am totally screwed");
+ *base_obj_offset = base_offset;
+ return offset;
+}
+
/* forward declaration for a mutually recursive function */
static int packed_object_info(struct packed_git *p, unsigned long offset,
char *type, unsigned long *sizep);
static int packed_delta_info(struct packed_git *p,
unsigned long offset,
+ enum object_type kind,
+ unsigned long obj_offset,
char *type,
unsigned long *sizep)
{
unsigned long base_offset;
- unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset;
- if (p->pack_size < offset + 20)
- die("truncated pack file");
- /* The base entry _must_ be in the same pack */
- base_offset = find_pack_entry_one(base_sha1, p);
- if (!base_offset)
- die("failed to find delta-pack base object %s",
- sha1_to_hex(base_sha1));
- offset += 20;
+ offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
/* We choose to only get the type of the base object and
* ignore potentially corrupt pack file that expects the delta
@@ -959,25 +994,6 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of
return offset + used;
}
-int check_reuse_pack_delta(struct packed_git *p, unsigned long offset,
- unsigned char *base, unsigned long *sizep,
- enum object_type *kindp)
-{
- unsigned long ptr;
- int status = -1;
-
- use_packed_git(p);
- ptr = offset;
- ptr = unpack_object_header(p, ptr, kindp, sizep);
- if (*kindp != OBJ_DELTA)
- goto done;
- hashcpy(base, (unsigned char *) p->pack_base + ptr);
- status = 0;
- done:
- unuse_packed_git(p);
- return status;
-}
-
void packed_object_info_detail(struct packed_git *p,
unsigned long offset,
char *type,
@@ -986,11 +1002,12 @@ void packed_object_info_detail(struct packed_git *p,
unsigned int *delta_chain_length,
unsigned char *base_sha1)
{
- unsigned long val;
+ unsigned long obj_offset, val;
unsigned char *next_sha1;
enum object_type kind;
*delta_chain_length = 0;
+ obj_offset = offset;
offset = unpack_object_header(p, offset, &kind, size);
for (;;) {
@@ -1005,7 +1022,13 @@ void packed_object_info_detail(struct packed_git *p,
strcpy(type, type_names[kind]);
*store_size = 0; /* notyet */
return;
- case OBJ_DELTA:
+ case OBJ_OFS_DELTA:
+ get_delta_base(p, offset, kind, obj_offset, &offset);
+ if (*delta_chain_length == 0) {
+ /* TODO: find base_sha1 as pointed by offset */
+ }
+ break;
+ case OBJ_REF_DELTA:
if (p->pack_size <= offset + 20)
die("pack file %s records an incomplete delta base",
p->pack_name);
@@ -1015,6 +1038,7 @@ void packed_object_info_detail(struct packed_git *p,
offset = find_pack_entry_one(next_sha1, p);
break;
}
+ obj_offset = offset;
offset = unpack_object_header(p, offset, &kind, &val);
(*delta_chain_length)++;
}
@@ -1023,15 +1047,15 @@ void packed_object_info_detail(struct packed_git *p,
static int packed_object_info(struct packed_git *p, unsigned long offset,
char *type, unsigned long *sizep)
{
- unsigned long size;
+ unsigned long size, obj_offset = offset;
enum object_type kind;
offset = unpack_object_header(p, offset, &kind, &size);
- if (kind == OBJ_DELTA)
- return packed_delta_info(p, offset, type, sizep);
-
switch (kind) {
+ case OBJ_OFS_DELTA:
+ case OBJ_REF_DELTA:
+ return packed_delta_info(p, offset, kind, obj_offset, type, sizep);
case OBJ_COMMIT:
case OBJ_TREE:
case OBJ_BLOB:
@@ -1077,23 +1101,15 @@ static void *unpack_compressed_entry(struct packed_git *p,
static void *unpack_delta_entry(struct packed_git *p,
unsigned long offset,
unsigned long delta_size,
+ enum object_type kind,
+ unsigned long obj_offset,
char *type,
unsigned long *sizep)
{
void *delta_data, *result, *base;
unsigned long result_size, base_size, base_offset;
- unsigned char *base_sha1;
-
- if (p->pack_size < offset + 20)
- die("truncated pack file");
- /* The base entry _must_ be in the same pack */
- base_sha1 = (unsigned char*)p->pack_base + offset;
- base_offset = find_pack_entry_one(base_sha1, p);
- if (!base_offset)
- die("failed to find delta-pack base object %s",
- sha1_to_hex(base_sha1));
- offset += 20;
+ offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
base = unpack_entry_gently(p, base_offset, type, &base_size);
if (!base)
die("failed to read delta base object at %lu from %s",
@@ -1130,13 +1146,14 @@ static void *unpack_entry(struct pack_entry *entry,
void *unpack_entry_gently(struct packed_git *p, unsigned long offset,
char *type, unsigned long *sizep)
{
- unsigned long size;
+ unsigned long size, obj_offset = offset;
enum object_type kind;
offset = unpack_object_header(p, offset, &kind, &size);
switch (kind) {
- case OBJ_DELTA:
- return unpack_delta_entry(p, offset, size, type, sizep);
+ case OBJ_OFS_DELTA:
+ case OBJ_REF_DELTA:
+ return unpack_delta_entry(p, offset, size, kind, obj_offset, type, sizep);
case OBJ_COMMIT:
case OBJ_TREE:
case OBJ_BLOB:
@@ -1186,6 +1203,24 @@ unsigned long find_pack_entry_one(const unsigned char *sha1,
return 0;
}
+static int matches_pack_name(struct packed_git *p, const char *ig)
+{
+ const char *last_c, *c;
+
+ if (!strcmp(p->pack_name, ig))
+ return 0;
+
+ for (c = p->pack_name, last_c = c; *c;)
+ if (*c == '/')
+ last_c = ++c;
+ else
+ ++c;
+ if (!strcmp(last_c, ig))
+ return 0;
+
+ return 1;
+}
+
static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
{
struct packed_git *p;
@@ -1197,7 +1232,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons
if (ignore_packed) {
const char **ig;
for (ig = ignore_packed; *ig; ig++)
- if (!strcmp(p->pack_name, *ig))
+ if (!matches_pack_name(p, *ig))
break;
if (*ig)
continue;
@@ -1382,9 +1417,10 @@ static int link_temp_to_file(const char *tmpfile, const char *filename)
dir = strrchr(filename, '/');
if (dir) {
*dir = 0;
- mkdir(filename, 0777);
- if (adjust_shared_perm(filename))
+ if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) {
+ *dir = '/';
return -2;
+ }
*dir = '/';
if (!link(tmpfile, filename))
return 0;
diff --git a/sha1_name.c b/sha1_name.c
index 6ffee2208..6d7cd7838 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -247,26 +247,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
NULL
};
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
- const char **p, *pathname;
- char *real_path = NULL;
- int refs_found = 0, am;
- unsigned long at_time = (unsigned long)-1;
+ const char **p, *ref;
+ char *real_ref = NULL;
+ int refs_found = 0;
+ int at, reflog_len;
unsigned char *this_result;
unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* At a given period of time? "@{2 hours ago}" */
- for (am = 1; am < len - 1; am++) {
- if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
- int date_len = len - am - 3;
- char *date_spec = xmalloc(date_len + 1);
- strlcpy(date_spec, str + am + 2, date_len + 1);
- at_time = approxidate(date_spec);
- free(date_spec);
- len = am;
- break;
+ /* basic@{time or number} format to query ref-log */
+ reflog_len = at = 0;
+ if (str[len-1] == '}') {
+ for (at = 1; at < len - 1; at++) {
+ if (str[at] == '@' && str[at+1] == '{') {
+ reflog_len = (len-1) - (at+2);
+ len = at;
+ break;
+ }
}
}
@@ -276,10 +275,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
for (p = fmt; *p; p++) {
this_result = refs_found ? sha1_from_ref : sha1;
- pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
- if (pathname) {
+ ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL);
+ if (ref) {
if (!refs_found++)
- real_path = xstrdup(pathname);
+ real_ref = xstrdup(ref);
if (!warn_ambiguous_refs)
break;
}
@@ -291,14 +290,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (warn_ambiguous_refs && refs_found > 1)
fprintf(stderr, warning, len, str);
- if (at_time != (unsigned long)-1) {
- read_ref_at(
- real_path + strlen(git_path(".")) - 1,
- at_time,
- sha1);
+ if (reflog_len) {
+ /* Is it asking for N-th entry, or approxidate? */
+ int nth, i;
+ unsigned long at_time;
+ for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
+ char ch = str[at+2+i];
+ if ('0' <= ch && ch <= '9')
+ nth = nth * 10 + ch - '0';
+ else
+ nth = -1;
+ }
+ if (0 <= nth)
+ at_time = 0;
+ else
+ at_time = approxidate(str + at + 2);
+ read_ref_at(real_ref, at_time, nth, sha1);
}
- free(real_path);
+ free(real_ref);
return 0;
}
diff --git a/show-index.c b/show-index.c
index c21d660b6..a30a2de5d 100644
--- a/show-index.c
+++ b/show-index.c
@@ -8,7 +8,7 @@ int main(int argc, char **argv)
static unsigned int top_index[256];
if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
- die("unable to read idex");
+ die("unable to read index");
nr = 0;
for (i = 0; i < 256; i++) {
unsigned n = ntohl(top_index[i]);
diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh
new file mode 100755
index 000000000..018fbea45
--- /dev/null
+++ b/t/t1004-read-tree-m-u-wf.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='read-tree -m -u checks working tree files'
+
+. ./test-lib.sh
+
+# two-tree test
+
+test_expect_success 'two-way setup' '
+
+ echo >file1 file one &&
+ echo >file2 file two &&
+ git update-index --add file1 file2 &&
+ git commit -m initial &&
+
+ git branch side &&
+ git tag -f branch-point &&
+
+ echo file2 is not tracked on the master anymore &&
+ rm -f file2 &&
+ git update-index --remove file2 &&
+ git commit -a -m "master removes file2"
+'
+
+test_expect_success 'two-way not clobbering' '
+
+ echo >file2 master creates untracked file2 &&
+ if err=`git read-tree -m -u master side 2>&1`
+ then
+ echo should have complained
+ false
+ else
+ echo "happy to see $err"
+ fi
+'
+
+# three-tree test
+
+test_expect_success 'three-way not complaining' '
+
+ rm -f file2 &&
+ git checkout side &&
+ echo >file3 file three &&
+ git update-index --add file3 &&
+ git commit -a -m "side adds file3" &&
+
+ git checkout master &&
+ echo >file2 file two is untracked on the master side &&
+
+ git-read-tree -m -u branch-point master side
+'
+
+test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index b3b920edb..6a917f2ff 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -30,11 +30,8 @@ rm -f .git/$m
test_expect_success \
"fail to create $n" \
"touch .git/$n_dir
- git-update-ref $n $A >out 2>err
- test "'$? = 1 &&
- test "" = "$(cat out)" &&
- grep "error: unable to resolve reference" err &&
- grep '"$n err"
+ git-update-ref $n $A >out 2>err"'
+ test $? != 0'
rm -f .git/$n_dir out err
test_expect_success \
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 6907cbcd2..acb54b6a0 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -17,13 +17,10 @@ test_expect_success \
git-commit -m "Initial commit." &&
HEAD=$(git-rev-parse --verify HEAD)'
-test_expect_success \
- 'git branch --help should return success now.' \
- 'git-branch --help'
-
test_expect_failure \
'git branch --help should not have created a bogus branch' \
- 'test -f .git/refs/heads/--help'
+ 'git-branch --help </dev/null >/dev/null 2>/dev/null || :
+ test -f .git/refs/heads/--help'
test_expect_success \
'git branch abc should create a branch' \
@@ -34,7 +31,7 @@ test_expect_success \
'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from HEAD
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
test_expect_success \
'git branch -l d/e/f should create a branch and a log' \
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
new file mode 100755
index 000000000..b1e9f2eed
--- /dev/null
+++ b/t/t3210-pack-refs.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Amos Waterland
+# Copyright (c) 2006 Christian Couder
+#
+
+test_description='git pack-refs should not change the branch semantic
+
+This test runs git pack-refs and git show-ref and checks that the branch
+semantic is still the same.
+'
+. ./test-lib.sh
+
+echo '[core] logallrefupdates = true' >>.git/config
+
+test_expect_success \
+ 'prepare a trivial repository' \
+ 'echo Hello > A &&
+ git-update-index --add A &&
+ git-commit -m "Initial commit." &&
+ HEAD=$(git-rev-parse --verify HEAD)'
+
+SHA1=
+
+test_expect_success \
+ 'see if git show-ref works as expected' \
+ 'git-branch a &&
+ SHA1=`cat .git/refs/heads/a` &&
+ echo "$SHA1 refs/heads/a" >expect &&
+ git-show-ref a >result &&
+ diff expect result'
+
+test_expect_success \
+ 'see if a branch still exists when packed' \
+ 'git-branch b &&
+ git-pack-refs --all &&
+ rm .git/refs/heads/b &&
+ echo "$SHA1 refs/heads/b" >expect &&
+ git-show-ref b >result &&
+ diff expect result'
+
+test_expect_failure \
+ 'git branch c/d should barf if branch c exists' \
+ 'git-branch c &&
+ git-pack-refs --all &&
+ rm .git/refs/heads/c &&
+ git-branch c/d'
+
+test_expect_success \
+ 'see if a branch still exists after git pack-refs --prune' \
+ 'git-branch e &&
+ git-pack-refs --all --prune &&
+ echo "$SHA1 refs/heads/e" >expect &&
+ git-show-ref e >result &&
+ diff expect result'
+
+test_expect_failure \
+ 'see if git pack-refs --prune remove ref files' \
+ 'git-branch f &&
+ git-pack-refs --all --prune &&
+ ls .git/refs/heads/f'
+
+test_expect_success \
+ 'git branch g should work when git branch g/h has been deleted' \
+ 'git-branch g/h &&
+ git-pack-refs --all --prune &&
+ git-branch -d g/h &&
+ git-branch g &&
+ git-pack-refs --all &&
+ git-branch -d g'
+
+test_expect_failure \
+ 'git branch i/j/k should barf if branch i exists' \
+ 'git-branch i &&
+ git-pack-refs --all --prune &&
+ git-branch i/j/k'
+
+test_expect_success \
+ 'test git branch k after branch k/l/m and k/lm have been deleted' \
+ 'git-branch k/l &&
+ git-branch k/lm &&
+ git-branch -d k/l &&
+ git-branch k/l/m &&
+ git-branch -d k/l/m &&
+ git-branch -d k/lm &&
+ git-branch k'
+
+test_expect_success \
+ 'test git branch n after some branch deletion and pruning' \
+ 'git-branch n/o &&
+ git-branch n/op &&
+ git-branch -d n/o &&
+ git-branch n/o/p &&
+ git-branch -d n/op &&
+ git-pack-refs --all --prune &&
+ git-branch -d n/o/p &&
+ git-branch n'
+
+test_done
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
index 360a67060..8b19d3cce 100755
--- a/t/t3401-rebase-partial.sh
+++ b/t/t3401-rebase-partial.sh
@@ -52,13 +52,10 @@ test_expect_success \
'rebase topic branch against new master and check git-am did not get halted' \
'git-rebase master && test ! -d .dotest'
-if test -z "$no_python"
-then
- test_expect_success \
+test_expect_success \
'rebase --merge topic branch that was partially merged upstream' \
'git-checkout -f my-topic-branch-merge &&
git-rebase --merge master-merge &&
test ! -d .git/.dotest-merge'
-fi
test_done
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index d34c6cf6f..0779aaa9a 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -7,12 +7,6 @@ test_description='git rebase --merge test'
. ./test-lib.sh
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
T="A quick brown fox
jumps over the lazy dog."
for i in 1 2 3 4 5 6 7 8 9 10
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index bb2531536..977c498f0 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -10,12 +10,6 @@ test_description='git rebase --merge --skip tests'
# we assume the default git-am -3 --skip strategy is tested independently
# and always works :)
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
test_expect_success setup '
echo hello > hello &&
git add hello &&
diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh
index 8f7366da8..499cafb88 100755
--- a/t/t6021-merge-criss-cross.sh
+++ b/t/t6021-merge-criss-cross.sh
@@ -10,12 +10,6 @@
test_description='Test criss-cross merge'
. ./test-lib.sh
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
test_expect_success 'prepare repository' \
'echo "1
2
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index 5ac25647a..b608e202c 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -3,12 +3,6 @@
test_description='Merge-recursive merging renames'
. ./test-lib.sh
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
test_expect_success setup \
'
cat >A <<\EOF &&
@@ -48,15 +42,20 @@ O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
EOF
git add A M &&
-git commit -m initial &&
+git commit -m "initial has A and M" &&
git branch white &&
git branch red &&
git branch blue &&
+git branch yellow &&
sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
mv A+ A &&
git commit -a -m "master updates A" &&
+git checkout yellow &&
+rm -f M &&
+git commit -a -m "yellow removes M" &&
+
git checkout white &&
sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
@@ -85,27 +84,27 @@ test_expect_success 'pull renaming branch into unrenaming one' \
git show-branch
git pull . white && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
git ls-files -s
test "$(git ls-files -u B | wc -l)" -eq 3 || {
echo "BAD: should have left stages for B"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep master || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
}
'
@@ -116,26 +115,26 @@ test_expect_success 'pull renaming branch into another renaming one' \
git checkout red
git pull . white && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
test "$(git ls-files -u B | wc -l)" -eq 3 || {
echo "BAD: should have left stages"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep red || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
}
'
@@ -145,26 +144,26 @@ test_expect_success 'pull unrenaming branch into renaming one' \
git show-branch
git pull . master && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
test "$(git ls-files -u B | wc -l)" -eq 3 || {
echo "BAD: should have left stages"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep red || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
}
'
@@ -174,35 +173,149 @@ test_expect_success 'pull conflicting renames' \
git show-branch
git pull . blue && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
test "$(git ls-files -u A | wc -l)" -eq 1 || {
echo "BAD: should have left a stage"
- exit 1
+ return 1
}
test "$(git ls-files -u B | wc -l)" -eq 1 || {
echo "BAD: should have left a stage"
- exit 1
+ return 1
}
test "$(git ls-files -u C | wc -l)" -eq 1 || {
echo "BAD: should have left a stage"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep red || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
+ }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ git show-branch
+ echo >A this file should not matter
+ git pull . white && {
+ echo "BAD: should have conflicted"
+ return 1
+ }
+ test -f A || {
+ echo "BAD: should have left A intact"
+ return 1
+ }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ git checkout white
+ git show-branch
+ rm -f A
+ echo >A this file should not matter
+ git pull . red && {
+ echo "BAD: should have conflicted"
+ return 1
+ }
+ test -f A || {
+ echo "BAD: should have left A intact"
+ return 1
+ }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f master
+ git tag -f anchor
+ git show-branch
+ git pull . yellow || {
+ echo "BAD: should have cleanly merged"
+ return 1
+ }
+ test -f M && {
+ echo "BAD: should have removed M"
+ return 1
+ }
+ git reset --hard anchor
+'
+
+test_expect_success 'updated working tree file should prevent the merge' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f master
+ git tag -f anchor
+ git show-branch
+ echo >>M one line addition
+ cat M >M.saved
+ git pull . yellow && {
+ echo "BAD: should have complained"
+ return 1
+ }
+ diff M M.saved || {
+ echo "BAD: should have left M intact"
+ return 1
+ }
+ rm -f M.saved
+'
+
+test_expect_success 'updated working tree file should prevent the merge' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f master
+ git tag -f anchor
+ git show-branch
+ echo >>M one line addition
+ cat M >M.saved
+ git update-index M
+ git pull . yellow && {
+ echo "BAD: should have complained"
+ return 1
+ }
+ diff M M.saved || {
+ echo "BAD: should have left M intact"
+ return 1
+ }
+ rm -f M.saved
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f yellow
+ git tag -f anchor
+ git show-branch
+ echo >M this file should not matter
+ git pull . master || {
+ echo "BAD: should have cleanly merged"
+ return 1
+ }
+ test -f M || {
+ echo "BAD: should have left M intact"
+ return 1
+ }
+ git ls-files -s | grep M && {
+ echo "BAD: M must be untracked in the result"
+ return 1
}
+ git reset --hard anchor
'
test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 2488e6eae..07cb706fa 100755
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -207,7 +207,8 @@ test_done () {
# t/ subdirectory and are run in trash subdirectory.
PATH=$(pwd)/..:$PATH
GIT_EXEC_PATH=$(pwd)/..
-export PATH GIT_EXEC_PATH
+HOME=$(pwd)/trash
+export PATH GIT_EXEC_PATH HOME
# Similarly use ../compat/subprocess.py if our python does not
# have subprocess.py on its own.
diff --git a/tree-diff.c b/tree-diff.c
index 7e2f4f088..37d235e06 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -215,6 +215,24 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
return retval;
}
+int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ tree = read_object_with_reference(new, tree_type, &real.size, NULL);
+ if (!tree)
+ die("unable to read root tree (%s)", sha1_to_hex(new));
+ real.buf = tree;
+
+ empty.size = 0;
+ empty.buf = "";
+ retval = diff_tree(&empty, &real, base, opt);
+ free(tree);
+ return retval;
+}
+
static int count_paths(const char **paths)
{
int i = 0;
diff --git a/unpack-trees.c b/unpack-trees.c
index 3ac0289b3..7cfd628d8 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -642,7 +642,7 @@ int threeway_merge(struct cache_entry **stages,
(remote_deleted && head && head_match)) {
if (index)
return deleted_entry(index, index, o);
- else if (path)
+ else if (path && !head_deleted)
verify_absent(path, "removed", o);
return 0;
}
@@ -661,8 +661,6 @@ int threeway_merge(struct cache_entry **stages,
if (index) {
verify_uptodate(index, o);
}
- else if (path)
- verify_absent(path, "overwritten", o);
o->nontrivial_merge = 1;
diff --git a/upload-pack.c b/upload-pack.c
index 189b239cc..ddaa72f0a 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -16,7 +16,7 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n
#define OUR_REF (1U << 1)
#define WANTED (1U << 2)
static int multi_ack, nr_our_refs;
-static int use_thin_pack;
+static int use_thin_pack, use_ofs_delta;
static struct object_array have_obj;
static struct object_array want_obj;
static unsigned int timeout;
@@ -137,7 +137,9 @@ static void create_pack_file(void)
close(pu_pipe[1]);
close(pe_pipe[0]);
close(pe_pipe[1]);
- execl_git_cmd("pack-objects", "--stdout", "--progress", NULL);
+ execl_git_cmd("pack-objects", "--stdout", "--progress",
+ use_ofs_delta ? "--delta-base-offset" : NULL,
+ NULL);
kill(pid_rev_list, SIGKILL);
die("git-upload-pack: unable to exec git-pack-objects");
}
@@ -393,6 +395,8 @@ static void receive_needs(void)
multi_ack = 1;
if (strstr(line+45, "thin-pack"))
use_thin_pack = 1;
+ if (strstr(line+45, "ofs-delta"))
+ use_ofs_delta = 1;
if (strstr(line+45, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
else if (strstr(line+45, "side-band"))
@@ -416,9 +420,9 @@ static void receive_needs(void)
}
}
-static int send_ref(const char *refname, const unsigned char *sha1)
+static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
- static const char *capabilities = "multi_ack thin-pack side-band side-band-64k";
+ static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta";
struct object *o = parse_object(sha1);
if (!o)
@@ -444,8 +448,8 @@ static int send_ref(const char *refname, const unsigned char *sha1)
static void upload_pack(void)
{
reset_timeout();
- head_ref(send_ref);
- for_each_ref(send_ref);
+ head_ref(send_ref, NULL);
+ for_each_ref(send_ref, NULL);
packet_flush(1);
receive_needs();
if (want_obj.nr) {
diff --git a/wt-status.c b/wt-status.c
index 4b74e6858..9692dfa32 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -41,10 +41,8 @@ void wt_status_prepare(struct wt_status *s)
s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0;
- head = resolve_ref(git_path("HEAD"), sha1, 0);
- s->branch = head ?
- strdup(head + strlen(get_git_dir()) + 1) :
- NULL;
+ head = resolve_ref("HEAD", sha1, 0, NULL);
+ s->branch = head ? xstrdup(head) : NULL;
s->reference = "HEAD";
s->amend = 0;
@@ -72,25 +70,25 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p)
color_printf(color(WT_STATUS_HEADER), "#\t");
switch (p->status) {
case DIFF_STATUS_ADDED:
- color_printf(c, "new file: %s", p->one->path); break;
+ color_printf(c, "new file: %s", p->one->path); break;
case DIFF_STATUS_COPIED:
- color_printf(c, "copied: %s -> %s",
+ color_printf(c, "copied: %s -> %s",
p->one->path, p->two->path);
break;
case DIFF_STATUS_DELETED:
- color_printf(c, "deleted: %s", p->one->path); break;
+ color_printf(c, "deleted: %s", p->one->path); break;
case DIFF_STATUS_MODIFIED:
- color_printf(c, "modified: %s", p->one->path); break;
+ color_printf(c, "modified: %s", p->one->path); break;
case DIFF_STATUS_RENAMED:
- color_printf(c, "renamed: %s -> %s",
+ color_printf(c, "renamed: %s -> %s",
p->one->path, p->two->path);
break;
case DIFF_STATUS_TYPE_CHANGED:
color_printf(c, "typechange: %s", p->one->path); break;
case DIFF_STATUS_UNKNOWN:
- color_printf(c, "unknown: %s", p->one->path); break;
+ color_printf(c, "unknown: %s", p->one->path); break;
case DIFF_STATUS_UNMERGED:
- color_printf(c, "unmerged: %s", p->one->path); break;
+ color_printf(c, "unmerged: %s", p->one->path); break;
default:
die("bug: unhandled diff status %c", p->status);
}
@@ -156,10 +154,8 @@ void wt_status_print_initial(struct wt_status *s)
static void wt_status_print_updated(struct wt_status *s)
{
struct rev_info rev;
- const char *argv[] = { NULL, NULL, NULL };
- argv[1] = s->reference;
init_revisions(&rev, NULL);
- setup_revisions(2, argv, &rev, NULL);
+ setup_revisions(0, NULL, &rev, s->reference);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = wt_status_print_updated_cb;
rev.diffopt.format_callback_data = s;
@@ -170,9 +166,8 @@ static void wt_status_print_updated(struct wt_status *s)
static void wt_status_print_changed(struct wt_status *s)
{
struct rev_info rev;
- const char *argv[] = { NULL, NULL };
init_revisions(&rev, "");
- setup_revisions(1, argv, &rev, NULL);
+ setup_revisions(0, NULL, &rev, NULL);
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = wt_status_print_changed_cb;
rev.diffopt.format_callback_data = s;
@@ -227,10 +222,8 @@ static void wt_status_print_untracked(const struct wt_status *s)
static void wt_status_print_verbose(struct wt_status *s)
{
struct rev_info rev;
- const char *argv[] = { NULL, NULL, NULL };
- argv[1] = s->reference;
init_revisions(&rev, NULL);
- setup_revisions(2, argv, &rev, NULL);
+ setup_revisions(0, NULL, &rev, s->reference);
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
rev.diffopt.detect_rename = 1;
run_diff_index(&rev, 1);
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 714c56354..07995ec33 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -86,11 +86,10 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
if (len > 0 &&
(isalpha((unsigned char)*rec) || /* identifier? */
*rec == '_' || /* also identifier? */
- *rec == '(' || /* lisp defun? */
- *rec == '#')) { /* #define? */
+ *rec == '$')) { /* mysterious GNU diff's invention */
if (len > sz)
len = sz;
- if (len && rec[len - 1] == '\n')
+ while (0 < len && isspace((unsigned char)rec[len - 1]))
len--;
memcpy(buf, rec, len);
*ll = len;